Módulo 4: Policy As Code With Conftest

2. Qué es Open Policy Agent y Rego

Descripción

Open Policy Agent (OPA) es un motor de políticas de código abierto, independiente de cualquier nube o herramienta específica: recibe un dato estructurado (JSON, casi siempre), lo evalúa contra reglas que tú escribiste, y devuelve un veredicto. Rego es el lenguaje en el que se escriben esas reglas — declarativo, no imperativo: en vez de describir cómo llegar a una conclusión paso a paso, describes qué condiciones hacen que una conclusión sea verdadera. conftest —la herramienta que instalas en la lección 3— es una capa delgada sobre OPA, pensada específicamente para evaluar archivos de configuración: YAML, JSON, HCL, y el terraform plan en su forma JSON que vas a usar desde la lección 5 en adelante.

Conexión con el módulo

Esta lección es puramente conceptual — la única de todo el módulo sin un comando ejecutado —, y existe para que cuando la lección 4 te ponga a escribir tu primera regla real, no estés aprendiendo la sintaxis y el concepto al mismo tiempo. Aquí separas las dos preguntas: qué es una política declarativa (esta lección), de cómo se escribe una en la sintaxis exacta que el motor actual exige (lección 4).


Analogía: el inspector que revisa el plano antes del primer ladrillo

Un inspector de construcción tiene dos formas posibles de trabajar. La primera: visita el edificio ya terminado, un año después, y señala qué normas de seguridad se violaron — la escalera de incendios es demasiado angosta, el cableado no está a la distancia reglamentaria del agua. Ese trabajo tiene valor, pero llega tarde: corregirlo ahora significa romper una pared que ya se construyó. La segunda forma: el inspector revisa el plano, antes de que exista un solo ladrillo, y rechaza el permiso de construcción si el plano mismo viola una norma. Ese es un trabajo estrictamente más barato — corregir un plano cuesta un lápiz; corregir un edificio cuesta una demolición parcial.

Policy-as-code con conftest/OPA es el segundo inspector, aplicado a infraestructura. El terraform plan es el plano — la descripción completa y precisa de qué se va a construir, antes de que un solo recurso exista de verdad —, y una política Rego es la norma que ese plano tiene que cumplir antes de que nadie apruebe el apply. Esto es, literalmente, lo que la palabra "preventivo" significa en el vocabulario de esta guía: no es que el guardrail actúe rápido después del hecho, es que actúa antes de que el hecho —el recurso creado, modificado o destruido— exista en absoluto. Vas a ver esta distinción una vez más, con nombre explícito, en el Módulo 7 de esta guía, cuando compares controles preventivos contra controles detectivos como CloudTrail — el inspector que revisa el edificio ya construido.


Qué hace OPA, en términos exactos

OPA no sabe nada de Terraform, ni de Kubernetes, ni de ningún sistema específico — y esa ignorancia es, a propósito, su característica central. OPA hace una sola cosa: recibe un documento JSON como input, lo evalúa contra un conjunto de reglas Rego, y produce un resultado. Quién genera ese JSON, y qué formato tenía antes de convertirse en JSON, le es completamente indiferente. Esto es lo que hace que la misma herramienta que vas a usar en este módulo sobre un terraform plan sea, en otros contextos —fuera del alcance de esta guía—, la misma herramienta que equipos usan para validar manifiestos de Kubernetes, respuestas de una API, o configuración de un firewall: el motor no cambia, solo cambia el input y las reglas que le escribes.

conftest es la capa que hace este motor genérico utilizable desde la línea de comandos, sin escribir ningún código Go ni levantar ningún servidor: toma un archivo (.yaml, .json, o el .json que produce terraform show -json), lo carga como input, evalúa las reglas Rego de un directorio que le indiques, y te devuelve PASS o FAIL con el mensaje exacto de cada regla que se violó. Es, en espíritu, lo mismo que un framework de tests unitarios —pytest, jest— pero en vez de probar que tu código hace lo que dice, prueba que tu configuración cumple las reglas que decidiste que son obligatorias.


