Infrastructure

Azure App Service : diagnostiquer un token Managed Identity avant de revenir à un secret client

Un runbook de production pour qualifier une panne d'identité managée Azure App Service avec endpoint IMDS, Entra ID, RBAC, Key Vault ou API cible, logs, validation et rollback avant de réintroduire un secret client.

16 juil. 2026 azureapp-servicemanaged-identityidentityentra-idkey-vaultobservabilitykqlautomationrunbookrollbackproduction

Quand une application Azure App Service n’arrive plus à obtenir un token via Managed Identity, la tentation est de remettre un client secret dans la configuration pour rétablir le service. C’est rapide, mais cela transforme souvent un incident d’exploitation en dette de sécurité : secret à stocker, rotation à planifier, droits à élargir et diagnostic perdu.

Le cas d’usage est une API hébergée sur App Service qui appelle Key Vault, Storage, Microsoft Graph ou une API interne protégée par Entra ID. Depuis un déploiement, une rotation d’identité, une modification RBAC ou un changement de slot, l’application reçoit des 401, 403, CredentialUnavailableException, ManagedIdentityCredential authentication unavailable ou des timeouts sur l’obtention de token. Le but du runbook est de décider si l’on doit corriger l’identité, le périmètre d’autorisation, la cible appelée, le code de credential chain, ou activer un contournement temporaire borné.

Figer le contrat d’identité attendu

Commence par écrire l’identité qui devait agir. Une identité managée n’est pas seulement une case cochée dans App Service : elle définit un principal Entra ID, une ressource appelante, une audience de token, un scope RBAC et un journal d’audit côté cible.

text managed-identity-contract.txt
Application
App Service: app-orders-prod
Slot: production ou staging
Resource group: rg-prod-app
Runtime: code de production, job WebJob ou Function dans le meme plan

Identite attendue
Type: system-assigned ou user-assigned
Client ID attendu si user-assigned
Object ID / principal ID attendu
Audience du token: https://vault.azure.net, https://storage.azure.com ou api://...
Ressource cible: Key Vault, Storage, API interne, Graph ou service tiers
Droits attendus: RBAC, access policy, app role ou scope API

Questions avant retour au secret client
L'identite est-elle encore active sur le bon slot ?
Le code demande-t-il un token pour la bonne audience ?
Le principal possede-t-il les droits sur la cible exacte ?
La cible rejette-t-elle l'identite ou le token n'est-il jamais obtenu ?
Un changement de slot, role assignment, Key Vault, API ou SDK vient-il d'avoir lieu ?

Si ce contrat n’est pas clair, un secret client va masquer la panne sans expliquer quelle identité devait réellement porter l’action.

Séparer obtention de token et autorisation cible

Un 403 côté Key Vault ou API ne prouve pas que Managed Identity est cassée. Il peut indiquer que le token est bien émis, mais que la cible refuse le principal, le rôle, le scope, l’audience ou une condition réseau.

text identity-failure-classification.txt
Familles de panne
Token indisponible
  Endpoint Managed Identity inaccessible
  Identite desactivee sur App Service ou sur le slot
  Mauvais client ID pour une user-assigned identity
  Credential chain qui ignore ManagedIdentityCredential

Token emis mais cible refusee
  Role assignment manquant ou au mauvais scope
  Key Vault access policy encore utilisee
  App role non assigne sur API interne
  Audience du token incorrecte
  Propagation RBAC ou cache cote service

Panne applicative ou reseau
  App appelle une mauvaise URL
  DNS ou route vers API privee en echec
  TLS, proxy ou firewall casse le chemin
  Le code recycle un token expire ou un cache incorrect

Blocage
Revenir au secret client avant de savoir dans quelle famille se trouve la panne

Cette séparation évite deux erreurs : élargir les droits alors que le token n’est jamais demandé, ou réintroduire un secret alors que seul un rôle cible manque.

Capturer l’identité exposée par App Service

Vérifie d’abord l’état réel de l’application et du slot. Les erreurs arrivent souvent après un swap, un changement de user-assigned identity ou un script IaC qui a recréé l’identité sans remettre les rôles.

bash 01-app-service-identity-state.sh
SUBSCRIPTION="00000000-0000-0000-0000-000000000000"
RG="rg-prod-app"
APP="app-orders-prod"
SLOT="production"

