Nel post precedente abbiamo visto perché considero le Azure Policy uno degli strumenti più importanti per la governance di un tenant Azure.
Infrastructure as Code ci permette di descrivere l’infrastruttura che vogliamo creare. Le Policy fanno qualcosa di diverso: definiscono le regole entro cui quell’infrastruttura può esistere.
Una Policy è una regola
Semplificando parecchio, possiamo immaginare una Policy come una regola composta da due parti: IF qualcosa è vero, THEN applica un determinato effetto.
Per esempio:
SE una risorsa viene creata fuori dalle region ammesse
ALLORA impediscine la creazione
La parte interessante è che il controllo viene eseguito dalla piattaforma. Non dobbiamo creare un processo esterno che ogni tanto interroga Azure alla ricerca di configurazioni sbagliate. La regola entra a far parte del meccanismo con cui Azure valuta le risorse.
Ed è esattamente questo che rende le Policy interessanti dal punto di vista della governance.
Naturalmente prima dobbiamo descrivere la regola.
Una Policy Definition contiene le informazioni necessarie per stabilire quali risorse debbano essere valutate, le condizioni da verificare e l’effetto da applicare. Definire una Policy, però, non significa automaticamente applicarla ovunque. La Policy deve essere anche assegnata ad uno scope.
Ed è qui che la struttura organizzativa del tenant diventa importante: possiamo avere regole che devono valere per tutta l’organizzazione e altre molto più specifiche, magari applicabili solamente ad una particolare subscription o ad un determinato insieme di workload. Di conseguenza la struttura dei Management Group e delle subscription non è semplicemente un modo per tenere in ordine il portale. Diventa parte integrante del modello di governance.
Ma fino a questo momento siamo rimasti abbastanza sul teorico. E io, quando devo capire veramente qualcosa, la devo smontare, come facevo (faccio?) da piccolo con il Lego. E quindi, in questo caso, guardiamo del codice.
Per fare un esempio che tutti, fin dal giorno zero, possono applicare, parliamo di tag e supponiamo che ogni risorsa debba avere un tag Environment.
Potremmo scrivere una Policy che verifichi la presenza del tag e impedisca la creazione della risorsa quando manca. In realtà non dobbiamo nemmeno inventarcela: Azure contiene già una Policy built-in chiamata Require a tag on resources.
Microsoft mantiene una serie di Policy built-in che si possono trovare qui
Possiamo recuperare il codice direttamente dal repository GitHub della policy built-in, oppure possiamo usare la Azure CLI:
az policy definition show \
--name 871b6d14-10aa-478d-b590-94f262ecfa99
Ridotta alle parti che ci interessano, la struttura è sostanzialmente questa:
{
...
"mode": "Indexed",
"parameters": {
"tagName": {
"type": "String",
"metadata": {
"displayName": "Tag Name",
"description": "Name of the tag, such as 'environment'"
}
}
},
"policyRule": {
"if": {
"field": "[concat('tags[', parameters('tagName'), ']')]",
"exists": "false"
},
"then": {
"effect": "deny"
}
},
...
}
Ed ecco buona parte di Azure Policy in una ventina di righe: la Policy riceve un parametro chiamato tagName. La parte if verifica se il tag esiste e la parte then stabilisce cosa succede quando la condizione è verificata.
In questo caso:
IF il tag non esiste THEN deny
La struttura delle Policy Definition ruota proprio attorno alla regola e all’effetto da applicare quando una risorsa soddisfa le condizioni indicate.
Aspetta. Non abbiamo appena detto Policy as Code?
La tentazione potrebbe essere prendere quel JSON, metterlo nel nostro repository e creare una Custom Policy.
Possiamo farlo. Ma non dovremmo.
Perché quella Policy esiste già.
Microsoft fornisce diverse built-in Policy dedicate ai tag: per richiedere un tag, richiedere tag e valore, ereditarli da Resource Group o subscription, aggiungerli e modificarli.
Prima di creare una Custom Policy, quindi, una delle prime cose che faccio è verificare se Azure ne possiede già una che risolve lo stesso problema.
Anche Azure CLI può aiutarci:
az policy definition list \
--query "[?contains(displayName, 'tag')].[displayName,name,policyType]" \
--output table
Perché è importante? Perché scrivere la Policy è la parte facile. Il problema arriva dopo.
Una Custom Policy diventa qualcosa che dobbiamo capire, documentare, testare, versionare, distribuire e mantenere. Dieci Custom Policy sono gestibili. Cinquanta iniziano a essere interessanti. Qualche centinaio, magari replicate tra più tenant e con piccole variazioni tra loro, diventano rapidamente un prodotto software che nessuno aveva pianificato di costruire.
Quindi, se una policy built-in risolve il problema, preferisco usare quella.
Applichiamo la policy
Affinché una policy venga effettivamente valutata da Azure, deve essere “assegnata”. Possiamo utilizzare direttamente la policy built-in e specificare quale tag vogliamo rendere obbligatorio:
POLICY_ID="/providers/Microsoft.Authorization/policyDefinitions/871b6d14-10aa-478d-b590-94f262ecfa99"
az policy assignment create \
--name require-environment-tag \
--display-name "Require Environment tag" \
--scope "$SCOPE" \
--policy "$POLICY_ID" \
--params '{"tagName":{"value":"Environment"}}'
Dove il valore della variabile $SCOPE può essere, tra gli altri, un Management Group, una subscription o un Resource Group.
Abbiamo quindi applicato una regola aziendale senza creare nessuna nuova Policy Definition. L’Assignment è ciò che collega una Policy o un’Initiative (ovvero un insieme di policy) ad uno scope e fornisce eventualmente i valori dei suoi parametri.
E il parametro è particolarmente importante perché rende la policy riutilizzabile. Non ci serve una Policy RequireEnvironmentTag, una RequireCostCenterTag e una RequireApplicationTag.
Ci serve una sola regola generale:
Require tag X
Il valore di X appartiene all’Assignment.
È un dettaglio apparentemente piccolo, ma è uno dei modi con cui possiamo evitare l’esplosione del numero di Policy da gestire.
Bloccare tutto subito? Forse no

