Le registre des concessions

Sur Le Port, on n'a rien construit. Les bassins, les quais, les portiques et les voies d'accès existaient avant nous, et ils appartiennent à l'autorité portuaire. Ce qu'on obtient, c'est une concession : un lot délimité, pour une durée donnée, avec un règlement qui dit ce qu'on a le droit d'y faire.

La capitainerie tient ce registre. Elle sait quel lot est attribué à qui, quel opérateur paie quelle redevance, quelles marchandises sont admises sur quel terminal. Sans ce registre, le premier venu poserait ses conteneurs sur le quai voisin et personne ne saurait qui facturer.

Azure fonctionne de la même façon, avec une nuance qui coûte cher : le registre existe toujours, même quand personne ne l'a conçu. Ta première ressource se crée forcément dans un groupe de ressources, lui-même dans un abonnement, lui-même rattaché à un locataire.

Si tu ne décides pas de cette structure, elle se décide toute seule, par accumulation. Six mois plus tard, personne ne sait plus quel abonnement porte quoi, ni qui paie la machine virtuelle allumée depuis mars.

La gouvernance ne consiste pas à ajouter des contrôles après coup. Elle consiste à créer les ressources à un endroit où les contrôles existaient déjà.

Vue d'ensemble : quatre portées, une seule règle

Azure empile quatre niveaux d'organisation, du plus large au plus étroit : le groupe d'administration, l'abonnement, le groupe de ressources, la ressource. Chacun est une portée, c'est-à-dire un point d'accroche auquel on peut attacher une décision.

La règle qui gouverne tout le reste tient en une phrase : ce qu'on attache à une portée s'applique à cette portée et à tout ce qu'elle contient.

Deux choses descendent ainsi le long de l'arbre : les attributions de rôles et les affectations de stratégie. Une troisième ne descend pas toute seule, et c'est la source d'à peu près toutes les surprises de refacturation : les balises.

flowchart TB T["Locataire Entra ID"] --> R["Groupe d'administration racine"] R --> MG1["Plateforme"] R --> MG2["Charges de travail"] R --> MG3["Bac à sable"] MG1 --> S1["Abonnement connectivité"] MG1 --> S2["Abonnement identité"] MG2 --> S3["Abonnement production"] MG2 --> S4["Abonnement hors production"] S3 --> RG1["Groupe de ressources paiement"] S3 --> RG2["Groupe de ressources portail"] RG1 --> RES1["Compte de stockage"] RG1 --> RES2["Base de données"] classDef racine fill:#8a4d16,stroke:#5f5e5a,color:#f5f2ec classDef groupe fill:#b5651d,stroke:#5f5e5a,color:#f5f2ec classDef abo fill:#d89253,stroke:#5f5e5a,color:#1a1a1a classDef res fill:#ede9e1,stroke:#5f5e5a,color:#1a1a1a class T,R racine class MG1,MG2,MG3 groupe class S1,S2,S3,S4 abo class RG1,RG2,RES1,RES2 res
Portée Ce qu'on y décide Ce qui s'hérite vers le bas Le piège classique
Groupe d'administration Les règles communes à un ensemble d'abonnements Rôles et stratégies Reproduire l'organigramme, qui change tous les ans
Abonnement La facturation, les quotas, le périmètre d'isolation Rôles et stratégies Tout mettre dans un seul, puis découvrir une limite de quota
Groupe de ressources Le cycle de vie commun et la suppression groupée Rôles et stratégies Regrouper par type de ressource au lieu de regrouper par durée de vie
Ressource La configuration effective Verrous et rôles vers les sous-ressources Corriger ici ce qui aurait dû être interdit plus haut
La question n'est jamais « quel contrôle poser », mais « à quelle portée le poser ». Trop haut, il bloque des équipes qui n'étaient pas visées. Trop bas, celui qui crée la ressource peut le retirer.

Les quatre étages

Le locataire et les groupes d'administration

Le locataire, c'est l'annuaire Microsoft Entra ID : la capitainerie qui délivre les badges. Un abonnement fait confiance à exactement un locataire pour authentifier ses utilisateurs, et on ne la change pas de gaieté de cœur. Transférer un abonnement vers un autre annuaire fait tomber les attributions de rôles qui vivaient dans l'ancien.

Au-dessus des abonnements, les groupes d'administration forment un arbre. Un groupe racine est créé automatiquement pour l'annuaire, il contient tout, et il ne se déplace ni ne se supprime. Un administrateur général doit explicitement élever ses accès avant de pouvoir le gérer, ce qui est une protection et non une formalité.