La anatomía mínima de una regla Rego

Toda política de este módulo, sin excepción, sigue la misma forma: una regla llamada deny, que agrega un mensaje a un conjunto cuando se cumple una condición. En la sintaxis que las versiones actuales del motor exigen —la que vas a confirmar tú mismo, corriendo el motor real, en la lección 4—:

package main

deny contains msg if {
    input.environment == "production"
    input.debug == true
    msg := "production environment must not run with debug mode enabled"
}

Cinco piezas, cada una con un trabajo concreto:

  • package main — todo archivo .rego pertenece a un paquete; conftest busca, por convención, las reglas del paquete main (o conftest, según cómo lo invoques) para decidir qué evaluar. Todas las políticas de este módulo usan main.
  • deny contains msg if { ... } — declara que deny es un conjunto (contains, no un único valor), y que cada elemento de ese conjunto se agrega solo if el cuerpo que sigue es verdadero. Si el cuerpo nunca se cumple, el conjunto deny queda vacío — y un deny vacío es, para conftest, un PASS.
  • input.environment, input.debuginput es la variable especial que contiene el documento completo que estás evaluando (el YAML o el JSON de turno), navegado como si fuera un diccionario anidado. No la declaras en ningún lado; el motor la puebla automáticamente con el archivo que le pasaste.
  • Las condiciones dentro de { } — cada línea es una condición que tiene que ser verdadera para que la regla dispare. Rego evalúa esto como una conjunción implícita: todas las líneas tienen que cumplirse a la vez, como si cada salto de línea fuera un AND. No hay ningún if/else explícito porque no hace falta — si ninguna condición se cumple, la regla simplemente no aporta nada al conjunto deny.
  • msg := "..." — el mensaje que conftest va a mostrarte cuando esta regla dispare. Este es, en la práctica, la parte más importante de cualquier política real: un deny sin un mensaje claro es una política que falla sin decirte por qué.

Esta forma —deny contains msg if { condiciones; msg := "..." }— es la única que vas a necesitar en todo este módulo. Las tres políticas de la lección 6 y 7 son, estructuralmente, la misma forma repetida tres veces, con condiciones distintas sobre un input distinto (el terraform plan en JSON, en vez de un YAML de prueba).


Rego es declarativo, no imperativo — y eso cambia cómo lo lees

Si vienes de Python o JavaScript, la primera sorpresa de Rego es que no hay ningún return, ningún bucle for explícito que acumule un resultado, ningún if/elif/else con ramas. Una regla Rego no calcula un resultado paso a paso — declara bajo qué condiciones un hecho es verdadero, y el motor decide, evaluando todas las combinaciones posibles, si esas condiciones se cumplen para el input que le diste. Cuando más adelante (lección 7) necesites revisar cada Statement dentro de una política IAM, no vas a escribir un for statement in policy.Statements: como en Python — vas a escribir some statement in policy.Statement, que le dice al motor "existe al menos un elemento de este conjunto para el cual lo que sigue es verdadero", y el motor se encarga de probar cada elemento por ti. Esta diferencia de paradigma es, con distancia, lo que más cuesta al empezar con Rego — y es exactamente lo que la lección 4 te deja practicar con un ejemplo mínimo, antes de que la lección 7 te pida iterar sobre una estructura real y anidada.


Sentinel, por contraste: el motor comercial que no vas a usar aquí

HashiCorp tiene su propio motor de políticas, llamado Sentinel, nativo de HCP Terraform (la plataforma SaaS de HashiCorp) y de Terraform Enterprise. Conceptualmente resuelve el mismo problema que OPA/conftest —evaluar un plan contra reglas antes de aplicarlo— y sí existe un binario de Sentinel descargable gratis. La diferencia real, la que importa para esta guía, no es el precio del binario: es que la integración de Sentinel con Terraform está diseñada alrededor de HCP Terraform — el import tfplan/v2 que una política Sentinel usa para leer un plan depende de los datos ("mocks") que HCP Terraform genera automáticamente cuando corre tu plan en su propia infraestructura remota. No hay un camino simple, documentado y $0 para apuntar el binario de Sentinel, de forma standalone, a un terraform show -json cualquiera generado en tu propia máquina — que es, precisamente, lo que conftest hace de fábrica, sin ninguna plataforma de por medio, desde la lección 5 de este módulo.