az account set --subscription "$SUBSCRIPTION"

az webapp identity show --resource-group "$RG" --name "$APP" --query "{type:type,principalId:principalId,tenantId:tenantId,userAssignedIdentities:userAssignedIdentities}" --output json

az webapp config appsettings list --resource-group "$RG" --name "$APP" --query "[?contains(name, 'IDENTITY') || contains(name, 'AZURE_CLIENT_ID') || contains(name, 'KEYVAULT') || contains(name, 'AUTH')].[name,value]" --output table

Si l’application utilise une user-assigned identity, vérifie que AZURE_CLIENT_ID pointe vers le bon client ID. Après un swap, les app settings marqués slot setting et les identités attachées peuvent diverger.

Tester le token depuis le même runtime

Un test depuis le poste d’un administrateur prouve seulement les droits de l’administrateur. Le test utile part du même App Service, avec les mêmes variables, le même slot et le même code de credential chain.

bash 02-runtime-token-probe.sh
# A executer dans une console App Service, un endpoint de diagnostic controle
# ou une revision temporaire qui utilise le meme slot et la meme identite.

TOKEN_ENDPOINT="$IDENTITY_ENDPOINT"
TOKEN_HEADER="$IDENTITY_HEADER"
RESOURCE="https://vault.azure.net"

curl -sS -H "X-IDENTITY-HEADER: $TOKEN_HEADER" "$TOKEN_ENDPOINT?api-version=2019-08-01&resource=$RESOURCE" | jq '{expires_on, resource, client_id, access_token_present: (.access_token != null)}'

Le résultat attendu n’est pas d’afficher le token complet. Il faut prouver l’audience, le client ID et la disponibilité du endpoint. Si ce test échoue, corrige l’identité App Service avant de toucher aux droits cible.

Lire les refus côté Entra, Key Vault ou API

Quand le token est émis, la preuve doit venir de la cible. Pour Key Vault, Storage, API Management, une API interne ou Microsoft Graph, cherche le principal, l’audience et le motif du refus dans les logs disponibles.

kusto 03-target-denials.kql
let StartTime = datetime(2026-07-16T08:00:00Z);
let EndTime = datetime(2026-07-16T09:00:00Z);
let AppPrincipalId = "00000000-0000-0000-0000-000000000000";
AzureDiagnostics
| where TimeGenerated between (StartTime .. EndTime)
| where ResourceProvider has_any ("MICROSOFT.KEYVAULT", "MICROSOFT.APIMANAGEMENT", "MICROSOFT.STORAGE")
| where ResultType has_any ("401", "403", "Forbidden", "Unauthorized")
 or OperationName has_any ("SecretGet", "SecretList", "GetBlob", "GatewayLogs")
| extend Raw = tostring(Properties)
| where Raw has AppPrincipalId or Identity has AppPrincipalId or CallerIPAddress != ""
| project TimeGenerated, ResourceProvider, Resource, OperationName, ResultType, ResultSignature, Identity, CallerIPAddress, Raw
| order by TimeGenerated desc

Adapte la requête aux tables réellement activées. L’important est de relier le refus au principal App Service, pas seulement de constater un 403 côté application.

Vérifier les rôles au bon scope

Les rôles manquent rarement “partout”. Ils manquent au bon scope : vault précis, secret précis, storage account précis, API app registration, resource group ou subscription. Vérifie aussi les suppressions récentes.

bash 04-role-scope-check.sh
PRINCIPAL_ID="00000000-0000-0000-0000-000000000000"
TARGET_SCOPE="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-prod-sec/providers/Microsoft.KeyVault/vaults/kv-prod"

az role assignment list --assignee "$PRINCIPAL_ID" --all --query "[].{role:roleDefinitionName,scope:scope,principalType:principalType,createdOn:createdOn}" --output table

az role assignment list --assignee "$PRINCIPAL_ID" --scope "$TARGET_SCOPE" --include-inherited --query "[].{role:roleDefinitionName,scope:scope}" --output table

Si le service cible utilise encore des access policies Key Vault ou des app roles Entra, la commande RBAC ne suffit pas. Le diagnostic doit suivre le modèle d’autorisation réellement activé.

