Automation
Azure Resource Graph : prouver qu’un inventaire est complet avant une remédiation automatisée
Un runbook de production pour prouver le scope, l’identité, la pagination et la complétude d’Azure Resource Graph avant qu’un inventaire ne pilote des changements.
Un job de plateforme inventorie les ressources de production avec Azure Resource Graph, sélectionne celles qui n’ont pas de tag de propriétaire, puis prépare un lot de remédiation. Un matin, le nombre de candidats baisse de trente pour cent. Le job est vert et la requête retourne toujours des lignes. Cela ne prouve pas que le parc s’est amélioré : l’identité runtime voit peut-être moins d’abonnements, le résultat peut être tronqué ou le client peut s’être arrêté après la première page.
Un inventaire incomplet est dangereux précisément parce qu’il peut paraître valide. Ce runbook transforme la complétude en gate explicite avant toute écriture. Le cas fil rouge est un workflow de tags multi-abonnements, mais les mêmes contrôles s’appliquent aux inventaires de coûts, aux contrôles de posture, aux candidats de décommissionnement et à la préparation de changements.
Figer le contrat d’inventaire
Suspendez les étapes qui modifient l’état tout en conservant la requête en lecture seule. Consignez l’identité, le tenant, le scope des management groups, les abonnements attendus, la version de requête, la version du client et les derniers totaux valides. Un nombre de lignes sans ce contexte n’est pas une preuve reproductible.
inventory: prod-owner-tag-candidates
runtime_identity: mi-platform-inventory-prod
tenant_id: <tenant-id>
management_groups:
- mg-production
expected_subscriptions_source: platform-subscription-registry
query_version: 8f2c1d7
client: azure-cli-2.x
write_gate:
mode: disabled
require:
- expected-subscriptions-covered
- every-page-consumed
- result-not-partial
- control-totals-within-baseline
- positive-and-negative-canary
rollback_assets:
- previous-query
- previous-scope-manifest
- previous-identity-assignment
- per-resource-change-journal Utilisez un registre maintenu comme source des abonnements attendus. Déduire cet ensemble du même appel Resource Graph crée un contrôle circulaire : un abonnement absent disparaît à la fois de l’inventaire et de sa validation.
Prouver l’identité et le scope d’abonnements
Resource Graph ne retourne que les ressources que le principal peut lire. Il n’ajoute aucune ligne témoin pour les abonnements masqués par RBAC. Une réponse réussie peut donc être complète pour l’appelant et incomplète pour le modèle d’exploitation.
Exécutez le diagnostic sous l’identité managée ou le service principal réellement utilisé par le job. Comparez ses abonnements visibles et actifs au registre approuvé, puis vérifiez un accès de lecture au control plane sur le scope prévu. Après une modification d’abonnement ou de rôle, renouvelez le contexte d’authentification avant de retester.
az account show \
--query '{tenant:tenantId, subscription:id, user:user}' \
--output json
az account list --all \
--query "[?state=='Enabled'].{id:id,name:name,tenant:tenantId}" \
--output json > visible-subscriptions.json
az role assignment list \
--assignee-object-id <runtime-principal-object-id> \
--all --include-inherited \
--query "[].{scope:scope,role:roleDefinitionName}" \
--output json > runtime-rbac.json Ne corrigez pas un écart inexpliqué en accordant un accès large au tenant. Identifiez l’abonnement ou la branche de management group manquante et restaurez l’affectation minimale prévue.
Rendre la requête paginable par construction
Utilisez un tri déterministe et projetez un identifiant de ressource scalaire. Évitez limit, take et sample dans un inventaire de production : ces opérateurs bornent volontairement le résultat et peuvent empêcher le retour d’un token de continuation. Une projection composée uniquement de colonnes dynamiques ou nulles ne peut pas non plus être considérée comme paginable de façon fiable.
Resources
| where tostring(tags.Environment) =~ "prod"
| extend Owner = tostring(tags.Owner)
| where isempty(Owner)
| project id = tolower(id), subscriptionId, resourceGroup,
type = tolower(type), name, location
| order by id asc Conservez exactement la même requête et le même scope entre les pages. Un token de continuation capture le contexte de pagination ; changer la requête, les abonnements ou le management group en cours de lecture invalide le contrat d’inventaire.
Lire les métadonnées de réponse, pas seulement les lignes
La réponse REST expose count, totalRecords, resultTruncated et, lorsqu’il est disponible, $skipToken. Conservez ces valeurs pour chaque page. Continuez jusqu’à disparition du token et bloquez le workflow si la réponse indique une troncature sans fournir de chemin de continuation sûr.
Pour chaque page de reponse
Conserver le statut HTTP et l'identifiant de correlation
Conserver count, totalRecords et resultTruncated
Noter la presence de $skipToken
Conserver les headers de quota Resource Graph
Conserver x-ms-tenant-subscription-limit-hit s'il est present
Ajouter les lignes seulement apres validation du schema
Rejeter l'inventaire quand
resultTruncated vaut true sans continuation sure
une page repete un token deja traite
des identifiants de ressource sont dupliques sans explication
les abonnements couverts different du registre approuve
le budget de retry expire apres throttling Traitez l’exécution sur scope partiel comme un mode dégradé explicite, pas comme une option de confort. Si allowPartialScopes est activé, rendez ce fait visible dans les preuves et maintenez les écritures désactivées tant que le scope omis n’est pas identifié et accepté. Distinguez aussi le throttling Resource Graph du throttling Azure Resource Manager et appliquez le backoff indiqué par les headers au lieu de lancer des jobs concurrents.
Construire des totaux de contrôle indépendants
La complétude ne se prouve pas en comparant une requête avec elle-même. Produisez des totaux par abonnement et type de ressource, puis comparez-les au dernier inventaire valide et à un registre d’abonnements indépendant. Analysez aussi bien les baisses que les hausses inexpliquées.
Resources
| summarize resources = count(),
resourceGroups = dcount(resourceGroup)
by subscriptionId, type = tolower(type)
| order by subscriptionId asc, type asc Pour une ressource de chaque abonnement critique, comparez la ligne Resource Graph avec un GET ARM direct. C’est un contrôle borné de fraîcheur, pas une raison de remplacer l’inventaire à l’échelle par des milliers d’appels ARM. Une ressource modifiée récemment peut demander du temps avant d’apparaître dans l’index : mesurez cette fenêtre et retentez avant de conclure à une absence durable.
Normalisez les identifiants en minuscules, triez-les et calculez une empreinte de l’ensemble complet. Stockez le nombre de pages, de lignes et d’abonnements ainsi que cette empreinte avec la version de requête. Ces valeurs rendent visible une régression silencieuse limitée à la première page.
Séparer sélection et exécution
La requête doit produire des candidats, pas autoriser des changements. Matérialisez un snapshot immuable des candidats, joignez les preuves de complétude, puis imposez une approbation ou une policy gate séparée avant la remédiation. Relancer la requête pendant l’exécution peut modifier silencieusement l’ensemble cible.
batch_id: owner-tags-20260918-01
inventory_digest: sha256:<digest>
query_version: 8f2c1d7
pages: 4
rows: 3274
subscriptions_expected: 18
subscriptions_observed: 18
partial_scope: false
truncated: false
candidate_rows: 42
execution:
mode: canary
max_targets: 1
require_owner_value_from: approved-cmdb
idempotency_key: batch-id-plus-resource-id
journal_before_and_after: true Refusez les valeurs de tag libres déduites du nom d’une ressource. L’inventaire identifie un champ manquant ; il ne prouve pas le bon propriétaire. Résolvez cette valeur depuis une source approuvée ou envoyez le candidat en validation humaine.
Valider avec un canari positif et un canari négatif
Choisissez un candidat approuvé et une ressource témoin qui ne doit pas être modifiée. Appliquez le changement borné au candidat, relisez-le directement par ARM, puis attendez que Resource Graph reflète le nouvel état. Confirmez que le témoin est intact et qu’une deuxième exécution ne produit aucune écriture.
La validation échoue si le canari disparaît de l’inventaire sans changement ARM, si le témoin négatif est sélectionné, si le job ne peut pas expliquer chaque page ou si une nouvelle exécution réécrit la ressource. Ces échecs désignent des défauts d’inventaire, de sélection ou d’idempotence, pas un simple problème de tags.
Décider reprise, maintien ou rollback
Ne reprenez la remédiation que lorsque l’identité runtime couvre les abonnements approuvés, que toutes les pages ont été consommées, qu’aucun signal de scope partiel ou de troncature ne subsiste, que les totaux sont expliqués et que les deux canaris passent.
Maintenez le workflow en lecture seule lorsque l’inventaire reste utile mais que les preuves de fraîcheur, de couverture ou de pagination sont incomplètes. Réparez la frontière précise : RBAC, contexte d’authentification, manifeste de scope, projection de requête, boucle de continuation ou gestion du throttling.
Rollbackez lorsqu’une nouvelle requête, identité ou version de client a créé l’écart. Restaurez la version précédente, désactivez la planification et régénérez l’inventaire avant toute nouvelle écriture. Si des changements ont déjà été appliqués, utilisez le journal par ressource pour restaurer seulement les valeurs touchées ; ne « rollbackez » jamais avec une deuxième requête large sur un scope toujours non prouvé.
Conclusion
Azure Resource Graph peut retourner une réponse valide tout en restant dangereux comme source d’inventaire pour une automatisation. La complétude dépend de l’identité, du scope d’abonnements, de la forme de la requête, de la pagination, des signaux de résultat partiel et de totaux de contrôle observables.
Transformez ces propriétés en gate d’écriture. Lorsque les preuves sont complètes, promouvez un canari idempotent puis reprenez par lots bornés. Sinon, conservez la lecture seule, réparez la frontière défaillante et gardez un journal de rollback précis.