Una delle tentazioni iniziali, una volta scoperte le Policy, è trasformarsi improvvisamente nella polizia del tenant. Abbiamo deciso le regole? Perfetto: effetto Deny e blocchiamo tutto quello che non le rispetta.
Tecnicamente funziona. Organizzativamente un po’ meno.
Soprattutto quando introduciamo le Policy in un tenant già esistente.
Lo scenario in cui spesso mi trovo a lavorare non consente un approccio del genere. Infrastrutture nate nel corso di diversi anni, con decine di servizi e configurazioni differenti. Applicare da un giorno all’altro una serie di regole bloccanti potrebbe riservarci qualche sorpresa.
Una strategia decisamente più prudente è partire con effetti come Audit o AuditIfNotExists. Prima osserviamo. Vediamo quali risorse non rispettano la regola, capiamo la dimensione del problema e soprattutto verifichiamo se la regola che abbiamo scritto rappresenta veramente quello che volevamo ottenere. Poi, eventualmente, iniziamo ad applicarla.
La governance è un processo iterativo. Soprattutto se si prende “un treno in corsa”.
Questo approccio secondo me è importante anche per un altro motivo. Una Policy non serve solamente per dire “no”. Può diventare uno strumento per capire quanto la situazione attuale sia distante dallo stato desiderato.
In altre parole: prima misuriamo il problema, poi lo risolviamo.
Quando una regola diventa un insieme di regole
Quando il numero di Policy cresce, gestirle singolarmente diventa rapidamente scomodo. Naming convention. Location consentite. Tag obbligatori. Configurazioni di networking. Requisiti di sicurezza.
Una Policy qui, una là e dopo qualche mese facciamo fatica perfino a capire quali regole costituiscano realmente lo standard aziendale.
Per questo Azure introduce il concetto di Initiative. Un’Initiative permette di raggruppare diverse Policy Definition e trattarle come un unico insieme coerente. Possiamo quindi definire, per esempio, un baseline aziendale e assegnarlo ad un determinato livello della nostra organizzazione. Il vantaggio non è solamente tecnico.
L’Initiative inizia a rappresentare un concetto comprensibile anche al di fuori della singola regola: questo è l’insieme delle policy che definisce lo standard aziendale.
Facciamo quindi un passo avanti, complicando leggermente il nostro esempio iniziale sui tag.
Nella nostra organizzazione potremmo decidere che ogni risorsa deve avere sia Environment sia CostCenter. Potremmo creare due Assignment indipendenti. Funzionerebbe.
Ma probabilmente quei due requisiti fanno parte dello stesso concetto: la nostra tagging baseline. È qui che diventano interessanti le Initiative: una Initiative raggruppa più Policy Definition e permette di gestirle e assegnarle come una singola entità.
Nel nostro caso, potremmo descrivere la nostra Initiative in questo modo:
[
{
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/871b6d14-10aa-478d-b590-94f262ecfa99",
"policyDefinitionReferenceId": "require-environment-tag",
"parameters": {
"tagName": {
"value": "Environment"
}
}
},
{
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/871b6d14-10aa-478d-b590-94f262ecfa99",
"policyDefinitionReferenceId": "require-costcenter-tag",
"parameters": {
"tagName": {
"value": "CostCenter"
}
}
}
]
Non abbiamo scritto due nuove Policy. Abbiamo riutilizzato due volte la stessa built-in, configurandola diversamente.
A questo punto possiamo creare la nostra Initiative aziendale con il seguente comando:
az policy set-definition create \
--name governance-tag-baseline \
--display-name "Governance - Tag baseline" \
--management-group "$MANAGEMENT_GROUP" \
--definitions tag-baseline.definitions.json \
--version 1.0.0
A questo punto la nostra baseline è diventata un concetto esplicito. Non stiamo assegnando una collezione casuale di Policy. Stiamo dicendo: queste sono le regole di tagging della nostra organizzazione.
Questa è per me la vera “governance” di un tenant.
E le Custom Policy?
Ovviamente arriverà il momento in cui le policy built-in non saranno sufficienti.
Supponiamo, per esempio, che non vogliamo semplicemente richiedere Environment. Vogliamo anche stabilire che i valori accettabili siano: dev, test e prod.
A questo punto possiamo decidere di creare una Custom Policy.
policy.rules.json:
{
"if": {
"anyOf": [
{
"field": "tags['Environment']",
"exists": "false"
},
{
"field": "tags['Environment']",
"notIn": "[parameters('allowedValues')]"
}
]
},
"then": {
"effect": "[parameters('effect')]"
}
}
policy.parameters.json:
{
"allowedValues": {
"type": "Array",
"metadata": {
"displayName": "Allowed Environment values",
"description": "Values accepted for the Environment tag"
},
"defaultValue": [
"dev",
"test",
"prod"
]
},
"effect": {
"type": "String",
"allowedValues": [
"Audit",
"Deny"
],
"defaultValue": "Audit"
}
}
Solitamente parametrizzo anche l’effetto intenzionalmente. Più la policy è parametrica più la rendo riutilizzabile. Questo mi permette, per esempio, di introdurre inizialmente la Policy in Audit e trasformarla successivamente in Deny, senza dover creare una definizione differente.
È anche coerente con una delle regole che cerco di seguire quando introduco Policy in ambienti esistenti: prima capire l’impatto, poi iniziare a bloccare.
Creare una Custom Policy con Azure CLI
Ora possiamo realmente creare la nostra definizione:
az policy definition create \
--name require-valid-environment-tag \
--display-name "Require a valid Environment tag" \
--description "Requires Environment and restricts its possible values" \
--mode Indexed \
--management-group "$MANAGEMENT_GROUP" \
--rules @policies/custom/require-environment/policy.rules.json \
--params @policies/custom/require-environment/policy.parameters.json \
--version 1.0.0
az policy definition create permette di specificare regole, parametri, modalità, Management Group e versione della definizione.
Possiamo quindi recuperare il suo resource ID:
POLICY_ID=$(az policy definition show \
--name require-valid-environment-tag \
--management-group "$MANAGEMENT_GROUP" \
--query id \
--output tsv)
e assegnarla:
az policy assignment create \
--name require-valid-environment-tag \
--scope "$SCOPE" \
--policy "$POLICY_ID" \
--params '{
"allowedValues": {
"value": ["dev", "test", "prod"]
},
"effect": {
"value": "Deny"
}
}' \
--enforcement-mode DoNotEnforce
L’ultima riga è importante.
La Policy ha effetto Deny, ma l’Assignment viene creato inizialmente con DoNotEnforce. In questo caso Azure continua a valutare la compliance, ma non applica l’effetto di enforcement. Quando siamo soddisfatti del risultato:
az policy assignment update \
--name require-valid-environment-tag \
--scope "$SCOPE" \
--enforcement-mode Default
Questa, secondo me, è una differenza importante rispetto al creare una Policy dal portale e premere Save. Stiamo iniziando a costruire un processo di delivery.
Dal codice al processo
A questo punto abbiamo fatto un bel passo avanti: siamo partiti da una regola abbastanza semplice, abbiamo visto che prima di creare una Custom Policy conviene verificare se Azure ne mette già a disposizione una built-in, abbiamo introdotto le Initiative per organizzare più regole e, quando le Policy esistenti non erano sufficienti, abbiamo creato la nostra definizione tramite Azure CLI.
Soprattutto, abbiamo smesso di considerare la Policy come qualcosa da configurare manualmente nel portale.
La nostra Policy adesso è descritta da file. Può essere versionata. Può essere modificata attraverso una Pull Request. Possiamo sapere chi l’ha cambiata, quando e perché.
Ma manca ancora un pezzo.
Negli esempi precedenti siamo stati noi a eseguire:
az policy definition create
e successivamente:
az policy assignment create
Funziona, ma se ci fermassimo qui avremmo semplicemente sostituito alcuni click sul portale con alcuni comandi eseguiti da terminale.
Non è ancora quello che intendo quando si parla di Policy as Code: se una Policy è codice, anche il suo ciclo di vita dovrebbe assomigliare a quello del codice che scriviamo tutti i giorni. Una modifica dovrebbe nascere in un branch, passare attraverso una Pull Request, essere validata automaticamente, distribuita inizialmente in maniera controllata e solo successivamente promossa negli ambienti in cui deve essere realmente applicata.
In altre parole, non dobbiamo automatizzare solamente il deploy della Policy. Dobbiamo automatizzare il suo ciclo di vita.
Ed è esattamente quello che faremo nel prossimo articolo. Perché una Policy è codice. E se è codice, possiamo finalmente iniziare a trattarla come tale.