Il 4 febbraio 2021 la Cloud Native Computing Foundation (CNCF) ha promosso Open Policy Agent (OPA) a progetto graduated, il grado di maturità più alto che la fondazione assegni. La data dice meno del fatto tecnico che la accompagna: un motore di decisione generico, scritto in Go e rilasciato sotto Apache 2.0, ha raccolto abbastanza adozione in produzione da rendere la sua semantica una base stabile su cui costruire.
Contesto
OPA nasce in Styra, per mano di Tim Hinrichs e Torin Sandall, entra nella sandbox CNCF nell’aprile 2018, passa a incubating circa un anno dopo e arriva alla graduation con due audit di sicurezza esterni alle spalle, il badge CII Best Practices e una governance definita. L’annuncio cita adozioni come Goldman Sachs, Netflix, Pinterest, T-Mobile e Atlassian, e un sondaggio su oltre 150 organizzazioni in cui il 91% dichiara di usare OPA in qualche fase, dalla QA alla produzione.
Il problema che il progetto affronta torna sempre uguale. Le decisioni di autorizzazione e compliance finiscono sparse nel codice applicativo: un if user.role == "admin" qui, una query SQL sugli ACL là, un controllo sui gruppi LDAP altrove, più la logica propria di ogni strumento — RBAC di Kubernetes, regole di una pipeline CI, validazione di un piano Terraform. Cambiare una regola vuol dire mettere mano a N punti in M linguaggi, e nessuno ha sott’occhio l’insieme di cosa sia davvero permesso.
Architettura
OPA stacca la decisione dall’applicazione. Il servizio che deve decidere — un’API, un admission controller, un gateway — manda a OPA un documento JSON come input e riceve in risposta il risultato della valutazione delle policy. La logica vive fuori dal servizio, scritta in Rego, un linguaggio dichiarativo che prende da Datalog.
package http.authz
default allow = false
allow {
input.method == "GET"
input.path = ["public", _]
}
allow {
input.user.role == "admin"
}
Quasi sempre OPA gira accanto al servizio che lo interroga — come sidecar nello stesso pod, o come daemon a livello di host — così la chiamata di valutazione resta locale e a bassa latenza. L’interrogazione passa per l’API REST: POST /v1/data/http/authz/allow con il documento di input nel body restituisce la decisione. Le policy e i dati di contesto si possono caricare in modo statico, oppure distribuire con il meccanismo dei bundle: a intervalli regolari OPA scarica da un endpoint HTTP un archivio di Rego e dati e lo applica senza riavvio.
Il terzo pezzo è il decision log. Ogni decisione presa si può registrare e spedire a lotti a un servizio di raccolta, insieme all’input, al risultato e alla policy che l’ha prodotta. Per audit e troubleshooting cambia molto: alla domanda «perché a quell’ora quell’utente ha potuto fare quell’operazione» si risponde con un record preciso, invece di ricostruire il fatto a posteriori dai log applicativi.
L’ecosistema attorno al motore è dove succede gran parte dell’integrazione vera. Gatekeeper porta OPA dentro Kubernetes come admission controller ed espone le policy tramite la Custom Resource ConstraintTemplate, al posto di Rego grezzo nei manifest. conftest esegue policy Rego su file statici — manifest Kubernetes, Dockerfile, output di terraform plan — ed è fatto per il gating in pipeline. Il plugin OPA-Envoy intercetta le richieste come filtro di autorizzazione esterno (ext_authz).
Punto critico
Rego è dove OPA si allontana dai sistemi a regole più comuni. È un linguaggio di interrogazione su documenti, non un linguaggio imperativo travestito. Le regole non girano in sequenza: una regola con lo stesso nome ripetuta più volte è una disgiunzione, vera se almeno un blocco è soddisfatto, e l’iterazione si scrive con variabili libere che il motore unifica contro i dati, non con cicli for.
Il modello rende bene l’autorizzazione su dati strutturati e diventa ostico per chi si aspetta la semantica di un linguaggio general-purpose. Due righe sintatticamente valide possono dire cose lontane tra loro, e il fallimento di una regola — undefined — non è un errore ma un valore, che si propaga in modi che chi scrive deve tenere a mente. Quel default allow = false non è cosmesi: senza, una allow non definita non vale false, resta indefinita, e a valle qualcuno deve decidere come trattarla.
In pratica le policy vanno testate come codice. OPA include un test runner che valuta Rego contro input di esempio e verifica le asserzioni; opa test su una cartella di file _test.rego pesa quanto la pipeline che li distribuisce. Una policy non testata che torna undefined dove ci si aspettava false può aprire un accesso senza che niente sollevi un errore.
Implicazioni
Staccare la policy dal codice ha un effetto organizzativo prima che tecnico. Quando la regola di autorizzazione è un bundle versionato, distribuito e logato, il suo ciclo di vita si scolla da quello del servizio. Si cambia chi può fare cosa senza ridistribuire l’applicazione, e a un auditor si mostra la regola in vigore a una certa data insieme al log delle decisioni che ha prodotto. Negli ambienti con obblighi di compliance — finanza, sanità, pubblica amministrazione — è qui il valore concreto: il controllo diventa un artefatto ispezionabile.
Il prezzo è un componente in più sul percorso della richiesta e una superficie di configurazione nuova da presidiare. La distribuzione dei bundle va resa affidabile — cosa succede se OPA non riesce a scaricare l’ultimo bundle? — la latenza della valutazione va misurata sotto carico, e il team deve imparare Rego abbastanza da fidarsi delle proprie regole.
Limiti
OPA decide, non applica. La decisione che allow vale false ha effetto solo se il servizio chiamante la rispetta: il punto di enforcement resta nel codice applicativo o nel proxy, e un’integrazione che si dimentica di controllare la risposta lascia la policy senza presa.
Il modello a bundle apre una finestra di consistenza: tra la pubblicazione di una nuova policy e il momento in cui ogni istanza OPA l’ha scaricata, decisioni diverse possono poggiare su versioni diverse delle regole. Per molti casi va bene così; per altri va dimensionata con attenzione.
Infine, «policy as code» sposta la complessità, non la cancella. La logica frammentata nel codice diventa logica concentrata in Rego: più ispezionabile, ma anche un unico punto dove un errore tocca tutto ciò che interroga quel motore. Conviene adottarlo dove la frammentazione era già il problema, meno dove bastava un controllo locale ben isolato.
https://www.cncf.io/announcements/2021/02/04/cloud-native-computing-foundation-announces-open-policy-agent-graduation/ https://www.cncf.io/blog/2020/08/13/introducing-policy-as-code-the-open-policy-agent-opa/ https://www.openpolicyagent.org/docs/integration https://www.openpolicyagent.org/docs/rest-api https://www.noze.it/insights/opa-open-policy-agent/
Immagine di copertina: Logo della Cloud Native Computing Foundation: simbolo geometrico azzurro e ciano a frecce intrecciate in un quadrato, accanto alla… — logo di The Linux Foundation, CC0 — https://commons.wikimedia.org/wiki/File:Cloud_Native_Computing_Foundation_logo.png