Chaque groupe a un parent unique, chaque abonnement se range dans exactement un groupe. La profondeur de l'arbre est bornée, et de toute façon un arbre profond devient vite illisible.

Le critère de découpage n'est pas l'organigramme. C'est : qu'est-ce que je veux appliquer différemment ? Une structure qui fonctionne sépare la plateforme (connectivité, identité, journalisation) des charges de travail, avec un bac à sable à part où les règles sont volontairement plus souples et l'accès au réseau d'entreprise inexistant.

À la racine, on ne met que ce qui doit valoir partout, sans exception : les régions autorisées, l'interdiction des configurations qui ne se rattrapent pas. Une stratégie de refus mal calibrée à ce niveau bloque l'entreprise entière.

L'abonnement

L'abonnement est la seule portée qui soit trois choses à la fois.

C'est une limite de facturation : la consommation y est agrégée, et c'est l'unité naturelle de refacturation vers une entité ou un projet.

C'est une limite de quota. Les limites de service s'appliquent par abonnement, et souvent par région à l'intérieur de celui-ci. Certaines s'augmentent sur demande, d'autres non. Une équipe qui monte en charge finit parfois par ajouter un abonnement plutôt que par optimiser quoi que ce soit, et la documentation des limites se lit avant, pas après.

C'est enfin une limite d'administration. C'est le rayon de souffle : ce qu'un rôle Propriétaire attribué ici peut atteindre, et ce qu'il ne peut pas atteindre.

Un abonnement se déplace d'un groupe d'administration à un autre sans toucher aux ressources qu'il contient. En revanche, il hérite immédiatement des stratégies de son nouveau parent, ce qui peut faire apparaître d'un coup une centaine de ressources non conformes.

Le groupe de ressources

Le groupe de ressources est un conteneur logique, et sa vraie définition est temporelle : on y met ce qui naît et meurt ensemble.

Supprimer le groupe supprime tout ce qu'il contient, en une commande, sans retour possible. C'est la fonctionnalité la plus utile pour un environnement éphémère et la plus dangereuse pour tout le reste. Un verrou de suppression sur les groupes de production n'est pas un luxe.

Une ressource appartient à un seul groupe à un instant donné. Elle peut parfois être déplacée, mais tous les types de ressources ne le supportent pas, et un déplacement ne change jamais la région d'exécution.

L'emplacement du groupe mérite sa propre phrase : il indique où sont stockées les métadonnées du groupe, pas où tournent les ressources. Rien n'empêche un groupe déclaré en Europe de l'Ouest de contenir une ressource au Canada, et pour un scénario soumis à une exigence de résidence, ce détail se vérifie.

Enfin, les groupes ne s'imbriquent pas. Toute la profondeur d'organisation se joue au-dessus, dans les groupes d'administration.

La ressource

Dernier étage, et il en cache un cinquième : certaines ressources portent des sous-ressources qui sont elles-mêmes des portées. Un sous-réseau à l'intérieur d'un réseau virtuel en est l'exemple canonique, et il permet de déléguer un droit très précis sans ouvrir le réseau entier.

C'est aussi à cet étage qu'on rencontre les verrous de ressource. Ils existent en deux formes, interdiction de suppression et lecture seule, ils s'héritent vers le bas, et ils s'appliquent à tout le monde, y compris au propriétaire. Le mode lecture seule a des effets de bord notables : des opérations qui ressemblent à des lectures sont en réalité des écritures dans l'API, et se retrouvent bloquées.

Le groupe de ressources se conçoit sur la question « qu'est-ce que je détruis d'un seul geste ? ». Si la réponse te fait peur, le découpage est mauvais.

Découper en abonnements

Sur Le Port, on ne concède pas un seul lot géant à un opérateur. On sépare le terminal à conteneurs du terminal céréalier, parce que les règles, les équipements et les redevances n'ont rien à voir. Mais chaque séparation ajoute une clôture, un poste de contrôle et un flux à organiser entre les deux.

Le découpage en abonnements suit la même arithmétique. Chaque motif de séparation est légitime, et chacun a un prix.

