Infrastructure
Azure Container Apps : diagnostiquer une identité managée avant d’élargir un rôle
Un runbook de production pour qualifier des 401/403 Azure Container Apps avec identité managée, token, scope RBAC, Key Vault, ACR, logs, validation et rollback avant d’élargir les permissions.
Quand une révision Azure Container Apps commence à recevoir des 401, des 403, des erreurs Key Vault, un refus ACR ou un appel API rejeté, la correction la plus rapide semble souvent être d’ajouter un rôle plus large à l’identité managée. C’est rarement le bon premier geste. Un rôle élargi peut faire repartir le service tout en masquant une dérive de révision, un mauvais client ID, un token demandé pour la mauvaise audience, un scope RBAC trop haut ou une configuration qui n’est plus alignée avec l’IaC.
Le cas d’usage est une Container App orders-worker-prod qui lit un secret Key Vault, tire une image depuis ACR et appelle une API interne protégée par Entra ID. Après un déploiement, une nouvelle révision démarre mais échoue sur ses dépendances. L’objectif du runbook est de décider s’il faut corriger l’identité attachée, le scope cible, le code de credential, la configuration de révision, ou rollbacker la révision avant d’élargir les permissions.
Figer le contrat d’identité
Commence par écrire quelle identité devait agir, sur quelle cible, avec quelle permission. Container Apps peut utiliser une identité système ou une identité assignée par l’utilisateur, et la révision active n’est pas toujours celle que l’équipe regarde dans le portail.
container_app:
name: orders-worker-prod
environment: cae-platform-prod
resource_group: rg-platform-prod
active_revision: orders-worker-prod--20260725-1
expected_identity:
type: user_assigned
client_id: 00000000-0000-0000-0000-000000000000
principal_id: 11111111-1111-1111-1111-111111111111
token_audiences:
- https://vault.azure.net
- https://management.azure.com
- api://internal-operations
expected_targets:
key_vault: read selected secrets only
acr: AcrPull on the registry
internal_api: app role operations.read
decision_needed:
fix_identity_or_scope
rollback_revision
temporary_exception_with_expiry Sans ce contrat, l’équipe risque de traiter tous les 403 comme un manque de droit. Or un 403 peut aussi prouver que le mauvais principal agit correctement, mais sur la mauvaise cible.
Séparer token, autorisation et chemin applicatif
Le diagnostic doit classer l’échec avant toute modification RBAC.
Token non obtenu
L'identité n'est pas attachée à la Container App
AZURE_CLIENT_ID pointe vers une identité absente ou de staging
Le code n'utilise pas ManagedIdentityCredential
Le runtime ne reçoit pas les variables d'identité attendues
Token obtenu mais rejeté par la cible
Le rôle est absent au scope exact
L'app role ou la permission API n'est pas assignée
L'audience du token ne correspond pas à la ressource
La propagation RBAC ou le cache service n'est pas terminé
Chemin ou configuration applicative
La révision appelle une URL de staging
Le secret ou le registry name a changé
ACR, Key Vault ou l'API reçoit un autre caller
Les logs applicatifs masquent la différence entre token et appel cible Cette séparation évite d’ajouter Contributor sur un groupe de ressources alors que l’application demande simplement un token pour la mauvaise audience.
Lire l’identité attachée à la Container App
Vérifie d’abord l’état réel de la Container App, pas seulement la configuration prévue dans le ticket.
SUBSCRIPTION="00000000-0000-0000-0000-000000000000"
RG="rg-platform-prod"
APP="orders-worker-prod"
az account set --subscription "$SUBSCRIPTION"
az containerapp identity show --resource-group "$RG" --name "$APP" --query "{type:type,principalId:principalId,tenantId:tenantId,userAssignedIdentities:userAssignedIdentities}" --output json
az containerapp revision list --resource-group "$RG" --name "$APP" --query "[].{name:name,active:properties.active,trafficWeight:properties.trafficWeight,createdTime:properties.createdTime}" --output table
az containerapp show --resource-group "$RG" --name "$APP" --query "properties.template.containers[].env[]" --output table Regarde surtout AZURE_CLIENT_ID, les variables de cible, les secrets référencés et les révisions actives. Une révision avec 10 % de trafic peut suffire à produire un incident intermittent.
Tester le token depuis la révision
Un test depuis un poste administrateur ne prouve pas le runtime. Le test utile part de la révision ou d’un conteneur de diagnostic équivalent, avec les mêmes variables.
# A exécuter depuis un shell de diagnostic contrôlé dans la même app
# ou depuis une révision temporaire qui porte la même identité.
RESOURCE="https://vault.azure.net"
curl -sS -H "Metadata: true" "$IDENTITY_ENDPOINT?api-version=2019-08-01&resource=$RESOURCE&client_id=$AZURE_CLIENT_ID" | jq '{client_id, resource, expires_on, token_present: (.access_token != null)}' Le but n’est pas d’exposer le token. Le but est de prouver le client ID, l’audience et la disponibilité de l’endpoint d’identité. Si cette étape échoue, élargir un rôle cible ne corrigera rien.
Corréler les refus côté cible
Si le token existe, passe aux logs de la cible. Key Vault, ACR, API Management, l’API interne ou Storage doivent montrer le principal, l’opération et le résultat.
let StartTime = datetime(2026-07-25T08:00:00Z);
let EndTime = datetime(2026-07-25T10:00:00Z);
let PrincipalId = "11111111-1111-1111-1111-111111111111";
AzureDiagnostics
| where TimeGenerated between (StartTime .. EndTime)
| where ResultType has_any ("401", "403", "Forbidden", "Unauthorized")
or OperationName has_any ("SecretGet", "RepositoryPull", "GatewayLogs")
| extend Raw = tostring(Properties)
| where Raw has PrincipalId or Identity has PrincipalId or CallerIdentity has PrincipalId
| project TimeGenerated, ResourceProvider, Resource, OperationName, ResultType, Identity, CallerIPAddress, Raw
| order by TimeGenerated desc Adapte les tables à la télémétrie réellement activée. La preuve importante est la même : la cible a-t-elle rejeté l’identité attendue, une autre identité, ou aucun appel n’arrive-t-il jusqu’à elle ?
Vérifier le scope minimal
Quand le refus est confirmé côté cible, vérifie le scope exact. Une identité peut avoir un rôle au mauvais registre ACR, au vault de staging, ou sur le groupe de ressources alors que la cible utilise une app role Entra.
PRINCIPAL_ID="11111111-1111-1111-1111-111111111111"
TARGET_SCOPE="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sec-prod/providers/Microsoft.KeyVault/vaults/kv-orders-prod"
az role assignment list --assignee "$PRINCIPAL_ID" --all --query "[].{role:roleDefinitionName,scope:scope,condition:condition}" --output table
az role assignment list --assignee "$PRINCIPAL_ID" --scope "$TARGET_SCOPE" --include-inherited --query "[].{role:roleDefinitionName,scope:scope}" --output table Le correctif minimal est souvent un rôle précis au bon scope, une app role manquante, ou un retour à l’identité précédente. Le correctif maximal est rarement nécessaire.
Décider correction, rollback ou exception
La décision doit rester exploitable par l’astreinte et relisible après incident.
fix_container_app_identity:
when:
- identity_not_attached_to_app
- wrong_user_assigned_client_id
- active_revision_uses_staging_configuration
validation:
- runtime_token_probe_returns_expected_client_id
- active_revision_reaches_dependency
fix_target_authorization:
when:
- token_is_issued_for_expected_audience
- target_logs_show_denial_for_expected_principal
- minimal_role_or_app_role_is_missing
validation:
- target_log_shows_success_for_same_principal
- permission_scope_matches_iac
rollback_revision:
when:
- failure_started_with_new_revision
- previous_revision_has_expected_identity_contract
- identity_fix_cannot_be_validated_quickly
validation:
- traffic_returns_to_previous_revision
- dependency_calls_succeed
temporary_exception:
allowed_only_if:
- user_impact_is_active
- scope_is_same_or_narrower_than_expected
- expiry_and_owner_are_recorded
blocked_if:
- root_cause_is_unknown
- broader_role_would_hide_wrong_identity Une exception RBAC sans date d’expiration devient une dette de sécurité. Elle doit être traitée comme une mesure temporaire, pas comme une correction d’architecture.
Valider avant fermeture
La fermeture doit prouver le runtime, la cible et le rollback.
Validation finale
La révision active porte l'identité attendue
Le token est obtenu depuis le runtime pour la bonne audience
La cible accepte le principal attendu au scope minimal
Les logs montrent un succès avec le même principal
Les variables de staging ou secrets temporaires ont été retirés
L'IaC reflète l'état corrigé
Un rollback vers la révision précédente reste possible jusqu'à stabilisation Si la correction a été appliquée au portail, elle reste incomplète tant que l’IaC, les alertes et le runbook ne décrivent pas le nouvel état.
Conclusion
Un 401 ou 403 dans Azure Container Apps ne doit pas déclencher automatiquement un élargissement de rôle. La bonne séquence consiste à prouver l’identité active, tester le token depuis la révision, lire les refus côté cible, vérifier le scope minimal, puis décider entre correction, rollback ou exception bornée.
La décision finale doit laisser une plateforme plus explicable qu’avant l’incident : une identité connue, un scope justifié, des logs corrélables et un chemin de rollback prêt si la nouvelle révision reste instable.