Infrastructure
Azure DevOps WIF : diagnostiquer une dérive d'issuer ou de subject avant de réintroduire un secret
Un runbook de production pour comparer service connection, federated credential, issuer, subject, audience et RBAC avant de réparer, migrer ou rollbacker une identité Azure DevOps.
Un pipeline Azure DevOps qui déployait sans secret échoue soudain avec AADSTS70021, AADSTS700211 ou AADSTS700213. La service connection existe, l’identité possède encore ses rôles Azure et le YAML n’a pas changé. Sous pression, le contournement paraît évident : recréer un client secret et relancer la production.
Ce contournement mélange pourtant trois contrats différents : Azure DevOps émet un token avec un issuer, un subject et une audience ; Microsoft Entra cherche un federated credential strictement compatible ; Azure RBAC autorise ensuite l’identité sur une cible. Le cas fil rouge est une service connection Azure Resource Manager qui utilise workload identity federation et dont le contrat a dérivé après une conversion, une recréation ou un changement de configuration. Le runbook doit aboutir à une décision explicite : réparer le mapping, terminer la migration d’issuer, rollbacker la modification ou bloquer la livraison sans revenir durablement au secret.
Figer l’échec avant de modifier l’identité
Conservez un run précis, son horodatage UTC, la tâche en échec et le message Entra complet. Relevez l’organisation, le projet, l’identifiant de la service connection, son type d’identité, le tenant, le client ID et le scope Azure attendu. Un nom d’affichage ne suffit pas : il peut être identique dans plusieurs projets ou avoir été réutilisé après recréation.
incident: INC-CI-492
pipeline_run: 20260929.3
failed_at_utc: 2026-09-29T06:42:18Z
azure_devops:
organization: platform-ops
project: delivery
service_connection_id: <endpoint-guid>
service_connection_name: azure-prod
identity:
type: app-registration-or-user-assigned-managed-identity
tenant_id: <tenant-guid>
client_id: <application-client-guid>
azure_scope: /subscriptions/<subscription-guid>/resourceGroups/rg-app-prod
error:
code: AADSTS700213
correlation_id: <correlation-guid>
trace_id: <trace-guid>
assertion_issuer: <issuer-from-error>
assertion_subject: <subject-from-error>
stop_conditions:
- service connection id is unknown
- assertion claims were not preserved
- target identity is ambiguous
- proposed workaround creates a non-expiring secret Ne cliquez pas encore sur Verify and save. Une sauvegarde peut faire évoluer les valeurs générées par Azure DevOps et effacer la comparaison avec l’état qui a échoué. Capturez d’abord l’écran de configuration, l’endpoint via l’API ou la CLI Azure DevOps et le federated credential côté Entra.
Lire l’erreur comme une étape de la chaîne
Une absence de federated identity record signifie que Microsoft Entra n’a pas trouvé de tuple compatible avec l’assertion présentée. Comparez les valeurs exactes, caractères et casse compris.
AADSTS700211oriente vers l’issuer ;AADSTS700213oriente vers le subject ;AADSTS70021reste plus général et exige de contrôler issuer, subject et audience ;AuthorizationFailedou un403ARM arrive plus tard : le token a été obtenu, mais le principal n’a pas le droit requis au scope visé.
Cette séparation évite d’ajouter un rôle à une identité qui ne s’authentifie pas, ou de recréer un credential lorsque le problème est uniquement RBAC.
Comparer les deux côtés du contrat
Exportez la service connection sans publier de secret dans les logs. Puis inventoriez les federated credentials sur l’app registration ou l’identité managée réellement référencée.
ORG_URL="https://dev.azure.com/platform-ops"
PROJECT="delivery"
ENDPOINT_ID="<endpoint-guid>"
APP_ID="<application-client-guid>"
az devops configure --defaults organization="$ORG_URL" project="$PROJECT"
az devops service-endpoint show --id "$ENDPOINT_ID" --query '{id:id,name:name,type:type,authorization:authorization.scheme,servicePrincipalId:authorization.parameters.serviceprincipalid,tenantId:authorization.parameters.tenantid}' --output yaml
az ad app federated-credential list --id "$APP_ID" --query '[].{name:name,issuer:issuer,subject:subject,audiences:audiences}' --output yaml Pour une user-assigned managed identity, utilisez l’inventaire de federated credentials de cette identité plutôt que celui d’une app registration. Vérifiez aussi que le client ID pointe encore vers le même objet. Une identité supprimée puis recréée peut conserver un nom familier tout en changeant de principal.
Le contrat doit correspondre sur trois champs : issuer, subject et audience. L’audience attendue est généralement api://AzureADTokenExchange. Ne normalisez pas un subject à la main et ne remplacez pas un identifiant par un nom plus lisible. La valeur à configurer côté Entra est celle générée par la service connection.
Identifier la génération d’issuer sans mélanger les formats
Les service connections Azure DevOps WIF peuvent encore présenter deux familles de contrat. Les anciennes utilisent l’issuer Azure DevOps https://vstoken.dev.azure.com/... et un subject lisible de type sc://organisation/projet/service-connection. Les nouvelles utilisent un issuer Microsoft Entra commençant par https://login.microsoftonline.com/... et un subject basé sur des identifiants Azure DevOps.
Il ne faut pas construire un hybride entre les deux. Copier le nouvel issuer tout en conservant l’ancien subject, ou l’inverse, produit un credential plausible mais inutilisable. Azure DevOps a annoncé le retrait de l’issuer Azure DevOps au 1er juillet 2027 pour les service connections concernées dans Azure public : une panne de mapping peut donc révéler une migration incomplète, mais elle ne justifie pas une conversion improvisée pendant un déploiement critique.
Assertion presentee par le run en echec
issuer: valeur exacte du message Entra ou de la configuration Azure DevOps
subject: valeur exacte du message Entra ou de la configuration Azure DevOps
audience: api://AzureADTokenExchange
Credential configure sur l'identite
issuer: valeur exportee depuis Entra
subject: valeur exportee depuis Entra
audiences: liste exportee depuis Entra
Verdict
exact_match: authentification a poursuivre vers RBAC
issuer_mismatch: reparer ou terminer la migration d'issuer
subject_mismatch: recreer le mapping depuis les valeurs Azure DevOps
audience_mismatch: corriger l'audience sans elargir issuer ou subject
identity_mismatch: arreter et retrouver l'objet reel Si l’organisation, le projet ou la service connection a été renommé, comparez le claim réellement émis au credential existant. Ne déduisez pas l’impact du renommage à partir du seul libellé visible : le format historique contient des noms, tandis que le nouveau contrat s’appuie davantage sur des identifiants.
Préparer une correction réversible
Séparez le contrôle de la service connection, la propriété de l’identité et le RBAC cible. Un administrateur Azure DevOps peut modifier l’endpoint sans avoir le droit d’ajouter un federated credential sur l’app registration ou la managed identity. Cette séparation de responsabilités est normale ; elle doit apparaître dans le changement.
Pour une connexion gérée automatiquement, utilisez le flux Update proposé par Azure DevOps lorsque la conversion d’issuer est disponible. Pour une connexion manuelle, copiez issuer, subject et audience depuis Azure DevOps, créez un nouveau federated credential sur la même identité, puis revenez terminer la configuration. Gardez l’ancien credential pendant la fenêtre de canari si la politique interne l’autorise ; ne supprimez l’ancien chemin qu’après preuve du nouveau.
change:
service_connection_id: <endpoint-guid>
identity_client_id: <application-client-guid>
from_credential: ado-wif-legacy
to_credential: ado-wif-entra-issuer
target_scope_unchanged: true
preconditions:
- current issuer subject and audience exported
- current Azure role assignments exported
- identity owner and service connection admin available
- canary pipeline authorized explicitly
- no client secret added
rollback:
- stop production pipeline authorization
- restore previous service connection configuration
- keep or restore previous federated credential while supported
- remove only the failed candidate credential
- verify no RBAC scope changed Ne modifiez pas simultanément issuer, identité cible et rôle Azure. Sinon, un canari réussi ne dira pas quel changement était nécessaire, et un échec ne laissera pas de rollback lisible.
Canaryer en lecture seule avant le déploiement
Créez ou utilisez un pipeline de qualification explicitement autorisé sur la service connection. Le canari doit demander un token, confirmer tenant et principal, puis lire une ressource connue dans le scope minimal. Ajoutez un contrôle négatif sur un scope non autorisé. Ne lancez pas le template de déploiement complet pour tester l’authentification.
trigger: none
steps:
- task: AzureCLI@2
displayName: Validate federated service connection
inputs:
azureSubscription: azure-prod
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
set -euo pipefail
az account show --query '{tenantId:tenantId,subscriptionId:id,user:user.name,userType:user.type}' --output yaml
az group show --name rg-app-prod --query '{id:id,name:name,location:location}' --output yaml
if az group show --name rg-forbidden-control >/dev/null 2>&1; then
echo "Negative RBAC control unexpectedly succeeded" >&2
exit 1
fi Le succès minimal prouve cinq choses : Azure DevOps émet l’assertion, Entra la relie au bon principal, le token vise le bon tenant, le scope prévu est lisible et le contrôle négatif reste refusé. Répétez le canari depuis le même type d’agent et avec la même tâche Azure DevOps que la production ; une extension ancienne peut ne pas supporter WIF même si la connexion est correcte.
Corréler la preuve côté Entra et Azure DevOps
Conservez run ID, service connection ID, client ID, correlation ID et fenêtre UTC. Les journaux de connexion du service principal peuvent confirmer le principal, la ressource et le résultat. Adaptez la table aux diagnostics réellement envoyés dans le workspace.
let Start = datetime(2026-09-29T06:30:00Z);
let End = Start + 2h;
let ClientId = "<application-client-guid>";
AADServicePrincipalSignInLogs
| where TimeGenerated between (Start .. End)
| where AppId == ClientId or ServicePrincipalId == ClientId
| project TimeGenerated,
AppDisplayName,
ServicePrincipalId,
ResourceDisplayName,
ResultType,
ResultDescription,
CorrelationId,
IPAddress
| order by TimeGenerated desc Une absence totale de sign-in cohérent avec le run renvoie vers la tâche, l’assertion ou la service connection. Un sign-in réussi suivi d’un refus ARM renvoie vers le rôle et son scope. Gardez ces deux verdicts distincts dans le ticket d’incident.
Décider, valider ou rollbacker
Réparez le federated credential lorsque le client ID est le bon et qu’un seul champ du contrat ne correspond plus à la valeur générée par Azure DevOps. Terminez la migration d’issuer lorsque la connexion est éligible, que les deux côtés ont été inventoriés et que le canari passe sans changement RBAC. Corrigez RBAC uniquement après avoir prouvé une authentification réussie et une action refusée au scope exact.
Rollbackez la conversion si l’identité devient ambiguë, si la tâche de production ne supporte pas WIF, si le contrôle négatif réussit ou si les traces ne permettent plus d’attribuer le principal. Restaurez alors la configuration WIF précédente encore valide, retirez le credential candidat et réautorisez la production seulement après un nouveau canari.
Un secret temporaire n’est acceptable que dans un processus d’urgence déjà approuvé, avec propriétaire, scope minimal, expiration courte et suppression vérifiée. Il ne doit pas devenir le rollback par défaut d’une dérive issuer/subject. Le rollback normal reste un contrat fédéré connu, observable et limité.
Conclusion
Une service connection WIF fonctionne quand deux configurations indépendantes décrivent exactement le même contrat. Le nom de la connexion, l’existence de l’identité et ses rôles ne suffisent pas : issuer, subject et audience doivent correspondre avant même que RBAC entre en jeu.
Figez l’assertion, comparez les deux côtés, migrez une seule couche à la fois et canaryez une lecture avec contrôle négatif. La décision de production devient alors défendable : valider le nouveau contrat fédéré, réparer un mapping précis ou rollbacker sans réintroduire silencieusement un secret durable.