Infrastructure

Azure Key Vault : migrer des access policies vers Azure RBAC sans couper les workloads

Un runbook de production pour inventorier les accès Key Vault, préparer les rôles data plane, basculer vers Azure RBAC avec un canari, valider les workloads et rollbacker sans élargir les droits.

19 sept. 2026 azurekey-vaultrbacidentitysecurityobservabilityautomationrunbookrollbackproduction

Un coffre Azure Key Vault fonctionne depuis des années avec des access policies. Une équipe sécurité demande de passer à Azure RBAC pour mieux séparer administration du coffre et accès aux secrets. Les role assignments sont créés, le modèle d’autorisation est basculé, puis plusieurs applications retournent des 403. La réaction la plus rapide serait d’ajouter Key Vault Administrator aux identités en échec. Elle rétablit parfois le service, mais elle masque les consommateurs oubliés et élargit durablement le data plane.

Le cas fil rouge est un coffre partagé par une Function App qui lit des secrets, une pipeline qui renouvelle un certificat et une équipe d’astreinte qui consulte les métadonnées. Ce runbook prépare la migration, bascule dans une fenêtre bornée, observe les accès réels et produit une décision : valider Azure RBAC, corriger un rôle précis ou revenir temporairement aux access policies.

Figer le contrat avant la migration

Commencez par capturer l’état du coffre, pas seulement la liste visible dans le portail. Il faut conserver le modèle d’autorisation, les access policies, les paramètres réseau, les diagnostic settings, les role assignments directs et hérités, ainsi que les identités attendues. Le réseau ne change pas pendant cette opération : un 403 après la bascule ne doit pas conduire à ouvrir le firewall ou l’accès public.

bash 01-snapshot-key-vault-access.sh
set -euo pipefail

RG="rg-platform-prod"
VAULT="kv-orders-prod"
VAULT_ID=$(az keyvault show -g "$RG" -n "$VAULT" --query id -o tsv)

az keyvault show -g "$RG" -n "$VAULT" --query '{id:id,rbac:properties.enableRbacAuthorization,tenant:properties.tenantId,accessPolicies:properties.accessPolicies,networkAcls:properties.networkAcls}' -o json > key-vault-before.json

az role assignment list --scope "$VAULT_ID" --include-inherited --all -o json > role-assignments-before.json

az monitor diagnostic-settings list --resource "$VAULT_ID" -o json > diagnostic-settings-before.json

Associez le snapshot à un identifiant de changement et à une heure UTC. Vérifiez aussi que l’opérateur dispose séparément de Microsoft.Authorization/roleAssignments/write et Microsoft.KeyVault/vaults/write. Un droit de modifier le coffre ne prouve pas le droit de créer les role assignments nécessaires.

Inventorier les consommateurs réels

Une access policy décrit un principal et des permissions, mais pas le service qui l’utilise ni sa criticité. Résolvez chaque objectId vers un utilisateur, un groupe, un service principal ou une identité managée. Recherchez aussi les identités absentes des policies qui héritent déjà de droits RBAC depuis le resource group ou la subscription.

yaml key-vault-consumers.yml
vault: kv-orders-prod
consumers:
- name: orders-function-prod
  principal_id: 11111111-1111-1111-1111-111111111111
  principal_type: managed_identity
  operations: [secrets/get]
  probe: read-secret-version
  criticality: critical
- name: certificate-renewal-pipeline
  principal_id: 22222222-2222-2222-2222-222222222222
  principal_type: service_principal
  operations: [certificates/get, certificates/import]
  probe: import-test-certificate
  criticality: scheduled
- name: platform-oncall
  principal_id: 33333333-3333-3333-3333-333333333333
  principal_type: group
  operations: [secrets/list-metadata]
  probe: list-secret-metadata
  criticality: interactive

unknown_policies: []
unowned_principals: []
validation_owner: platform-team

Ne migrez pas un principal inconnu en lui attribuant un rôle large « par prudence ». Marquez-le comme bloquant, retrouvez son propriétaire et observez les logs historiques. Une policy inutilisée peut être retirée plus tard, dans un changement distinct.

Traduire des opérations en rôles data plane

Ne faites pas une correspondance par intitulé. Partez des opérations réellement requises. Une application qui lit la valeur d’un secret relève généralement de Key Vault Secrets User; une pipeline qui gère des secrets peut nécessiter Key Vault Secrets Officer; les clés et certificats ont leurs propres rôles. Key Vault Reader lit les métadonnées, pas la valeur des secrets. Key Vault Contributor administre le coffre au control plane mais n’accorde pas automatiquement l’accès aux données.

Les rôles intégrés ne reproduisent pas toutes les combinaisons fines des access policies. Si aucun rôle ne couvre le besoin sans excès, créez un rôle custom revu et versionné. Gardez le scope au niveau du coffre par défaut. Un scope au secret peut être pertinent, mais il augmente le nombre d’objets à exploiter et ne doit pas servir à compenser un coffre qui mélange trop d’applications.

bash 02-stage-key-vault-rbac.sh
VAULT_ID=$(az keyvault show -g "$RG" -n "$VAULT" --query id -o tsv)

az role assignment create --assignee-object-id "11111111-1111-1111-1111-111111111111" --assignee-principal-type ServicePrincipal --role "Key Vault Secrets User" --scope "$VAULT_ID"

az role assignment create --assignee-object-id "22222222-2222-2222-2222-222222222222" --assignee-principal-type ServicePrincipal --role "Key Vault Certificates Officer" --scope "$VAULT_ID"

Créez les assignments avant la bascule et attendez leur propagation. Ils n’autorisent pas encore le data plane tant que le coffre utilise les access policies, mais ils doivent être présents, résolus vers les bons principals et relisibles avant la fenêtre de changement.

