Plantilla de Revisión de Infrastructure as Code
Una plantilla para revisar código de infraestructura Terraform y CloudFormation.
Visión General
El código de infraestructura es software, y merece la misma disciplina de revisión que el código de aplicación — un solo security group mal configurado o una política IAM demasiado permisiva puede exponer todo tu entorno. Esta plantilla te da un proceso de revisión estructurado para cambios de Terraform, CloudFormation, Pulumi o Ansible, para que nada peligroso se cuele un viernes por la tarde.
Cuándo Usar
Usa este recurso cuando:
- Agregues un nuevo módulo de Terraform o stack de CloudFormation a producción
- Revises pull requests que modifican infraestructura
- Audites código de infraestructura existente por problemas de seguridad o costos
Solución
# Revisión de Infrastructure as Code: `<Módulo / Stack>`
## 1. Metadatos del Cambio
| Campo | Valor |
|-------|-------|
| Módulo / Stack | `nombre` |
| Herramienta | `Terraform / CloudFormation / Pulumi / Ansible` |
| Entorno | `dev / staging / prod` |
| Ticket | `JIRA-1234` |
| Autor | `@autor` |
| Revisor | `@revisor` |
| Nivel de Riesgo | `Bajo / Medio / Alto / Crítico` |
## 2. Análisis Estático
- [ ] `terraform validate` o `cfn-lint` pasa sin errores
- [ ] `terraform plan` o `change set` ha sido revisado para eliminaciones inesperadas
- [ ] Escaneo de seguridad (Checkov, tfsec, cfn-nag) tiene cero hallazgos HIGH/CRITICAL
- [ ] Estimación de costo proporcionada para nuevos recursos (Infracost o manual)
- [ ] Bloqueo de archivo de estado configurado para Terraform
- [ ] Configuración de backend usa un store de estado remoto y cifrado
## 3. Revisión de Seguridad
| Check | Aprobado / Fallido | Notas |
|-------|-------------------|-------|
| Sin secretos hardcodeados en código o variables | | |
| Roles IAM / RBAC de mínimo privilegio | | |
| Security groups restringen ingress a CIDRs conocidos | | |
| Cifrado en reposo habilitado para almacenamiento | | |
| Cifrado en tránsito forzado (TLS 1.2+) | | |
| Acceso público deshabilitado por defecto | | |
| Logging habilitado para todos los data planes | | |
| WAF / protección DDoS para endpoints públicos | | |
## 4. Confiabilidad y Operaciones
| Check | Aprobado / Fallido | Notas |
|-------|-------------------|-------|
| Límites / cuotas de recursos verificados | | |
| Health checks y auto-recovery configurados | | |
| Redundancia multi-AZ o multi-región donde se requiere | | |
| Política de backup / snapshot definida | | |
| Monitoreo y alertas incluidos | | |
| Shutdown graceful / draining para servicios stateful | | |
| Idempotencia verificada: re-ejecución no produce cambios | | |
## 5. Costo y Eficiencia
| Check | Aprobado / Fallido | Notas |
|-------|-------------------|-------|
| Instancias right-sized (no default / máximo) | | |
| Capacidad reservada o savings plans considerados | | |
| Recursos no utilizados removidos en este cambio | | |
| Políticas de ciclo de vida de almacenamiento definidas | | |
| Costos de transferencia de datos estimados | | |
## 6. Documentación
- [ ] README actualizado con inputs, outputs y ejemplo de uso
- [ ] Architecture Decision Record (ADR) incluido para cambios importantes
- [ ] Runbook actualizado para nuevos procedimientos operacionales
- [ ] Playbooks de alertas de on-call cubren nuevas señales de monitoreo
## 7. Plan de Rollback
| Escenario | Acción de Rollback | Tiempo para Completar |
|-----------|-------------------|----------------------|
| Falla de despliegue | Re-aplicar el estado anterior o revertir el merge — evita `destroy -target` en recursos stateful | 15 min |
| Regresión de rendimiento | Revertir a imagen anterior / escalar arriba | 10 min |
| Incidente de seguridad | Deshabilitar acceso público + revocar claves | 5 min |
Explicación
Revisar infraestructura no es lo mismo que revisar código de aplicación — el radio de explosión es mayor. Un bug en código de aplicación afecta un pod; un bug en Terraform puede borrar una base de datos o dejarla expuesta en internet. Por eso la plantilla divide la revisión en tres pasadas: análisis estático (los chequeos automatizados), revisión de seguridad (criterio humano) y preparación operacional (¿puedes ejecutarlo y recuperarte cuando falle?). El plan de rollback tampoco es opcional — cada cambio de infra tiene que ser reversible dentro del RTO del servicio que soporta.
El flujo es este:
Cómo Adoptar Esta Plantilla
No pegues la checklist entera en cada PR y lo des por hecho — calibra el nivel de riesgo. Un módulo nuevo o cualquier cosa que toque estado de prod merece la revisión completa de principio a fin. Un cambio de tags o un ajuste fuera de prod solo necesita la sección de análisis estático más una pasada rápida de seguridad; una revisión pesada para trivialidades solo enseña a la gente a aprobar sin mirar.
Un despliegue que funciona: mete primero el análisis estático y el escaneo de seguridad en el CI, construye el hábito de revisar el plan durante un par de semanas, y después haz obligatoria la checklist completa para cambios de producción. Si tu equipo ya usa un proceso de revisión genérico (mira la checklist de code review), mantén las dos — aquella caza bugs de lógica, esta caza errores de radio de explosión.
Variantes
| Herramienta | Análisis Estático | Escaneo de Seguridad | Gestión de Estado |
|---|---|---|---|
| Terraform | terraform validate, fmt | Checkov, tfsec, Terrascan | Backend S3 remoto + bloqueo |
| CloudFormation | cfn-lint, cfn-guard | cfn-nag, Checkov | Stack sets + drift detection |
| Pulumi | pulumi preview | Checkov | Estado Pulumi Cloud |
| Ansible | ansible-lint, syntax-check | Roles de hardening de Ansible | Git + AWX / Tower |
Lo que funciona
Algunas prácticas que hacen que estas revisiones funcionen en equipos reales:
- Ejecuta análisis estático en CI/CD antes de que un humano vea el pull request
- Requiere dos aprobaciones para cambios de infraestructura en producción, no una
- Revisa el diff de
terraform plan, no solo el código; los planes revelan cambios destructivos - Separa archivos de estado por entorno; nunca compartas estado de prod y dev
- Usa versionado de módulos; fija versiones de provider y módulo para evitar actualizaciones sorpresa
Errores Comunes
- Revisar solo el diff de código e ignorar la salida de
terraform plan - Hardcodear secretos en lugar de usar un gestor de secretos (Vault, AWS Secrets Manager)
- Usar
countofor_eachen recursos stateful sin considerar pérdida de datos al destruir - Olvidar actualizar documentación cuando la infraestructura cambia
- Ejecutar
terraform applylocalmente en lugar de a través de un pipeline CI/CD con logging de auditoría
Soluciones Avanzadas
Escaneo automatizado de IaC con Checkov en CI/CD
Integra el escaneo de infraestructura en tu pipeline para detectar problemas antes de la revisión:
# .github/workflows/terraform-review.yml
name: Terraform Security Scan
on:
pull_request:
paths:
- "terraform/**"
- "infrastructure/**"
jobs:
checkov-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bridgecrewio/checkov-action@v12
with:
directory: terraform/
framework: terraform
output_format: sarif
output_file_path: results.sarif
soft_fail: false
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
terraform-plan-review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-tf
aws-region: us-east-1
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.7.0"
- name: Terraform Init
run: terraform -chdir=terraform/ init -input=false
- name: Terraform Plan
run: terraform -chdir=terraform/ plan -input=false -out=tfplan
- name: Post Plan to PR
uses: actions/github-script@v7
with:
script: |
const { execSync } = require('child_process');
const planOutput = execSync('terraform -chdir=terraform/ show -no-color tfplan').toString();
const truncated = planOutput.substring(0, 50000);
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: `## Terraform Plan\n\n\`\`\`diff\n${truncated}\n\`\`\``
});
Política personalizada de Checkov para reglas específicas de la organización
Define políticas personalizadas que hagan cumplir los estándares de tu organización:
# checkov_custom_policies/aws_require_tags.py
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckResult, CheckCategories
class RequireCostCenterTag(BaseResourceCheck):
def __init__(self):
name = "Ensure all resources have a CostCenter tag"
check_id = "CKV_AWS_CUSTOM_1"
supported_resources = ["aws_*"]
categories = [CheckCategories.CONVENTION]
super().__init__(name=name, id=check_id, categories=categories,
supported_resources=supported_resources)
def scan_resource_conf(self, conf):
tags = conf.get("tags", [{}])[0]
if "CostCenter" in tags and "Environment" in tags:
return CheckResult.PASSED
return CheckResult.FAILED
check = RequireCostCenterTag()
# Run custom policies alongside built-in checks
checkov -d terraform/ \
--external-checks-dir checkov_custom_policies/ \
--framework terraform \
--output cli \
--soft-fail false
Pruebas de módulos de Terraform con Terratest
Valida que tu infraestructura realmente funciona escribiendo pruebas automatizadas:
// test/infrastructure_test.go
package test
import (
"testing"
"time"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/gruntwork-io/terratest/modules/aws"
"github.com/stretchr/testify/assert"
)
func TestVpcModule(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../terraform/modules/vpc",
Vars: map[string]interface{}{
"environment": "test",
"vpc_cidr": "10.0.0.0/16",
"enable_nat": true,
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vpcId := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcId)
subnetIds := terraform.OutputList(t, terraformOptions, "private_subnet_ids")
assert.Equal(t, 2, len(subnetIds))
// Verify NAT gateway was created
natGatewayId := terraform.Output(t, terraformOptions, "nat_gateway_id")
assert.NotEmpty(t, natGatewayId)
// Verify VPC has DNS support
aws.AssertVpcDnsSupportEnabled(t, "us-east-1", vpcId)
}
Automatización de detección de drift
Programa verificaciones regulares de drift y alerta cuando la infraestructura diverge del código:
import subprocess
import smtplib
from email.mime.text import MIMEText
from typing import List, Dict
class DriftDetector:
def __init__(self, terraform_dir: str, environments: List[str]):
self.terraform_dir = terraform_dir
self.environments = environments
def check_drift(self, env: str) -> Dict:
"""Check for drift in a specific environment."""
result = {"environment": env, "drifted": False, "resources": []}
try:
subprocess.run(
["terraform", f"-chdir={self.terraform_dir}/{env}", "init", "-input=false"],
check=True, capture_output=True
)
subprocess.run(
["terraform", f"-chdir={self.terraform_dir}/{env}", "refresh", "-input=false"],
check=True, capture_output=True
)
plan = subprocess.run(
["terraform", f"-chdir={self.terraform_dir}/{env}", "plan", "-detailed-exitcode", "-input=false"],
capture_output=True, text=True
)
if plan.returncode == 2:
result["drifted"] = True
result["resources"] = self._parse_drifted_resources(plan.stdout)
except subprocess.CalledProcessError as e:
result["error"] = str(e)
return result
def _parse_drifted_resources(self, plan_output: str) -> List[str]:
"""Extract resource addresses from plan output."""
resources = []
for line in plan_output.split("\n"):
if line.startswith(" # ") and "will be" in line:
resources.append(line.strip())
return resources
def send_alert(self, drift_results: List[Dict]) -> None:
"""Send email alert if drift is detected."""
drifted_envs = [r for r in drift_results if r["drifted"]]
if not drifted_envs:
return
body = "Drift detected in the following environments:\n\n"
for env in drifted_envs:
body += f"Environment: {env['environment']}\n"
for resource in env["resources"]:
body += f" - {resource}\n"
body += "\n"
msg = MIMEText(body)
msg["Subject"] = "[ALERT] Terraform Drift Detected"
msg["From"] = "infra-alerts@example.com"
msg["To"] = "platform-team@example.com"
with smtplib.SMTP("smtp.example.com", 587) as server:
server.send_message(msg)
# Example usage
detector = DriftDetector("terraform/environments", ["prod", "staging", "dev"])
results = [detector.check_drift(env) for env in detector.environments]
detector.send_alert(results)
Lectura Adicional
- Documentación de terraform plan — qué te dice realmente la salida del plan
- Checkov — análisis estático para Terraform, CloudFormation y manifiestos de Kubernetes
- Terratest — pruebas automatizadas para módulos de Terraform
- AWS Well-Architected — Operational Excellence — la disciplina de revisión de la que parte esta plantilla
- Repositorio companion — el workflow de Checkov, la política personalizada, el test de Terratest y el detector de drift de esta página como archivos ejecutables
Preguntas frecuentes
¿Los cambios de infraestructura deberían requerir la misma aprobación que los despliegues de aplicación?
A menudo deberían requerir más escrutinio. Los cambios de aplicación se pueden revertir con un despliegue; los cambios de infraestructura pueden destruir datos. Considera un flujo de aprobación separado para infraestructura de producción, o requiere la firma de un ingeniero senior.
¿Cómo reviso un módulo de Terraform grande sin perder detalles?
Divide la revisión en capas: primero análisis estático y revisión de plan, luego chequeos de seguridad, luego preparación operacional. No intentes revisar todo a la vez. Usa un checklist (como esta plantilla) para que ninguna categoría sea omitida.
¿Qué es drift detection y por qué importa?
El drift ocurre cuando alguien cambia infraestructura fuera de IaC (ej. por consola). Herramientas como Terraform refresh, AWS Config o CloudFormation drift detection identifican estos cambios. Revisa reportes de drift regularmente; de lo contrario tu código y la realidad divergen, haciendo futuros cambios peligrosos.
¿Qué herramientas de escaneo uso para cada herramienta de IaC?
Para Terraform, Checkov o tfsec. Para CloudFormation, cfn-lint junto con cfn-nag o cfn-guard. Para Ansible, ansible-lint. Elijas lo que elijas, conéctalo al CI para que corra en cada pull request — un escaneo que depende de que alguien se acuerde de ejecutarlo deja de ocurrir en un mes.
¿Quién debería aprobar cambios de infraestructura en producción?
Al menos una persona que no escribió el código, y una segunda aprobación para todo lo que toque estado de prod, IAM o red. La checklist cubre lo mecánico; quien aprueba necesita contexto suficiente para cazar lo que las herramientas no ven.
Recursos Relacionados
Plantilla de Checklist para Code Review
Checklist estructurada para revisiones de código consistentes y exhaustivas que detectan bugs, mejoran la legibilidad y comparten conocimiento en el equipo.
DocPlantilla de Checklist de Despliegue
Una checklist de verificación lista para copiar que asegura despliegues de producción seguros, desde los tests previos hasta el monitoreo posterior.
DocPlantilla de Política de Versionado de Módulos Terraform
Política lista para adaptar sobre versionado y publicación de módulos Terraform: reglas de semver, gestión de breaking changes y deprecación, formato de changelog, publicación en registro y pipelines CI/CD de release.
DocPolítica de Gestión de Estado de Terraform
Una política para gestionar Terraform state files: backend configuration, locking, isolation, access control, versioning y disaster recovery.
DocPlantilla de Seguridad de Pipeline CI/CD
Una plantilla para asegurar pipelines de compilación y despliegue contra filtraciones de credenciales, manipulación, ataques a la cadena de suministro y despliegues no autorizados.
GuideGuía Completa de Módulos de Terraform
Construye módulos de Terraform reutilizables con estructura limpia, inputs validados, outputs honestos y versionado seguro. Incluye pruebas con terraform test y Terratest, además de publicación en el Registry.