Infrastructure
Azure DevOps : contenir un agent auto-hébergé compromis avant de rouvrir le pool
Un runbook de production pour isoler un agent Azure DevOps auto-hébergé suspect, conserver les preuves, borner les identités exposées et valider un remplacement sain avant de rouvrir le pool aux jobs de production.
Un agent Azure DevOps auto-hébergé exécute du code récupéré depuis les dépôts et accède aux systèmes privés nécessaires au pipeline. C’est précisément pour cela qu’un processus inattendu, une tâche modifiée, une connexion sortante vers une destination inconnue ou un secret visible dans un log ne doivent pas être traités comme une simple panne de runner.
Le cas fil rouge est un agent Linux d’un pool de production. Une alerte endpoint signale un shell lancé par une tâche de build, puis une connexion vers une adresse hors du chemin de sortie attendu. Le même hôte vient d’exécuter des déploiements avec une service connection et un feed privé. L’agent est toujours online. L’objectif immédiat n’est pas de prouver toute la chaîne d’attaque : il faut arrêter les nouveaux jobs, conserver les preuves utiles et borner les identités susceptibles d’avoir traversé l’hôte avant de décider si le pool peut rouvrir.
Geler le chemin d’exécution sans effacer les preuves
Commencez par un contrat d’incident court. Un hôte suspect, l’identité de l’agent et l’identité d’un pipeline sont trois périmètres différents. Notez-les avant qu’une rotation ou une suppression d’urgence ne rende la chronologie illisible.
Fenetre d'incident: 2026-10-02 13:10Z a maintenant
Organisation / projet: naxaya-devops / platform
Pool / agent: prod-private-linux / ado-prod-07
Hote: vm-ado-prod-07
Signal: processus enfant et destination sortante inattendus
Derniere execution saine: deploy-api #2418
Executions apres le signal: #2419, #2420
Bornes immediates
Empecher les nouveaux jobs d'atteindre cet agent
Ne pas supprimer l'agent ni nettoyer le workspace
Suspendre les pipelines production qui ciblent le pool
Conserver run IDs, commit SHAs et timestamps UTC
Decision attendue
Faux positif et retour controle
Reconstruction et rotation des acces
Confinement elargi du pool et escalade incident Désactivez l’agent ou retirez-le de l’ordonnancement, puis suspendez les points d’entrée de production qui peuvent encore cibler le pool. Une demand qui exclut momentanément la machine ne suffit pas : un changement YAML pourrait la contourner. Si d’autres agents partagent la même image, le même cache, le même token de bootstrap ou le même chemin d’administration, considérez le pool comme un périmètre commun possible tant que les preuves ne l’ont pas réduit.
L’isolation réseau doit conserver le canal de réponse et suivre la procédure forensique de l’organisation. Un arrêt brutal, la suppression de la VM ou un deny sur tout le subnet peut détruire des preuves volatiles, couper l’accès des intervenants ou interrompre des agents sains qui partagent le chemin.
Conserver d’abord la chronologie du plan de contrôle
Azure DevOps détient des preuves qui ne dépendent pas de l’hôte suspect. Capturez-les avant de modifier les permissions :
- IDs des pipelines, stages, jobs, versions de tâches et commit SHAs ;
- heures de mise en file, d’affectation et nom de l’agent pour chaque exécution de la fenêtre ;
- changements de YAML, branches protégées, variable groups, secure files et service connections ;
- ajouts ou suppressions d’agents, changements de permissions du pool et autorisations de pipelines dans l’audit log ;
- approbations, checks et historique de déploiement des environments.
Pour chaque execution concernee
run_id, pipeline_id, commit_sha
queued_at_utc, started_at_utc, finished_at_utc
pool_name, agent_name, job_name
repositories et versions de taches
service connections et ressources protegees utilisees
artefacts produits ou promus
Pour chaque changement administratif
event_id, acteur, timestamp_utc
ressource, ancien scope, nouveau scope
approbation ou ticket associe Exportez ou placez ces éléments en rétention selon le processus d’incident. Évitez de coller les logs bruts dans un ticket largement accessible : le masquage est utile, mais il ne prouve pas que chaque token, argument de ligne de commande ou secret structuré a été retiré.
Collecter les preuves hôte sans relancer le pipeline
L’hôte peut répondre à des questions utiles sans exécuter un nouveau job. Capturez les diagnostics de l’agent, le journal du service, les processus actifs, l’état réseau récent et un inventaire borné des fichiers modifiés. Calculez les empreintes avant de transférer les preuves vers un stockage approuvé.
INCIDENT_DIR="/var/tmp/ado-ir-20261002"
AGENT_HOME="/opt/azdo-agent"
sudo install -d -m 0700 "$INCIDENT_DIR"
sudo systemctl status 'vsts.agent.*' --no-pager > "$INCIDENT_DIR/systemd-status.txt"
sudo journalctl -u 'vsts.agent.*' --since '2026-10-02 13:00:00 UTC' --no-pager > "$INCIDENT_DIR/agent-journal.txt"
ps auxwwf > "$INCIDENT_DIR/processes.txt"
ss -tpna > "$INCIDENT_DIR/sockets.txt"
sudo find "$AGENT_HOME/_diag" -maxdepth 1 -type f -newermt '2026-10-02 13:00:00 UTC' -print > "$INCIDENT_DIR/diag-files.txt"
sudo find "$AGENT_HOME/_work" -xdev -type f -newermt '2026-10-02 13:00:00 UTC' -printf '%TY-%Tm-%TdT%TH:%TM:%TSZ %s %p
' > "$INCIDENT_DIR/work-files.txt"
sudo sha256sum "$INCIDENT_DIR"/* > "$INCIDENT_DIR/SHA256SUMS" Adaptez les chemins et commandes à l’image. N’archivez pas tout le répertoire de travail par défaut : il peut contenir du code source, des artefacts et des identifiants actifs. N’exécutez pas le script suspect pour reproduire l’alerte. Si une capture mémoire, un snapshot disque ou un outil endpoint sont nécessaires, passez le relais à l’équipe autorisée à les collecter.
Construire la carte d’exposition avant toute rotation
L’identifiant d’enregistrement permet au runner de communiquer avec son pool. Les jobs peuvent recevoir des accès totalement différents : workload identity federation, service connections, secrets de variable groups, secure files, tokens de registry, clés SSH ou managed identity attachée à l’hôte. La rotation doit suivre l’exposition observée, pas la plus longue liste d’identifiants dont l’équipe se souvient.
Identifiant ou trust Preuve d'utilisation Controle immediat
Enregistrement agent Session dans la fenetre Garder agent en quarantaine
Service connection WIF Token emis pour le run Desactiver ou restreindre
Managed identity Sign-in / ressource Azure Bloquer hote; revoir usage role
Secret variable group Inputs pipeline et tache Revoquer; tourner a la source
Secure file Evenement ou tache download Retirer autorisation; remplacer
Token registry ou feed Logs client et service Revoquer; inspecter packages
Cle SSH de deploiement Log cible d'authentification Retirer; auditer la cible
Pour chaque element
owner, scope, derniere utilisation, expiration, dependance de remplacement
runs qui l'ont recu et systemes qu'il pouvait atteindre Commencez par les identifiants dont la présence est prouvée dans les jobs concernés, puis élargissez aux ressources joignables avec la même identité. Pour un accès fédéré, contrôlez l’émission du token, son subject et son audience autant que la définition de la service connection. Pour une managed identity, lisez les sign-ins Azure, l’Activity Log et les logs des ressources sur la fenêtre : il n’existe peut-être aucun secret statique à tourner, mais l’hôte compromis a pu demander des tokens.
Évitez la rotation générale sans carte de dépendances. Elle peut transformer un incident de sécurité en plusieurs incidents de disponibilité tout en laissant intact le véritable chemin d’accès.
Distinguer une exécution malveillante d’un runner contaminé
Une même alerte peut provenir de couches différentes :
- une étape de build autorisée a lancé un scanner ou un installateur avec un processus enfant inattendu ;
- un commit, un template ou une tâche tierce a changé et exécuté du code indésirable ;
- une dépendance ou un artefact a été remplacé en amont ;
- l’image de l’agent, son bootstrap ou son workspace persistant a été modifié ;
- un opérateur a utilisé l’hôte hors pipeline.
Comparez l’exécution suspecte à une exécution saine du même pipeline. Comparez le YAML résolu, le commit, les templates, les versions de tâches, les artefacts téléchargés, l’environnement et les destinations sortantes. Comparez ensuite l’hôte à un agent propre construit depuis la même image. Un changement qui suit le commit oriente vers la charge ; un changement présent avant le checkout oriente vers l’image ou l’hôte. Aucune de ces observations ne suffit seule à blanchir l’autre couche.
Reconstruire la confiance depuis une frontière saine
Quand une exécution arbitraire ou un accès aux identifiants sur l’hôte est plausible, un nettoyage sur place ne rétablit pas la confiance. Construisez un remplacement depuis la dernière image approuvée et la source de bootstrap validée. Attribuez-lui un nouvel enregistrement d’agent, appliquez les correctifs, restaurez seulement les outils déclarés et rattachez-le à un pool canari restreint.
Ne copiez ni _work, ni les caches d’outils, ni le matériel SSH, ni la configuration agent de la machine en quarantaine. Épinglez les artefacts de bootstrap et les versions de tâches lorsque le modèle de livraison le permet. Exécutez le service avec une identité non interactive et à privilèges minimaux, distincte de l’administrateur qui enregistre l’agent. Restreignez les pipelines autorisés à utiliser le pool au lieu de le rouvrir à tous les projets.
trigger: none
pool:
name: prod-private-linux-canary
steps:
- checkout: none
- bash: |
set -euo pipefail
id
uname -a
test -w "$(Agent.TempDirectory)"
getent hosts dev.azure.com
displayName: Valider le runtime sans ressource protegee Le premier canari ne doit charger ni variable group, ni secure file, ni service connection, ni dépôt de production. Validez la session agent, l’état attendu de l’OS, le DNS, le proxy et la sortie réseau. Utilisez ensuite un dépôt en lecture seule et un artefact jetable. N’ajoutez qu’une ressource protégée à la fois, après validation de son remplacement ou de sa confiance résiduelle.
Décider si le pool peut rouvrir
La remise en service est une décision de sécurité assortie de critères opérationnels :
ROUVRIR AVEC DES AGENTS SAINS
Fenetre et executions concernees bornees
Hotes suspects toujours en quarantaine
Identifiants exposes revoques ou remplaces
Provenance image et bootstrap verifiee
Canari valide sans sortie inattendue
Permissions pool et ressources protegees restreintes
Detection et audit actifs
GARDER LE POOL FERME
Jobs ou identites inconnus dans la fenetre
Meme indicateur present sur un autre agent
Artefact produit impossible a declarer fiable
Rotation incomplete ou activite cible inexpliquee
ROLLBACKER LA REMISE EN SERVICE
Suspendre pipelines et retirer autorisation du pool
Desactiver les nouveaux identifiants introduits
Isoler la cohorte de la nouvelle image
Revenir au chemin de secours deja valide Gardez l’hôte d’origine hors du pool jusqu’à la décision de rétention des preuves. Fermer l’alerte ne signifie pas que la frontière d’exécution peut être autorisée de nouveau.
Conclusion
Un agent auto-hébergé suspect se trouve au croisement du code source, des identités de déploiement, du réseau privé et des cibles de production. Le confinement commence donc par l’arrêt de l’ordonnancement sans effacer l’hôte, puis par la reconstruction de la chronologie du plan de contrôle et la carte des accès qui ont réellement pu traverser le runner.
La décision finale reste volontairement étroite : rouvrir le pool avec des agents sains et des accès renouvelés uniquement lorsque la fenêtre, les artefacts et les actions aval sont expliqués ; sinon, le garder fermé et utiliser le chemin de secours validé. Le rollback est tout aussi explicite : retirer l’autorisation du pool avant qu’un nouveau job de production ne transforme l’incertitude en second incident.