Cloud
Azure Resource Locks : diagnostiquer un déploiement bloqué avant de retirer le verrou
Un runbook de production pour séparer verrous Azure hérités, RBAC, Policy et deny assignments, puis valider un déverrouillage borné, le déploiement et son rollback.
Un déploiement de production met à jour plusieurs ressources, puis échoue au remplacement d’un diagnostic setting. Le pipeline remonte ScopeLocked. Un opérateur trouve un verrou CanNotDelete sur le resource group et propose de le retirer, de relancer tout le déploiement, puis de le recréer.
Cette séquence peut rétablir la release. Elle peut aussi exposer toutes les ressources du groupe à la suppression, rejouer des opérations déjà réussies ou recréer un verrou sur un scope différent sans conserver la preuve de ce qui s’est passé pendant son absence. Le cas fil rouge est un déploiement Bicep ou Terraform qui doit remplacer une seule ressource d’extension dans un resource group protégé. Le runbook doit aboutir à une décision contrôlée : éviter le delete, ouvrir une fenêtre de déverrouillage bornée, rendre l’opération au propriétaire du control plane ou rollbacker un déploiement partiel.
Figer l’opération en échec, pas seulement le job
Conservez le run du pipeline, le commit, le nom du déploiement, la fenêtre UTC, l’object ID de l’appelant, le correlation ID et l’ID complet de la ressource cible. Un job rouge n’indique ni l’action control plane refusée ni les actions précédentes qui ont réussi.
RG="rg-platform-prod"
DEPLOYMENT="platform-20260930-01"
START_UTC="2026-09-30T05:45:00Z"
END_UTC="2026-09-30T06:15:00Z"
az deployment operation group list --resource-group "$RG" --name "$DEPLOYMENT" --query "[].{state:properties.provisioningState,resource:properties.targetResource.resourceName,type:properties.targetResource.resourceType,status:properties.statusMessage}" --output json > deployment-operations.json
az monitor activity-log list --resource-group "$RG" --start-time "$START_UTC" --end-time "$END_UTC" --query "[].{time:eventTimestamp,status:status.value,operation:operationName.value,resource:resourceId,caller:caller,correlationId:correlationId}" --output json > activity-log.json Pour Terraform, conservez le plan, le serial du state, le lock file des providers et la sortie complète de l’apply. Isolez les écritures réussies de l’action en échec. Ne relancez rien tant que l’état Azure et celui du moteur de déploiement peuvent diverger.
La formulation utile de l’incident est précise : « le principal du pipeline a tenté un DELETE sur ce diagnostic setting à 06:02 UTC ; Azure a retourné ScopeLocked avec ce correlation ID après la réussite de deux mises à jour sans rapport ».
Prouver quel contrôle a bloqué la requête
Plusieurs contrôles peuvent refuser une écriture Azure. Leur correction n’est pas la même :
ScopeLockedou un message explicite de management lock pointe versMicrosoft.Authorization/locks;AuthorizationFailedimpose de vérifier d’abord l’appelant, l’action et le scope RBAC ;RequestDisallowedByPolicydoit être relié à une affectation Policy et à son effet ;- une deny assignment peut neutraliser un rôle pourtant valide, notamment lorsqu’elle vient d’une managed application ou d’une deployment stack.
Inspectez l’erreur brute avant de modifier les droits. Ajouter Owner ne contourne pas un management lock ; supprimer un verrou ne corrige pas un refus RBAC ou Policy.
CALLER_OBJECT_ID="<pipeline-principal-object-id>"
TARGET_ID="/subscriptions/<sub>/resourceGroups/rg-platform-prod/providers/Microsoft.Storage/storageAccounts/stplatformprod/providers/Microsoft.Insights/diagnosticSettings/send-platform-logs"
az ad sp show --id "$CALLER_OBJECT_ID" --query "{id:id,appId:appId,displayName:displayName}" --output json
az role assignment list --assignee-object-id "$CALLER_OBJECT_ID" --all --include-inherited --query "[].{role:roleDefinitionName,scope:scope,condition:condition}" --output json > caller-rbac.json
az lock list --output json > subscription-locks.json
az lock list --resource-group "rg-platform-prod" --output json > resource-group-locks.json Si la cible est une ressource d’extension comme un diagnostic setting, elle hérite du verrou de la ressource à laquelle elle est attachée. Un lock peut donc bloquer le delete sans apparaître directement sur la ressource d’extension.
Reconstruire la chaîne de verrous effective
Les management locks Azure existent au scope abonnement, resource group ou ressource. Les enfants héritent des verrous parents et le verrou le plus restrictif de la chaîne l’emporte. CanNotDelete autorise les mises à jour mais bloque la suppression. ReadOnly bloque aussi les mises à jour du control plane.
Lisez chaque verrou candidat comme un tuple :
lock_id
lock_name
level: CanNotDelete | ReadOnly
declared_scope
inherited_by_target: true | false
owner_or_change_record
notes
creation_or_last-change evidence
required operation: PUT | PATCH | POST | DELETE Le dernier champ est déterminant. Un déploiement qui remplace une ressource enfant peut contenir un delete même si la configuration désirée ressemble à une mise à jour. À l’inverse, un verrou CanNotDelete n’explique pas l’échec d’un PUT in-place. Ne déduisez pas l’opération du seul diff de template : contrôlez l’opération du déploiement ou l’Activity Log.
Les locks agissent sur les appels du control plane Azure, pas sur les opérations du data plane du service. Retirer un verrou de ressource ne répare donc pas une opération sur un blob, une ligne SQL ou un message de queue. Gardez le diagnostic sur le plan qui a réellement échoué.
Décider si la suppression est réellement nécessaire
Regénérez un plan ou un what-if depuis les entrées figées et identifiez l’action destructive exacte.
RG="rg-platform-prod"
TEMPLATE="main.bicep"
PARAMETERS="main.prod.bicepparam"
az deployment group what-if --resource-group "$RG" --template-file "$TEMPLATE" --parameters "$PARAMETERS" --result-format FullResourcePayloads --output json > what-if.json
jq -r '
.changes[]
| select(.changeType == "Delete" or .changeType == "Create" or .changeType == "Modify")
| [.changeType, .resourceId]
| @tsv
' what-if.json Classez le changement avant de toucher au verrou :
- si une mise à jour in-place stable est supportée, corrigez le déploiement plutôt que d’ouvrir le scope ;
- si le remplacement vient d’un renommage IaC, d’une régression de provider ou d’une dérive de propriété, réparez d’abord ce contrat ;
- si le delete est intentionnel, identifiez toutes les dépendances et le plus petit scope dont le lock doit changer ;
- si le lock appartient à une managed application ou à une autre équipe plateforme, arrêtez-vous : le cycle de vie du service ou son propriétaire doit porter l’opération.
Un apply Terraform ciblé n’est pas une sortie de secours générique. Il peut isoler une réparation exceptionnelle, mais un plan complet doit suivre pour prouver que le reste du graphe converge toujours.
Construire un enregistrement de déverrouillage borné
Avant le retrait, exportez le verrou et rendez la fenêtre explicite. L’enregistrement doit nommer l’ID exact du lock, l’opération cible, l’approbateur, l’opérateur, le début, l’expiration, les déploiements concurrents à suspendre, le propriétaire de la validation et la commande de recréation.
Dans le cas fil rouge, le verrou est volontairement au scope resource group. Son retrait touche plus que le diagnostic setting : gelez les autres identités de déploiement pendant la fenêtre et surveillez toutes les écritures sur le groupe.
RG="rg-platform-prod"
LOCK_NAME="protect-platform-prod"
LOCK_ID="$(az lock show --name "$LOCK_NAME" --resource-group "$RG" --query id -o tsv)"
az lock show --ids "$LOCK_ID" --output json > lock-before.json
jq -e '
.level == "CanNotDelete"
and .name == "protect-platform-prod"
' lock-before.json >/dev/null
az lock delete --ids "$LOCK_ID" Ne supprimez jamais tous les locks renvoyés par une commande de liste. N’élargissez pas non plus l’identité du pipeline pour lui permettre de gérer durablement les verrous. L’identité de déverrouillage et celle du déploiement doivent rester séparables, avec des actions visibles dans l’Activity Log.
Un trap de sortie peut réduire l’erreur humaine dans un wrapper d’automatisation, mais il ne constitue pas le seul mécanisme de reprise : un runner tué ou un credential perdu peut laisser le scope ouvert. Utilisez un timer externe ou un change record surveillé qui alerte avant l’expiration de la fenêtre approuvée.
Exécuter un seul changement contrôlé
Lancez uniquement l’artefact approuvé et gardez stable la liste des ressources. Refusez le run si un nouveau plan introduit un autre delete, si un appelant différent est actif ou si la fenêtre restante devient insuffisante.
Observez les événements control plane pendant l’exécution. Pour la ressource cible, exigez l’opération, l’appelant, le correlation ID et le provisioning state attendus. Au niveau du resource group, signalez toute écriture qui n’appartient pas au correlation ID approuvé.
Validez ensuite le résultat du service, pas seulement Succeeded : les données de diagnostic arrivent de nouveau dans la destination prévue, leur schéma et leur latence restent acceptables, et aucun setting ou rôle sans rapport n’a changé.
Recréer le lock et prouver la convergence
Réappliquez le verrou immédiatement après l’opération approuvée, avant le nettoyage ou tout travail optionnel.
RG="rg-platform-prod"
LOCK_NAME="protect-platform-prod"
az lock create --name "$LOCK_NAME" --resource-group "$RG" --lock-type CanNotDelete --notes "Production deletion guard; removal requires approved bounded change"
az lock show --name "$LOCK_NAME" --resource-group "$RG" --query "{id:id,level:level,notes:notes}" --output json > lock-after.json
az deployment group what-if --resource-group "$RG" --template-file main.bicep --parameters main.prod.bicepparam --output json > what-if-after.json Comparez lock-before.json et lock-after.json sur le scope, le niveau, le nom et les notes opérationnelles. Un verrou recréé un niveau plus bas n’est pas équivalent. Relancez ensuite le plan IaC ou le what-if complet et expliquez chaque changement restant.
Ne testez pas la protection en supprimant une ressource de production. Validez le refus sur un canari jetable avec le même modèle de verrou avant d’adopter la procédure. En production, prouvez l’existence du lock, la convergence de l’IaC, la santé applicative et l’impossibilité pour le principal de déploiement de retirer le verrou.
Décider, valider ou rollbacker
Conservez le lock et redessinez le déploiement lorsque le delete est accidentel, qu’une mise à jour in-place existe ou que le scope de remplacement est trop large.
Approuvez un déverrouillage borné seulement si le delete exact est intentionnel, le verrou effectif et son propriétaire sont prouvés, les writers concurrents sont suspendus, l’état précédent est récupérable, la surveillance est active et la recréation du lock a un propriétaire testé.
Arrêtez et escaladez si le lock est hérité de l’abonnement, détenu par une managed application, associé à une deny assignment inexpliquée ou impossible à recréer avant la fin de la fenêtre.
Si le déploiement a partiellement appliqué, recréez d’abord le verrou sauf si cela empêche la compensation documentée. Restaurez la dernière configuration connue, réconciliez le state IaC, validez la santé du service, puis produisez un nouveau plan complet. Un scope déverrouillé n’autorise jamais à relancer un déploiement partiellement compris.
Conclusion
Un management lock est une frontière de sécurité du control plane, pas une case gênante. L’incident n’est résolu que lorsque l’opération bloquée est identifiée, la chaîne de verrous effective prouvée, le changement réduit à un scope intentionnel et la protection restaurée avec des preuves.
La décision de production devient alors défendable : éviter le delete, ouvrir une seule fenêtre surveillée, rendre l’opération au service propriétaire ou rollbacker la release partielle sans affaiblir durablement la plateforme.