Automation

Azure DevOps : contenir un secret exposé dans les logs avant de relancer le pipeline

Un runbook de production pour borner une fuite de secret dans les logs Azure DevOps, préserver les preuves, révoquer les accès, valider le masquage et décider la reprise ou le rollback.

20 sept. 2026 azureazure-devopspipelinessecretssecurityincidentkey-vaultautomationauditobservabilityrunbookrollbackproduction

Un pipeline Azure DevOps termine en erreur et une valeur sensible apparaît en clair dans le log d’une tâche. Le premier réflexe est souvent de masquer la ligne, de supprimer le run ou de faire tourner tous les secrets du projet. Ces actions peuvent réduire l’exposition visible, mais elles ne disent ni quel secret a fuité, ni où il a été copié, ni si une révocation immédiate va interrompre une charge de production.

Le cas fil rouge est un pipeline de déploiement qui lit une valeur depuis Azure Key Vault, la transmet à un script, puis affiche accidentellement une commande développée. Le même secret peut aussi avoir atteint un artifact de diagnostic, le workspace d’un agent auto-hébergé ou un système de logs externe. Ce runbook contient l’incident, qualifie la portée réelle, remplace l’identifiant compromis et n’autorise la reprise qu’après un canari sans nouvelle fuite.

Geler les écritures sans détruire les preuves

Empêchez d’abord une nouvelle exposition. Désactivez le déclenchement automatique concerné, suspendez les stages capables d’écrire en production et bloquez les relances manuelles non approuvées. N’effacez pas immédiatement le run : sa chronologie, son identité d’exécution et ses artifacts sont nécessaires pour borner l’incident.

Capturez une fiche d’incident qui ne contient jamais la valeur secrète :

yaml secret-exposure-incident.yml
incident_id: sec-2026-09-20-01
detected_utc: <timestamp>

pipeline:
project: <project>
definition_id: <id>
run_id: <id>
commit: <sha>
stage: <stage>
job: <job>
task: <task>

suspected_secret:
source: azure-key-vault
logical_name: <name-without-value>
version_or_revision: <version>
fingerprint: <keyed-fingerprint>

containment:
automatic_triggers_paused: true
production_writes_blocked: true
evidence_access_restricted: true

decision_owner: <role>
rollback_owner: <role>

Conservez l’accès aux preuves dans un groupe restreint. Un log compromis ne doit pas être recopié dans un ticket généraliste, un canal de discussion ou un rapport non protégé. Référencez le run et les timestamps ; ne collez pas la ligne sensible.

Identifier le secret sans le republier

Le nom de variable affiché par une tâche n’est pas une preuve suffisante. Une variable peut être alimentée par un Variable Group, une référence Key Vault, une sortie de tâche ou une transformation locale. Reconstituez le chemin de valeur depuis la définition du pipeline jusqu’au processus qui l’a écrite.

Pour rechercher la même valeur dans des emplacements autorisés sans la consigner à nouveau, calculez une empreinte HMAC avec une clé d’incident éphémère. Une simple empreinte non salée reste vulnérable si le secret a peu d’entropie.

bash fingerprint-secret.sh
set -euo pipefail

read -rsp "Secret value: " SECRET_VALUE
printf '
'
read -rsp "Incident HMAC key: " INCIDENT_KEY
printf '
'

FINGERPRINT=$(printf '%s' "${SECRET_VALUE}" | openssl dgst -sha256 -hmac "${INCIDENT_KEY}" -binary | base64)

printf 'fingerprint=%s
' "${FINGERPRINT}"
unset SECRET_VALUE INCIDENT_KEY FINGERPRINT

Exécutez cette opération uniquement dans un poste d’investigation maîtrisé, sans historique de shell ni collecte de terminal. L’empreinte sert à comparer des copies déjà autorisées ; elle ne rend pas acceptable le téléchargement massif de logs ou d’artifacts.

Borner la fenêtre et les copies

Partez de la première exécution contenant la valeur, puis cherchez la dernière exécution saine avec le même commit, la même définition, les mêmes templates et la même source de secret. La fenêtre doit tenir compte des retries, des jobs parallèles et des pipelines consommateurs du même Variable Group ou du même secret Key Vault.

Classez les emplacements plutôt que de traiter le log comme l’unique copie :

text exposure-scope.txt
Emplacement                         Contrôle
Log du job                         Ligne, timestamp, tâche et niveau d'accès
Artifact de diagnostic             Contenu, rétention, téléchargements autorisés
Workspace de l'agent               Fichiers temporaires, cache, permissions locales
Sortie de tâche                     Variables propagées aux jobs ou stages suivants
Système de logs externe            Ingestion, index, export et politique de rétention
Notification ou webhook            Payload envoyé et destinataires
Fork, copie ou export manuel       Propriétaire et emplacement approuvé

Pour chaque copie
Conserver la référence et l'empreinte, jamais la valeur dans le ticket
Restreindre l'accès avant nettoyage
Documenter suppression, expiration ou impossibilité de retrait
Considérer toute copie téléchargée comme hors du contrôle du pipeline