Motif de découpage Ce que ça sépare bien Ce que ça complique
Production et hors production Les droits, les fenêtres de changement, les stratégies strictes réservées à la production La comparaison des deux environnements, qui dérivent silencieusement
Entité de facturation ou projet La refacturation, sans dépendre de la qualité de l'étiquetage La vue consolidée, à reconstruire au niveau du groupe d'administration
Limites de quota et d'échelle Une charge très volumineuse qui consomme à elle seule un quota régional Le suivi, avec deux abonnements pour une seule application
Souveraineté ou classification des données Les périmètres réglementaires, avec des règles de région distinctes Les échanges entre périmètres, qui deviennent des flux à justifier
Autonomie d'équipe La délégation d'un rôle large sans exposer le reste Le réseau, qui se raccorde ensuite par appairage et redevient un sujet central
Bac à sable L'expérimentation, coupée du réseau d'entreprise Rien, à condition que la coupure soit réelle et pas déclarative

La question à se poser avant d'en créer un de plus est simple : est-ce que je sépare une facture, un quota, ou un droit ? Si la réponse n'est aucun des trois, un groupe de ressources suffit probablement.

Un abonnement de plus, c'est un jeu de stratégies, un jeu de rôles, un plan d'adressage et une ligne de facture de plus. Cela se justifie, cela ne s'improvise pas.

Azure Policy : ce qui empêche

Le règlement portuaire ne se contente pas d'exister. Un agent refuse à l'entrée le camion qui n'a pas le bon scellé, et une visite de conformité passe périodiquement sur les terminaux déjà en exploitation. Deux moments distincts, deux mécanismes distincts.

Azure Policy fait exactement les deux. Une définition décrit une condition et un effet. Une affectation applique cette définition à une portée. À partir de là, toute requête de création ou de modification qui traverse cette portée est évaluée avant d'atteindre le fournisseur de ressources.

sequenceDiagram participant D as Équipe applicative participant A as Azure Resource Manager participant P as Azure Policy participant F as Fournisseur de ressources D->>A: "Créer un compte de stockage, accès public activé" A->>P: "Évaluer les affectations qui couvrent la portée" P-->>A: "Modify ajoute la balise CostCenter manquante" P-->>A: "Deny refuse l'accès public au blob" A-->>D: "Requête rejetée, nom de la stratégie renvoyé" Note over P,F: "Aucun appel au fournisseur : rien n'a été créé"

L'ordre compte : les effets qui corrigent la requête sont évalués avant ceux qui la refusent. Une balise manquante est ajoutée, une configuration interdite est bloquée.

Effet Ce qu'il fait Quand l'utiliser
Audit Marque la ressource non conforme et laisse passer Mesurer l'ampleur avant d'interdire quoi que ce soit
Deny Refuse la requête au moment de la création ou de la mise à jour Ce qui ne doit jamais exister, une fois l'audit digéré
Append Ajoute un champ à la requête Compléter une propriété simple sans refuser la demande
Modify Ajoute, remplace ou retire une balise ou une propriété Étiquetage imposé, y compris en rattrapage sur l'existant
DeployIfNotExists Déploie la ressource complémentaire manquante Paramètre de diagnostic, agent, protection attendue partout
AuditIfNotExists Constate l'absence d'une ressource liée Vérifier sans déployer, quand le déploiement doit rester manuel
DenyAction Bloque une action donnée, la suppression par exemple Protéger des ressources que personne ne doit détruire
Disabled Neutralise l'affectation sans la supprimer Éteindre une règle le temps d'un incident, en gardant sa trace

Une définition tient dans un objet JSON. En voici une des plus courtes, et des plus employées.

