Saltar al contenido

Temas

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.

IntermedioEn progreso 6 min de lectura1 oct 2026#iac#terraform#policy-as-code#shift-left#devsecops#aws

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?

IaC: el escaneo rompe el build antes de desplegar
Pull requestcambio .tfEscaneo IaCpolicy as codeRama mainmerge · deployCheckov · Trivy · cfn-guard · OPAFAIL · SG 0.0.0.0/0:22PASS · sin hallazgosmerge bloqueadomerge ✓SG :22 abiertoSG restringidoaplicar

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

s3.tf (inseguro)
resource "aws_s3_bucket" "adjuntos" {
bucket = "citas-adjuntos"
}
# Bloque público "temporal" para servir descargas
resource "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
}
}
s3.tf (corregido)
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:

Terminal
# Escanear el directorio de Terraform y fallar si hay hallazgos
checkov --directory infra/ --framework terraform --compact
# Trivy sobre configuración, fallando con severidad alta o crítica
trivy config --exit-code 1 --severity HIGH,CRITICAL infra/
# Política propia con Conftest sobre el plan en JSON
terraform show -json plan.out > plan.json
conftest 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:

policy/ssh.rego
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:

  1. 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.

  2. 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.

  3. 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.

  4. Gate previo al despliegue: una última comprobación antes de terraform apply, por si algo llegó a main por 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 config sobre é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.

Referencias#

Notas relacionadas

ESC
↑↓ para navegar↵ para abriresc para cerrar