Consultez les événements d’audit disponibles pour identifier les consultations, téléchargements ou modifications pendant la fenêtre. L’absence d’événement ne prouve pas l’absence de lecture : elle borne seulement les traces dont vous disposez.

Qualifier l’impact avant la rotation

Une valeur visible n’est pas toujours un credential actif, mais elle doit être traitée comme compromise tant que son usage n’est pas exclu. Déterminez son type, son émetteur, sa date d’expiration, ses droits, ses cibles et ses consommateurs réels.

Séparez quatre questions :

  • la valeur permet-elle encore de s’authentifier ;
  • quels verbes et quelles ressources sont accessibles ;
  • quelles charges utilisent encore cette version ;
  • quelles preuves indiqueraient un usage après l’exposition.

Pour un secret applicatif, corrélez les journaux de connexion et ceux de la ressource cible avec la fenêtre d’incident, l’identité attendue, les IP connues et les opérations normales. Pour une clé d’API ou un token propre à un service, utilisez les journaux du fournisseur concerné. Ne déduisez pas un compromis ou son absence depuis le seul résultat du pipeline.

Corriger la cause avant de produire une nouvelle valeur

Faire tourner le secret alors que le pipeline imprime encore ses arguments crée une seconde fuite. Corrigez d’abord le chemin d’exposition dans une branche contrôlée.

Les causes fréquentes sont explicites : set -x, Write-Host ou echo sur une commande développée, dump de l’environnement, sérialisation d’un objet de configuration, message d’erreur qui reprend l’URL complète, ou artifact de debug trop large. Le masquage Azure DevOps reste une protection de dernier recours. Il ne faut pas lui demander de reconnaître toutes les sous-chaînes, encodages, découpes ou transformations possibles d’un secret.

Remplacez le passage du secret en argument par un canal adapté au programme : entrée standard, fichier temporaire aux permissions strictes, variable d’environnement consommée sans affichage, ou identité fédérée/managée lorsque le backend le permet. Désactivez la trace autour de la lecture et restaurez-la seulement après destruction de la valeur locale.

Ajoutez un test négatif avec une valeur canari inoffensive et unique. Le test doit échouer si la valeur brute, son encodage usuel ou une URL qui la contient apparaît dans les logs ou artifacts produits.

Révoquer dans un ordre qui reste opérable

Préparez une nouvelle version, mettez à jour un consommateur canari, puis validez son accès avec l’identité et le chemin réseau réels. Une fois la nouvelle version prouvée, migrez les consommateurs par cohortes bornées et observez les refus de l’ancienne valeur.

Si le secret permet une écriture sensible ou si un usage non autorisé est visible, la priorité est la révocation immédiate, même si elle provoque une interruption contrôlée. Si aucun usage suspect n’est observé et que plusieurs consommateurs critiques dépendent encore de l’ancienne valeur, une courte période de chevauchement peut être décidée par le responsable d’incident. Elle doit avoir une échéance, une télémétrie et un arrêt explicites.

Ne révoquez pas une identité complète lorsqu’une credential ou une version suffit. À l’inverse, ne conservez pas un credential ancien uniquement parce qu’un consommateur inconnu pourrait exister : retrouvez son propriétaire, migrez-le ou acceptez explicitement son arrêt.

Valider un run canari sans nouvelle fuite

La reprise commence par un run manuel et borné sur une cible sans effet métier irréversible. Utilisez la nouvelle valeur, le correctif de logging et la même classe d’agent que la production.

text pipeline-resume-gate.txt
Le canari est acceptable si
La valeur canari n'apparaît dans aucun log ni artifact
Le fingerprint de la nouvelle valeur est absent des emplacements contrôlés
L'authentification utilise la version et l'identité attendues
Les refus avec l'ancienne credential sont expliqués
Aucun consommateur critique ne dépend encore de l'ancienne version
Les accès aux preuves compromises restent restreints
Le test négatif de non-divulgation bloque une régression

Décision
REPRENDRE: critères satisfaits et ancienne credential révoquée
MAINTENIR LE BLOCAGE: portée ou consommateurs encore inconnus
ROLLBACKER LE CODE: le correctif casse le pipeline sans arrêter la révocation

Le rollback du code et celui du secret sont deux décisions différentes. Vous pouvez revenir au dernier script fonctionnel seulement s’il ne réintroduit pas la fuite. La révocation d’une valeur compromise ne doit pas être annulée pour faire repasser un pipeline.

Conclusion

Un secret affiché dans Azure DevOps est un incident de chaîne d’exécution, pas une simple ligne à masquer. Il faut contenir les nouveaux runs, préserver les preuves, identifier la valeur sans la recopier, borner ses copies et ses droits, corriger le chemin de logging, puis révoquer selon l’impact réel.

La décision de reprise est vérifiable : un canari fonctionne avec la nouvelle credential, aucune représentation sensible n’apparaît dans les sorties, l’ancienne valeur est révoquée et les consommateurs critiques sont connus. Tant qu’un de ces points reste ambigu, le pipeline demeure bloqué ; on ne transforme pas l’absence de preuve en autorisation de redéployer.