{
  "properties": {
    "displayName": "Interdire l'accès public aux conteneurs blob",
    "mode": "Indexed",
    "policyRule": {
      "if": {
        "allOf": [
          { "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
          {
            "field": "Microsoft.Storage/storageAccounts/allowBlobPublicAccess",
            "notEquals": "false"
          }
        ]
      },
      "then": { "effect": "[parameters('effet')]" }
    },
    "parameters": {
      "effet": {
        "type": "String",
        "allowedValues": ["Audit", "Deny", "Disabled"],
        "defaultValue": "Audit"
      }
    }
  }
}

Le paramètre effet n'est pas une coquetterie : c'est ce qui permet d'affecter la même définition en Audit sur le bac à sable et en Deny sur la production, sans écrire deux règles.

Trois notions complètent le tableau. Les initiatives regroupent des dizaines de définitions derrière une seule affectation et un jeu de paramètres partagé, ce qui est la seule façon tenable de porter un référentiel réglementaire complet. Les exclusions retirent une sous-portée du champ d'une affectation. Les exemptions, elles, sont des objets à part entière, avec un motif et une date d'expiration : c'est la bonne réponse à « on fera ça plus tard », parce qu'elle se périme d'elle-même.

Reste la conformité. Une affectation évalue aussi l'existant et produit un état, réévalué périodiquement ou à la demande. Attention au malentendu : ajouter une règle Deny ne supprime rien de ce qui existait déjà, cela le marque simplement non conforme. Pour corriger l'existant, il faut une tâche de correction, donc un effet Modify ou DeployIfNotExists, donc une identité managée portée par l'affectation.

Une stratégie en Audit ne protège de rien, elle documente. C'est une étape, pas une destination.

L'étiquetage : ce qui explique

La stratégie empêche. L'étiquette raconte. Sur Le Port, le conteneur porte un numéro, un propriétaire, un contenu déclaré et une destination : sans ces marques, la pile de conteneurs est un tas anonyme que personne ne peut facturer ni évacuer.

Deux choses se nomment sur Azure, et les deux se décident avant la première ressource.

Le nom d'abord. Une convention lisible enchaîne le type de ressource, la charge de travail, l'environnement, la région et un numéro d'instance. Le guide de nommage du Cloud Adoption Framework propose des abréviations stables, ce qui évite les débats sans fin. Le nom ne se change presque jamais après coup, et certains sont uniques à l'échelle mondiale, comme celui d'un compte de stockage.

Les balises ensuite : des paires clé et valeur attachées à la ressource, au groupe ou à l'abonnement. Elles portent tout ce que le nom ne peut pas contenir.

Balise La question à laquelle elle répond Qui la remplit
Environment Est-ce de la production ? donc quelles fenêtres, quelle astreinte L'équipe qui déploie, imposé dans le modèle
CostCenter Qui paie cette ligne de facture ? La finance, imposé par stratégie
Owner Qui appelle-t-on quand ça sonne à 3 heures ? L'équipe applicative
Criticality Combien de temps peut-on rester sans ? Le métier, avec l'architecte
DataClassification Cette donnée peut-elle sortir de la région ? Le responsable des données
ExpiresOn A-t-on encore une raison de la laisser allumée ? Le créateur, obligatoire en bac à sable

Trois limites à connaître, parce qu'on ne se fait avoir qu'une fois.

Les balises ne s'héritent pas. Une balise posée sur le groupe de ressources ne se recopie pas sur les ressources qu'il contient. C'est précisément le travail de l'effet Modify, avec une définition intégrée prévue pour ça.

# Récupérer la définition intégrée qui recopie une balise du groupe de ressources
definition=$(az policy definition list \
  --query "[?displayName=='Inherit a tag from the resource group'].id | [0]" -o tsv)

# L'affecter au groupe d'administration, avec l'identité qui servira aux corrections
az policy assignment create \
  --name "heriter-centre-de-cout" \
  --policy "$definition" \
  --scope "/providers/Microsoft.Management/managementGroups/mg-corp" \
  --params '{"tagName":{"value":"CostCenter"}}' \
  --mi-system-assigned \
  --location westeurope

Deuxième limite : tous les types de ressources n'acceptent pas les balises, et leur nombre par ressource est plafonné. La matrice de prise en charge se consulte avant de bâtir un modèle de refacturation dessus.

Troisième limite, la plus coûteuse : une balise n'agit pas rétroactivement sur la facturation. Les enregistrements de coût déjà émis ne se réécrivent pas. Étiqueter en juin ne dit pas qui a consommé en mars.

Une balise imposée par stratégie dès le premier jour vaut mieux qu'un chantier d'étiquetage lancé le jour où la facture devient incompréhensible.

Déléguer sans céder le quai

Un opérateur portuaire peut confier la manutention d'un terminal à un prestataire. Il ne lui remet pas les clés de la capitainerie pour autant : il lui accorde un droit d'intervention sur un lot, révocable.

C'est le rôle d'Azure Lighthouse. Un locataire prestataire obtient des droits, définis et limités, sur des abonnements ou des groupes de ressources d'un locataire client, sans qu'aucun compte soit créé côté client. Les équipes du prestataire travaillent depuis leur propre annuaire, avec leurs propres exigences d'authentification.

Le client garde deux choses essentielles : la visibilité sur ce qui a été délégué, et la capacité de retirer la délégation. Tous les rôles ne sont d'ailleurs pas éligibles à ce mécanisme, ce qui est une bonne nouvelle.

Déléguer une opération n'est pas déléguer une propriété. Si le retrait de l'accès prend un ticket et trois jours, ce n'était pas une délégation.

Ce qui a remplacé les Blueprints

Pendant des années, la réponse à « comment j'emballe une hiérarchie, ses stratégies et ses modèles dans un objet réutilisable » était Azure Blueprints. Microsoft a annoncé la dépréciation du service, qui n'est jamais sorti de préversion, et oriente vers deux briques distinctes.

Les spécifications de modèle transforment un modèle ARM ou Bicep en ressource Azure de plein droit : versionnée, partageable, protégée par RBAC. On ne fait plus circuler un fichier par courriel, on référence un objet.

Les piles de déploiement gèrent un ensemble de ressources comme une unité unique, à travers plusieurs portées. Elles apportent surtout ce qui manquait : des paramètres de refus qui empêchent la modification ou la suppression des ressources gérées en dehors de la pile, et une décision explicite sur le sort des ressources retirées du modèle.

Si tu démarres aujourd'hui, ne construis rien sur Blueprints. La combinaison spécification de modèle plus pile de déploiement fait le travail, et le billet suivant en fait le cœur du sujet.

Scénario B : l'exposition Deep Root Analytics

En juin 2017, Chris Vickery, chercheur de la société UpGuard, découvre un espace de stockage objet accessible publiquement, sans authentification. Il contient des données de profilage portant sur environ 198 millions d'électeurs américains, constituées pour le compte d'organisations politiques. Le prestataire à l'origine de la configuration, Deep Root Analytics, a reconnu publiquement l'exposition. UpGuard a publié son analyse détaillée.

Le détail qui compte pour ce billet : rien n'a été piraté. Aucune vulnérabilité exploitée, aucune intrusion. Une ressource de stockage a été créée avec une option d'accès public, et laissée ainsi. N'importe qui connaissant l'adresse pouvait lire.

Ce n'est pas une histoire d'attaquant sophistiqué. C'est une histoire de portée.

Le contrôle technique existait chez tous les fournisseurs de l'époque, comme il existe aujourd'hui sur Azure : la propriété qui autorise ou interdit l'accès public à un conteneur de stockage est exactement celle que cible la définition JSON plus haut. Ce qui manquait, c'est une affectation à un niveau supérieur à celui de la personne qui a créé la ressource.

L'épisode se transpose tel quel sur la hiérarchie décrite plus haut.

Un contrôle posé au même niveau que celui qui crée la ressource n'est pas un contrôle : celui qui peut créer peut désactiver. Le garde-fou vit au groupe d'administration, pas dans le groupe de ressources du projet.

Un effet Audit aurait produit une ligne de non-conformité de plus, à lire dans un tableau de bord. Un effet Deny aurait renvoyé une erreur au moment de la création, à quelqu'un qui aurait dû trouver une autre solution. La différence entre les deux se compte en durée d'exposition, et UpGuard précise que personne n'a pu établir depuis combien de temps ces données étaient accessibles.

Enfin, sans balise de classification ni balise de propriétaire, une organisation ne sait pas répondre à la question la plus urgente d'un incident : qu'est-ce qu'il y a là-dedans, et qui en répond ? L'étiquetage sert d'abord à ça, bien avant de servir la comptabilité.

Une configuration dangereuse qui n'a jamais été refusée finit par exister. La seule question est de savoir combien de temps s'écoule avant que quelqu'un la trouve.

Points clés

  • Les quatre portées (groupe d'administration, abonnement, groupe de ressources, ressource) obéissent à une règle unique : rôles et stratégies descendent, les balises non.
  • L'abonnement est simultanément une limite de facturation, de quota et d'administration ; on n'en crée un de plus que pour séparer l'une de ces trois choses.
  • Un groupe de ressources rassemble ce qui naît et meurt ensemble, et son emplacement désigne l'endroit où vivent ses métadonnées, pas celui où tournent les ressources.
  • Audit mesure, Deny empêche, Modify et DeployIfNotExists corrigent l'existant via une tâche de correction et une identité managée ; l'affectation se pose au-dessus de celui qui crée.
  • L'exposition Deep Root Analytics de juin 2017 illustre le cas pur : aucune intrusion, une simple option d'accès public jamais refusée par une règle.

Dans la série

Palier 1 : Ouvrir le port. Domaine : Gouvernance.

Pour aller plus loin