Construire une matrice de validation

La bascule concerne tout le coffre. Il n’existe pas de canari par identité sur le même coffre : dès que enableRbacAuthorization passe à true, les workloads dépendent du modèle RBAC. Testez d’abord la correspondance sur un coffre non productif représentatif, puis préparez une fenêtre courte avec des probes exécutées par les identités réelles.

text rbac-cutover-matrix.txt
Principal                    Test positif                 Test negatif
orders-function-prod         lire secret canari           ecrire une nouvelle version
certificate-renewal-pipeline importer certificat test      lire un secret applicatif
platform-oncall              lister les metadonnees         lire la valeur d'un secret

Preuves exigees
- resultat fonctionnel depuis le chemin reseau habituel
- objectId attendu dans la trace
- operation et scope attendus
- aucun role administrateur ajoute en urgence
- test negatif refuse
- commande de rollback relue et operateur disponible

Un test avec l’identité personnelle de l’opérateur ne valide pas une identité managée. Exécutez chaque probe depuis son runtime normal : Function App, runner de pipeline ou poste d’administration contrôlé.

Basculer avec des conditions d’arrêt

Annoncez l’heure exacte, suspendez les déploiements qui modifient identités ou secrets, puis vérifiez une dernière fois le snapshot et les role assignments. La commande de bascule est simple; la discipline autour de la commande fait la sécurité du changement.

bash 03-enable-rbac-and-probe.sh
az keyvault update --resource-group "$RG" --name "$VAULT" --enable-rbac-authorization true

az keyvault show -g "$RG" -n "$VAULT" --query properties.enableRbacAuthorization -o tsv

# Lancer ensuite les probes avec chaque identite reelle.
# Ne pas utiliser le compte de l'operateur comme preuve applicative.

Arrêtez la migration si un workload critique échoue, si l’identité observée diffère de l’inventaire, si les logs sont absents ou si la correction proposée exige un rôle plus large que le contrat. Un délai de propagation peut justifier une courte attente avec retries bornés; il ne justifie pas une succession d’assignments improvisés.

Lire les refus sans confondre identité et réseau

Key Vault peut envoyer la catégorie AuditEvent vers Log Analytics. Selon le mode de destination, les événements se trouvent dans une table resource-specific ou dans AzureDiagnostics. Adaptez les colonnes au schéma réellement activé et cherchez l’opération, le code, le principal, l’adresse appelante et la corrélation temporelle.

kusto 04-key-vault-rbac-cutover.kql
let Cutover = datetime(2026-09-19T16:15:00Z);
union isfuzzy=true AZKVAuditLogs, AzureDiagnostics
| where TimeGenerated between (Cutover - 15m .. Cutover + 45m)
| extend Vault = tostring(column_ifexists("Resource", Resource)),
       Operation = tostring(column_ifexists("OperationName", OperationName)),
       Status = tostring(column_ifexists("HttpStatusCode", ResultSignature)),
       Principal = tostring(column_ifexists("Identity", identity_claim_oid_g)),
       CallerIp = tostring(column_ifexists("CallerIpAddress", CallerIPAddress))
| where Vault has "kv-orders-prod" or _ResourceId has "/vaults/kv-orders-prod"
| summarize Calls=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated)
by Principal, Operation, Status, CallerIp
| order by Status desc, Calls desc

Un 403 avec le bon principal et une opération précise pointe vers rôle, scope ou propagation. L’absence totale de trace renvoie plutôt vers DNS, réseau, mauvais nom de coffre ou instrumentation. Un succès depuis le portail mais un refus applicatif indique souvent que deux identités différentes ont été testées.

Décider validation, correction ou rollback

Validez si tous les tests positifs passent, tous les tests négatifs restent refusés, les workloads critiques sont stables et aucun rôle temporaire trop large n’a été ajouté. Corrigez en place si un seul principal identifié manque d’une opération prévue et si un rôle minimal existe. Conservez une observation renforcée après la fenêtre, car certains jobs ne s’exécutent qu’à intervalle long.

Rollbackez si plusieurs consommateurs critiques sont absents, si le principal réel n’est pas maîtrisé, si les assignments hérités rendent le périmètre illisible ou si les preuves d’audit manquent. Le retour au modèle précédent doit avoir été testé hors production. Avant de l’exécuter, confirmez que les access policies du snapshot sont toujours présentes et qu’aucun changement concurrent ne les a modifiées.

bash 05-rollback-to-access-policies.sh
az keyvault update --resource-group "$RG" --name "$VAULT" --enable-rbac-authorization false

az keyvault show -g "$RG" -n "$VAULT" --query '{rbac:properties.enableRbacAuthorization,accessPolicies:properties.accessPolicies}' -o json

# Rejouer toute la matrice. Un toggle reussi ne prouve pas le service retabli.

Ne supprimez pas immédiatement les role assignments préparés : ils facilitent l’analyse et une reprise ultérieure. Marquez-les comme non actifs pour ce coffre, corrigez l’inventaire, puis programmez une nouvelle migration plutôt que de prolonger un état hybride incompris.

Conclusion

Migrer Key Vault vers Azure RBAC n’est pas un changement de bouton. C’est une migration du contrat d’autorisation du data plane. La réussite dépend de l’inventaire des identités, de la traduction des opérations en rôles minimaux, de probes exécutées par les vrais runtimes et de traces disponibles au moment de la bascule.

La décision finale doit rester binaire et prouvable : conserver Azure RBAC parce que chaque accès attendu et chaque refus attendu ont été validés, ou revenir temporairement aux access policies avec le snapshot intact. Ajouter un rôle administrateur pour faire disparaître les 403 n’est ni une validation ni un rollback.