Esta guía nombra Sentinel aquí, una sola vez, por la misma razón por la que nombra otras herramientas comerciales en otros módulos: para que sepas que existe, qué problema resuelve, y por qué esta guía específicamente no lo construye — no por desconocimiento, sino porque el camino $0, ejecutable, verificable en tu propia terminal, es conftest. Si algún día trabajas en un equipo con HCP Terraform de pago, vas a reconocer el mismo problema —política que evalúa un plan antes del apply— resuelto con una sintaxis distinta y una plataforma distinta detrás.


Errores comunes

Esperar que Rego tenga un return o un flujo de control imperativo (de paradigma). Qué pasa: alguien, viniendo de un lenguaje imperativo, busca dónde "devolver" el resultado de una regla, o escribe un if/else explícito dentro del cuerpo de un deny. Cómo detectarlo: si tu primer borrador de una regla Rego tiene la forma de una función que "calcula y devuelve" en vez de una condición que "declara cuándo es verdad". Cómo corregirlo: una regla Rego no calcula nada — declara bajo qué condiciones algo pertenece a un conjunto (deny, en este módulo). Todas las líneas dentro de { } son una conjunción (AND implícito); si necesitas una disyunción (OR), la forma correcta en Rego es escribir dos reglas separadas con el mismo nombre — cada una es una forma alternativa de que la condición se cumpla, no una rama de un if.

Confundir input con datos que hay que declarar o importar (de sintaxis). Qué pasa: alguien busca, dentro del propio archivo .rego, dónde se define la variable input, o intenta asignarle un valor. Cómo detectarlo: si tu archivo Rego tiene una línea como input := {...}. Cómo corregirlo: input es una variable especial, poblada automáticamente por conftest con el contenido del archivo que le pasaste en la línea de comandos — nunca se declara ni se asigna dentro de la política. La lección 4 te muestra esto en vivo: el mismo archivo .rego, sin cambiar una línea, evalúa input distinto según qué YAML le pases.

Pensar que Rego y Sentinel son intercambiables porque resuelven "el mismo problema" (de alcance). Qué pasa: alguien, en un entrevista técnica o en un proyecto real, asume que aprender Rego significa que ya sabe escribir Sentinel, o viceversa. Cómo detectarlo: si tu respuesta a "¿conoces Sentinel?" es "sí, es lo mismo que Rego". Cómo corregirlo: resuelven el mismo problema (evaluar un plan contra reglas antes de aplicarlo), pero son dos lenguajes completamente distintos, con sintaxis distinta y ecosistemas de herramientas distintos — Rego es de OPA (Cloud Native Computing Foundation, código abierto, sin dueño comercial único), Sentinel es propietario de HashiCorp. Saber uno te da el concepto, no la sintaxis del otro.


Ejercicios

Ejercicio 1 — Reescribe, en prosa, qué haría esta regla Rego sin ejecutarla. Antes de la lección 4, sin correr ningún comando, lee esta regla y explica en una frase qué condición exacta la haría disparar:

deny contains msg if {
    input.action == "delete"
    input.resource_name == "Shipments"
    msg := "cannot delete Shipments"
}
Ver solución

Esta regla dispara —agrega un mensaje al conjunto deny— únicamente cuando el documento input tiene, a la vez, un campo action con el valor exacto "delete" y un campo resource_name con el valor exacto "Shipments". Si cualquiera de las dos condiciones no se cumple —por ejemplo, action es "create", o resource_name es "AppServerRole"—, la regla no aporta nada al conjunto, y conftest reporta esa política como PASS para ese input. Este es, en espíritu simplificado, exactamente el mecanismo que la lección 6 de este módulo va a construir sobre el terraform plan real.