Contrôler la credential chain applicative

Beaucoup d’incidents viennent d’une credential chain différente entre local, staging et production. DefaultAzureCredential peut essayer plusieurs sources. En production, l’application doit échouer clairement si l’identité attendue n’est pas disponible, au lieu de basculer silencieusement vers une autre source.

text credential-chain-controls.txt
A controler dans le code et la configuration
ManagedIdentityCredential utilise explicitement le client ID attendu si user-assigned
Pas de client secret residuel dans les app settings production
Les logs indiquent quel credential a ete retenu
Les erreurs de token ne sont pas masquees par un retry applicatif infini
Le cache de token est invalide apres changement d'identite ou de slot
Les tests de demarrage appellent une dependance representative avec l'identite runtime

Signaux de derive
AZURE_CLIENT_SECRET reapparait dans la configuration
AZURE_CLIENT_ID pointe vers une identite de staging
Le slot production et le slot staging n'ont pas les memes identites
Les logs ne distinguent pas token acquisition et appel cible

Le retour à un secret client devient souvent permanent parce qu’il est invisible. La première correction durable est de rendre la credential chain observable.

Décider correction, contournement ou rollback

La décision doit dire quelle preuve manque encore et quelle surface de risque est acceptée. Le secret client n’est acceptable que comme contournement borné, avec échéance et suppression planifiée.

yaml managed-identity-decision.yml
decision:
fix_app_service_identity:
  when:
    - managed_identity_endpoint_fails_from_runtime
    - identity_disabled_on_app_or_slot
    - wrong_user_assigned_client_id
  validation:
    - token_probe_returns_expected_client_id
    - app_restart_or_slot_swap_keeps_identity

fix_target_authorization:
  when:
    - token_is_issued_for_expected_audience
    - target_logs_show_401_or_403_for_app_principal
    - role_assignment_or_app_role_is_missing
  validation:
    - target_accepts_runtime_identity
    - audit_log_contains_app_principal

rollback_recent_change:
  when:
    - identity_or_role_changed_in_recent_deployment
    - slot_swap_changed_effective_identity
    - no_safe_equivalent_signal_exists
  validation:
    - previous_identity_path_works
    - removed_change_is_documented

temporary_client_secret:
  allowed_only_if:
    - user_impact_is_active
    - managed_identity_fix_needs_external_delay
    - secret_is_scoped_to_same_or_narrower_permissions
    - expiry_owner_and_removal_change_are_created
  blocked_if:
    - root_cause_is_unknown
    - secret_would_need_broader_permissions
    - audit_would_no_longer_identify_the_application

Un contournement secret sans date de retrait est une régression d’architecture. Il doit être traité comme une exception de production, pas comme une nouvelle norme.

Valider avant fermeture

La fermeture de l’incident doit prouver trois choses : le runtime obtient le token attendu, la cible accepte ce principal, et l’application fonctionne sans secret client résiduel.

text identity-validation-checklist.txt
Validation finale
Token obtenu depuis le slot production avec le client ID attendu
Audience du token conforme a la cible
Role, access policy ou app role confirme au bon scope
Log cible avec principal App Service et operation reussie
App settings production sans client secret temporaire
Test applicatif representatif passe apres restart ou swap
Alerte ou requete KQL ajoutee pour detecter le retour d'un secret client

Rollback pret
Identite precedente documentee
Role assignment precedent connu
Secret temporaire expire ou supprime
Changement IaC aligne avec l'etat corrige

Si le correctif a été fait à la main dans le portail, il reste incomplet tant que l’IaC, le runbook et la surveillance ne reflètent pas l’état attendu.

Conclusion

Une panne Managed Identity ne justifie pas automatiquement le retour à un client secret. Elle demande d’abord de localiser la rupture : identité exposée par App Service, audience du token, credential chain, autorisation cible, logs d’audit ou changement de slot.

Le bon runbook rétablit l’identité managée quand c’est possible, corrige le scope d’autorisation quand le token est valide, rollbacke le changement récent quand la production est exposée, et n’autorise un secret client que comme contournement temporaire, borné et surveillé. La décision finale doit laisser une application exploitable sans perdre le contrôle de son identité.