IaC seguro y policy as code
Por qué la seguridad se aplica antes de desplegar (shift left), cómo escanear Terraform y CloudFormation con Checkov, Trivy, cfn-guard y OPA, y cómo romper el build ante una configuración insegura.
Cuando un bucket aparece público o un security group deja el puerto 22 abierto a todo Internet, el fallo casi nunca es que «alguien no sabía». El fallo es cuándo se detectó: en producción, con GuardDuty avisando a las tres de la mañana, en lugar de en el pull request donde se introdujo. Si tu infraestructura es códigoInfraestructura como códigoDefinir y aprovisionar infraestructura con ficheros de código versionados (Terraform, CloudFormation) en lugar de clics manuales en la consola., puedes revisarla como código: escanearla, aplicarle reglas y romper el build antes de que el recurso inseguro llegue a existir. Eso es shift left aplicado a la infraestructura.
Shift left: mover la seguridad hacia la izquierda#
Imagina el ciclo de vida de un cambio de izquierda a derecha: lo escribes, abres un pull request, pasa CI, se mergea, se despliega y corre en producción. Cuanto más a la derecha detectas un problema, más caro es arreglarlo y más tiempo ha estado expuesto. Shift left consiste en mover los controles hacia la izquierda: que la comprobación ocurra en el pull request, donde el autor tiene el contexto fresco y el recurso todavía no existe.
Con IaC esto es especialmente potente porque el plan es predecible. No tienes que esperar a desplegar para saber que un aws_security_group abre el 0.0.0.0/0: está escrito en el fichero. El escáner lee el mismo código que crearía el recurso y responde una pregunta objetiva: ¿esta configuración incumple alguna regla?
Paso 1 de 5Pull request
Un pull request con un cambio de Terraform entra al pipeline. Antes de desplegar nada, un paso de escaneo lo revisa.
Las herramientas: escáneres y policy as code#
Hay dos familias que conviene no confundir, aunque se solapen.
Escáneres con catálogo de reglas: traen cientos de comprobaciones listas para usar.
- Checkov (Prisma Cloud/Bridgecrew): analiza Terraform, CloudFormation, Kubernetes, Dockerfile y más. Cada regla tiene un ID (
CKV_AWS_*) y se puede silenciar de forma puntual. - Trivy (Aqua Security): empezó como escáner de vulnerabilidades de imágenes y hoy también hace misconfiguration scanning de IaC con el motor de políticas integrado. Útil porque unifica vulnerabilidades, secretos y configuración en una sola herramienta.
- cfn-guard (AWS): valida plantillas de CloudFormation (y otros JSON/YAML) contra reglas escritas en un DSL propio. Es la pieza nativa si vives en CloudFormation.
Policy as code genérico: en lugar de un catálogo cerrado, escribes tus reglas.
- OPA/Rego + Conftest: Open Policy Agent evalúa cualquier estructura de datos (el plan de Terraform en JSON, un manifiesto de Kubernetes) contra políticas en lenguaje Rego. Conftest es el envoltorio que lo aplica a ficheros de configuración en el pipeline. Es la opción cuando necesitas una regla que ningún catálogo trae: «todo recurso debe tener la etiqueta
equipo», «solo se permiten regiones de la UE».
En la práctica, un pipeline maduro combina ambas: un escáner con catálogo para las malas configuraciones conocidas y policy as codePolicy as codeReglas de seguridad y cumplimiento escritas como código evaluable y automatizable dentro del pipeline (OPA/Rego, Conftest, Checkov). propia para las reglas específicas de tu organización.
Romper el build ante una configuración insegura#
El objetivo no es informar de que hay un problema: es impedir que el cambio avance. Una comprobación que solo imprime avisos se ignora a la tercera semana. La que rompe el build (código de salida distinto de cero) obliga a decidir: se corrige o se documenta una excepción explícita.
Mira el caso más común en Citas: el bucket de adjuntos y un security group de la API. Compara la versión que suele colarse con la corregida.
Inseguro: público y abierto
Corregido: privado y restringido
resource "aws_s3_bucket" "adjuntos" { bucket = "citas-adjuntos"}
# Bloque público "temporal" para servir descargasresource "aws_s3_bucket_public_access_block" "adjuntos" { bucket = aws_s3_bucket.adjuntos.id block_public_acls = false block_public_policy = false ignore_public_acls = false restrict_public_buckets = false}
resource "aws_security_group" "api" { name = "citas-api" ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] # SSH abierto a Internet }}resource "aws_s3_bucket" "adjuntos" { bucket = "citas-adjuntos"}
resource "aws_s3_bucket_public_access_block" "adjuntos" { bucket = aws_s3_bucket.adjuntos.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true}
resource "aws_kms_key" "adjuntos" { enable_key_rotation = true }
resource "aws_s3_bucket_server_side_encryption_configuration" "adjuntos" { bucket = aws_s3_bucket.adjuntos.id rule { apply_server_side_encryption_by_default { kms_master_key_id = aws_kms_key.adjuntos.arn sse_algorithm = "aws:kms" } }}Sobre el primer fichero, Checkov dispara varios hallazgos a la vez: CKV_AWS_18/CKV2_AWS_6 por el acceso público de S3, ausencia de cifrado y el CKV_AWS_24 por el SSH abierto. En CI, el escáner devuelve un código de error y el job falla:
# Escanear el directorio de Terraform y fallar si hay hallazgoscheckov --directory infra/ --framework terraform --compact
# Trivy sobre configuración, fallando con severidad alta o críticatrivy config --exit-code 1 --severity HIGH,CRITICAL infra/
# Política propia con Conftest sobre el plan en JSONterraform show -json plan.out > plan.jsonconftest test plan.json --policy policy/Y una política propia en Rego que niega cualquier 0.0.0.0/0 en puertos de administración:
package main
deny[msg] { sg := input.resource_changes[_] sg.type == "aws_security_group" rule := sg.change.after.ingress[_] rule.cidr_blocks[_] == "0.0.0.0/0" rule.from_port <= 22 rule.to_port >= 22 msg := sprintf("SG %s abre SSH a Internet", [sg.address])}Dónde encaja en el pull request#
El escaneo vive en el pull request, no en un informe nocturno que nadie lee. El flujo que funciona:
-
Pre-commit (opcional, rápido): un hook local pasa el escáner sobre los ficheros tocados. Atrapa lo evidente antes de empujar y ahorra vueltas de CI.
-
CI en el pull request: el job ejecuta Checkov/Trivy y Conftest sobre el plan. Si hay hallazgos por encima del umbral, falla y bloquea el merge mediante una branch protection rule que exige que el check pase.
-
Comentario en el PR: el resultado se publica como anotación o comentario, con el ID de regla y el fichero/línea, para que corregir sea inmediato.
-
Gate previo al despliegue: una última comprobación antes de
terraform apply, por si algo llegó amainpor otra vía. La seguridad en el PR reduce el ruido aquí, pero no sustituye a esta red.
Esto encaja con el principio de mínimo privilegio: el rol que ejecuta terraform apply en CI tampoco debería ser administrador. Cómo ese rol obtiene credenciales sin llaves de larga vida es el tema de secretos y CI/CD con OIDC.
Tu turno
Añade un gate de IaC a un repositorio tuyo
Elige un repositorio con Terraform o CloudFormation de tu trabajo o tu lab.
- Ejecuta Checkov o
trivy configsobre él en local. ¿Cuántos hallazgos salen? Ordénalos por severidad. - Elige un hallazgo alto (bucket público, SG abierto, cifrado ausente) y corrígelo en un pull request.
- Escribe una política propia en Rego/Conftest que represente una regla de tu organización que ningún catálogo cubra.
- Configura el check para que falle el pipeline y protege la rama para que el merge lo exija.
| Campo | Tu respuesta |
|---|---|
| Repositorio | |
| Hallazgos (total / altos) | |
| Hallazgo corregido | |
| Regla propia (en una frase) | |
| ¿El build falla ahora? |
Reto
El escáner no marca nada, pero sabes que el plan crea un recurso inseguro. ¿Qué suele pasar?
▸Mostrar solución
Casi siempre estás escaneando el código y no el plan resuelto. Un módulo, una variable o
un for_each puede introducir la configuración insegura en un valor que el HCL no muestra
literalmente. Genera el plan en JSON (terraform show -json) y pásaselo al escáner o a Conftest:
evaluarás lo que de verdad se va a crear.
Reto
¿Por qué «romper el build» es mejor control que un informe de hallazgos?
▸Mostrar solución
Porque cambia el comportamiento por defecto. Un informe deja la decisión en «cuando tengamos tiempo», y la deuda se acumula. Un build roto convierte la corrección en el camino de menor resistencia: para avanzar hay que arreglar el problema o documentar una excepción explícita y revisable. El control que no tiene consecuencias se ignora.
En resumen#
- Shift left con IaC significa detectar la mala configuración en el pull request, donde el recurso aún no existe y el autor tiene el contexto.
- Combina escáneres con catálogo (Checkov, Trivy, cfn-guard) para lo conocido y policy as code (OPA/Rego + Conftest) para tus reglas propias.
- Escanea el plan resuelto en JSON, no solo el código, para ver lo que de verdad se va a crear.
- El control debe romper el build y bloquear el merge; las excepciones se silencian en la línea concreta, con justificación y caducidad.
- Encaja en el PR (pre-commit → CI → comentario → gate previo a
apply) y refuerza mínimo privilegio y reducción de superficie.