Un serveur de labo, trois rôles de confiance, un seul domaine de panne
Une entrée VPN, des services privés et des tâches CI ont besoin de privilèges séparés même sur un seul serveur. Séparez les rôles, isolez les réseaux, gardez la récupération par console.
Tout fonctionne jusqu’à ce que l’exécuteur de build ait besoin du même socket d’hôte que l’invité VPN, et alors personne ne peut expliquer pourquoi une tâche peut lire les clés de la passerelle. Tout a été installé sur une seule machine parce que ça tenait, et chaque invité a reçu un accès large parce que c’était le moyen le plus rapide de le faire fonctionner. Le résultat est un hôte dont la frontière de confiance est le câble d’alimentation. Séparer les rôles demande moins de travail qu’il n’y paraît, et cela commence par les nommer.
Trois rôles qui partagent actuellement tout
Une entrée VPN, un ensemble de services privés et un exécuteur de build veulent des choses différentes. Le premier a besoin d’un port public et d’un réseau étroit côté privé. Le deuxième a besoin d’un stockage persistant et de secrets d’application. Le troisième exécute du code que quelqu’un d’autre a écrit.
C’est le fait de donner le même accès aux trois qui rend le symptôme déroutant, car ensuite chacun peut affecter les autres. Une tâche de build qui atteint le socket de l’hyperviseur est une tâche qui peut arrêter le VPN. Un invité de services avec une route vers le réseau de gestion peut changer la configuration du commutateur. Les privilèges sont le problème, et la liste des processus n’est que l’endroit où cela se voit. Nommer d’abord les rôles donne à chaque privilège un endroit où vivre.
Le croquis ci-dessous est un point de départ pour cette séparation, pas une promesse de haute disponibilité. Traitez-le comme une hypothèse à vérifier, pas comme la conception achevée.
Séparez trois rôles, pas trois processus
Trois invités sur un hôte donnent trois ensembles de privilèges et trois positions réseau. Ils ne donnent pas trois domaines de panne, et la conception devrait le dire tout haut.
La séparation n’aide que si une compromission dans un invité n’accorde pas automatiquement les autres, et c’est un réglage de l’hyperviseur, pas une convention de nommage. Appeler une VM passerelle n’empêche pas un autre invité d’atteindre l’hôte. Lisez la configuration du commutateur virtuel plutôt que le nom d’hôte, puis confirmez que chaque invité se trouve là où vous pensez.
host
├─ vpn-gw 10.60.10.2/24 public 51820/udp only
├─ services 10.60.0.10/24 no route to the management network
└─ ci-runner 10.60.20.2/24 outbound only, no host socket
Écrivez la topologie, puis vérifiez-la avec les outils qui la possèdent. Un invité qui apparaît sur le mauvais pont est une constatation, pas un détail. Corrigez le réseau avant de changer toute configuration de service.
Laissez l’invité VPN n’ouvrir que les portes qu’il possède
Publiez le port du tunnel sur le routeur et rien d’autre. Les ports d’administration des invités relèvent d’un chemin de gestion, pas d’internet, et l’interface propre à l’hyperviseur en relève aussi.
Le réseau côté privé devrait atteindre les services dont les utilisateurs distants ont besoin et s’arrêter là. L’authentification applicative reste activée, car un tunnel est un transport et non une connexion. Gardez la table de routage de l’invité VPN assez petite pour la décrire en une phrase. Si vous ne pouvez pas la décrire aussi simplement, la frontière s’est déjà brouillée.
virsh net-list --all
virsh net-dumpxml lab-private
sudo nft list ruleset | grep -n -E '51820|10.60'
Tout ce qui écoute sur l’hôte et n’est pas nécessaire à la gestion ne devrait pas écouter du tout. Cela inclut l’API de l’hyperviseur, qu’il est facile de laisser exposée sur une interface publique.
Les tâches de build ont besoin d’un autre type de frontière
Une tâche CI exécute des commandes contrôlées par le dépôt, donc sa confiance dépend de qui peut pousser du code. Un exécuteur shell lance ces commandes en tant qu’utilisateur de l’exécuteur sur l’hôte, et c’est la frontière la plus large disponible.
Les exécuteurs de conteneurs sont plus étroits, et un conteneur privilégié n’est pas de ceux-là. Gardez l’exécuteur dans son propre invité avec sa propre portée réseau, et ne lui donnez que l’accès aux artefacts et au déploiement dont les tâches ont besoin. Les travailleurs jetables réduisent l’état conservé ; ils ne réduisent ni l’accessibilité ni l’exposition des secrets. Les deux ont encore besoin de limites explicites.
L’invité de services possède les données, donc son plan de sauvegarde est celui qui doit fonctionner. Gardez les dépôts, les téléversements et la base de données dans le même calendrier. Testez une restauration au lieu de lire l’état de la tâche.
Planifiez la panne que les trois partagent
Un hôte est un domaine de panne. Un redémarrage supprime le tunnel et tous les services privés au même instant, et c’est exactement quand une voie d’entrée compte le plus.
Gardez un accès console qui ne dépend pas de l’invité VPN, qu’il s’agisse d’une console physique, d’une interface de gestion hors bande ou d’une console du fournisseur. Un instantané stocké sur le même disque n’est pas une copie indépendante, et une sauvegarde qui s’exécute à l’intérieur de l’invité qu’elle protège ne l’est pas non plus. Testez ensuite le démarrage à froid comme une séquence et non comme une liste : hôte, réseaux virtuels, chemin du tunnel et une application nommée sous un compte ordinaire.
virsh list --all
systemctl is-active libvirtd
curl -sS -m 5 -o /dev/null -w '%{http_code}\n' https://10.60.0.10/
ss -ltnp | grep -E ':51820|:443'
L’ordre de démarrage des processus ne prouve rien sur les routes et le DNS, alors testez le chemin plutôt que les états des unités. Le chemin est ce dont un utilisateur distant a réellement besoin.