Ejercicio 2 — Explica la diferencia entre "declarativo" e "imperativo" con tus propias palabras, usando el ejemplo del inspector. Sin repetir el texto de esta lección, explica en dos o tres frases por qué Rego se describe como declarativo, usando la analogía del inspector de construcción de esta misma lección.

Ver solución

Una explicación razonable: un enfoque imperativo sería un inspector que sigue una lista de pasos ("primero mide la escalera, después revisa el cableado, después calcula si cumple") — describe cómo llegar a la conclusión. Un enfoque declarativo, como Rego, es un inspector que solo tiene una lista de condiciones que el plano debe cumplir ("la escalera debe medir al menos X", "el cableado debe estar a Y metros del agua") — no le importa en qué orden se verifican, ni cómo se llega a esa conclusión, solo si las condiciones son verdaderas o falsas para el plano que tiene enfrente. Rego funciona igual: una regla no calcula un resultado paso a paso, declara qué combinación de hechos sobre el input hace que esa regla sea verdadera.

Ejercicio 3 — Decide si Sentinel sería una opción viable para un equipo sin presupuesto de herramientas. Un compañero de equipo, sin presupuesto para HCP Terraform de pago, te pregunta si podría usar Sentinel en vez de conftest para este mismo proyecto. ¿Qué le responderías, y por qué?

Ver solución

La respuesta honesta reconoce el matiz sin exagerar: "Existe un binario de Sentinel gratis para descargar, así que técnicamente no es un secreto comercial, pero su integración real con Terraform depende de los datos ('mocks') que HCP Terraform genera automáticamente cuando corre un plan en su propia plataforma — no hay una forma simple y documentada de apuntar Sentinel, standalone, a un terraform show -json cualquiera, sin pasar por esa plataforma. conftest, en cambio, está diseñado desde el inicio para consumir cualquier archivo JSON local, sin ninguna plataforma de por medio — es la opción con el camino $0 más directo para este caso exacto." Si tu respuesta distingue "el binario es gratis" de "la integración con Terraform requiere la plataforma completa", tienes la distinción correcta.


Resumen y siguiente paso

En esta lección viste qué es Open Policy Agent —un motor de políticas genérico, indiferente al formato de origen de su input— y qué es Rego —el lenguaje declarativo en el que se escriben sus reglas—, con la forma exacta (deny contains msg if { ... }) que vas a usar en cada política de este módulo. Viste, con la analogía del inspector que revisa el plano antes del primer ladrillo, por qué este enfoque se llama preventivo. Y nombraste Sentinel, el motor comercial de HashiCorp, con la distinción precisa de por qué esta guía no lo construye: no por falta de un binario gratis, sino porque su integración real con Terraform vive dentro de una plataforma de pago, mientras que conftest resuelve el mismo problema sin ninguna plataforma de por medio.

Antes de avanzar deberías poder: escribir de memoria la forma mínima de una regla deny; explicar por qué Rego es declarativo, no imperativo, con tus propias palabras; y distinguir qué hace Sentinel de qué hace OPA/conftest, sin confundir "tiene un binario gratis" con "tiene un camino $0 para este caso de uso".

La lección 3 deja la teoría atrás: vas a instalar conftest de verdad, en tu propia máquina, y confirmar la versión exacta del motor que vas a usar en el resto de este módulo.

Recursos

  1. Open Policy Agent — Documentación oficial — el motor sobre el que se construye conftest, la base técnica de todo este módulo.
  2. Open Policy Agent — Policy Language (Rego) — la referencia completa de la sintaxis de Rego, incluidas las palabras clave if y contains que vas a confirmar en vivo en la lección 4.
  3. Conftest — Documentación oficial — la capa que convierte OPA en una herramienta de línea de comandos, instalada en la lección 3.
  4. HashiCorp — Sentinel — el motor comercial nombrado por contraste en esta lección.
  5. terraform-and-iac-guide, Módulo 8, lección 4 — el punto exacto donde esa guía nombró conftest sin construirlo, la promesa que este módulo cumple.