DEVOPS 16 min

Terraform Seguro en AWS: State Remoto, Checkov y OpenTofu

Abandona el 'ClickOps' y traduce tu infraestructura manual a código declarativo. Gestiona estados (.tfstate), versiona tu entorno y aplica despliegues reproducibles con HashiCorp Terraform.

Escrito por Samir El Azem Crovetto • Senior Backend Engineer & DevSecOps

La Transición de "ClickOps" a Infraestructura como Código (IaC)

Aprovisionar recursos en nubes públicas como Amazon Web Services (AWS) a través de la consola web (ClickOps) introduce vulnerabilidades sistémicas en entornos corporativos: falta de trazabilidad histórica, ausencia de revisiones por pares, discrepancias de configuración (drift) imposibles de auditar y un alto índice de errores humanos accidentales que exponen bases de datos o abren puertos de gestión a Internet.

La Infraestructura como Código (IaC) resuelve estos problemas al definir la infraestructura de forma declarativa y reproducible mediante código versionado en Git. En este ecosistema, Terraform y su bifurcación de código abierto bajo la Linux Foundation, OpenTofu, representan los estándares dominantes del sector.

Sin embargo, adoptar IaC sin controles rigurosos de seguridad introduce nuevos vectores de ataque críticos:

  • Exposición del archivo de estado (terraform.tfstate): El state de Terraform contiene un mapa completo de los recursos desplegados y almacena en texto plano cualquier secreto procesado (contraseñas maestras de RDS, certificados TLS, claves de acceso y tokens de servicios).
  • Inexistencia de bloqueos de concurrencia: Múltiples pipelines de CI/CD o ingenieros ejecutando apply concurrentemente sin bloqueo pueden corromper el estado de la infraestructura.
  • Configuraciones permisivas no auditadas: Despliegue inadvertido de Security Groups con 0.0.0.0/0, buckets S3 públicos o instancias sin cifrado KMS por falta de análisis estático en los pipelines de entrega continua.
  • *Deriva de configuración (Configuration Drift):* Modificaciones manuales fuera de la pipeline que desalinean la infraestructura real respecto al código fuente.

Hacer IaC de nivel empresarial exige asegurar el ciclo de vida completo: desde el almacenamiento cifrado del estado hasta el análisis estático automatizado con herramientas como Checkov.


Pipeline DevSecOps para IaC: De la Pull Request al Despliegue

La seguridad en IaC debe implementarse como un pipeline automatizado de análisis estático y puertas de calidad (quality gates), tal como exploramos en los fundamentos de qué es DevSecOps:

graph TD
    subgraph Dev_Environment ["👨‍💻 Entorno de Desarrollo"]
        Dev["Ingeniero / SRE"]
        Git["Git Commit & Push"]
    end

    subgraph CI_Pipeline ["⚙️ Pipeline CI/CD (GitHub Actions / GitLab CI)"]
        Lint["terraform fmt & validate"]
        SecretScan["Escaneo de Secretos: Gitleaks"]
        IaCScan["Escaneo de Seguridad IaC: Checkov / Trivy"]
        Plan["terraform plan -out=tfplan"]
        Gate{"¿Pasa Reglas de Seguridad?"}
    end

    subgraph Approval_Gate ["🛡️ Aprobación y Gobernanza"]
        Review["Revisión de Plan por Pares (PR Approval)"]
    end

    subgraph CD_Deployment ["🚀 Despliegue en AWS Cloud"]
        LockDB["DynamoDB: Adquirir State Lock"]
        Apply["terraform apply tfplan"]
        StateS3["S3 Bucket: Guardar tfstate (SSE-KMS)"]
        UnlockDB["DynamoDB: Liberar State Lock"]
    end

    Dev --> Git
    Git --> Lint
    Lint --> SecretScan
    SecretScan --> IaCScan
    IaCScan --> Plan
    Plan --> Gate
    Gate -->|Fallo: Bloqueo crítico| Dev
    Gate -->|Aprobado| Review
    Review --> LockDB
    LockDB --> Apply
    Apply --> StateS3
    StateS3 --> UnlockDB

    classDef dev fill:#e1f5fe,stroke:#0288d1,stroke-width:2px;
    classDef ci fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px;
    classDef gate fill:#fff3e0,stroke:#f57c00,stroke-width:2px;
    classDef deploy fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;

    class Dev,Git dev;
    class Lint,SecretScan,IaCScan,Plan,Gate ci;
    class Review gate;
    class LockDB,Apply,StateS3,UnlockDB deploy;

1. Hardening del Backend Remoto: S3 y DynamoDB

Por defecto, Terraform almacena el estado localmente en el archivo terraform.tfstate. En entornos de equipo o producción, el estado debe persistirse en un backend remoto seguro en AWS, combinando Amazon S3 para el almacenamiento cifrado y Amazon DynamoDB para el bloqueo de concurrencia.

Requisitos de Hardening del Bucket S3:

  1. Bloqueo Total de Acceso Público: BlockPublicAccess = true para todas las políticas de lista de control de acceso (ACLs) y políticas de bucket.
  2. Versionado de Objetos: Activado para permitir revertir a estados anteriores ante corrupción accidental.
  3. Cifrado en Reposo Obligatorio (SSE-KMS): Cifrado mediante una clave KMS gestionada por el cliente (CMK), evitando el uso de claves compartidas por defecto.
  4. Política de Transporte Seguro: Exigir aws:SecureTransport = true para forzar todas las operaciones sobre TLS 1.3/1.2.

A continuación se muestra la configuración del backend en Terraform/OpenTofu:

terraform {
  required_version = ">= 1.6.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.40"
    }
  }

  backend "s3" {
    bucket         = "corp-production-terraform-state-eu-west-1"
    key            = "core-infra/vpc-rds.tfstate"
    region         = "eu-west-1"
    encrypt        = true
    kms_key_id     = "arn:aws:kms:eu-west-1:123456789012:key/b2c3d4e5-6789-01ab-cdef-222222222222"
    dynamodb_table = "terraform-state-lock-production"
  }
}

La tabla de DynamoDB requiere únicamente una clave primaria de tipo String denominada LockID. Cuando un operador o pipeline ejecuta terraform apply o terraform plan, Terraform inserta temporalmente un registro en DynamoDB; si otro proceso intenta ejecutarse simultáneamente, es bloqueado con el error Error: Error acquiring the state lock, previniendo colisiones catastróficas.


2. Gestión Segura de Secretos: El Dilema del tfstate

Uno de los problemas más incomprendidos en Terraform es que cualquier valor referenciado en un recurso o proveedor queda guardado en texto plano dentro del archivo .tfstate, incluso si la variable está marcada con sensitive = true.

El atributo sensitive = true solo oculta el valor en la salida estándar de la consola (stdout) durante plan y apply; no cifra el contenido dentro del archivo JSON del estado.

Antipatrones Comunes:

  • Definir contraseñas de bases de datos mediante variables estáticas en archivos terraform.tfvars.
  • Generar contraseñas aleatorias con el proveedor randompassword y pasarlas al recurso awsdb_instance: la contraseña se escribe íntegra en el estado.

Solución de Arquitectura: Generación y Rotación Fuera de Terraform

La práctica recomendada consiste en delegar la generación de secretos a servicios especializados como AWS Secrets Manager con rotación automática, o consumir credenciales generadas de forma efímera:

# Crear el contenedor del secreto en AWS Secrets Manager gestionado por KMS
resource "aws_secretsmanager_secret" "db_credentials" {
  name                    = "production/rds/mysql-app"
  kms_key_id             = aws_kms_key.infrastructure_key.arn
  recovery_window_in_days = 0 # Eliminación inmediata en caso de baja controlada
}

# La contraseña NO se define en Terraform, se gestiona mediante RDS auto-generation
resource "aws_db_instance" "production_database" {
  identifier                  = "corp-production-db"
  allocated_storage           = 100
  engine                      = "mysql"
  engine_version              = "8.0"
  instance_class              = "db.r6g.large"
  manage_master_user_password = true # AWS RDS gestiona y rota la contraseña en Secrets Manager
  master_user_secret_kms_key_id = aws_kms_key.infrastructure_key.arn
  username                    = "dbadmin"

  vpc_security_group_ids = [aws_security_group.db_sg.id]
  db_subnet_group_name   = aws_db_subnet_group.private.name
  publicly_accessible    = false
  storage_encrypted      = true
  kms_key_id             = aws_kms_key.infrastructure_key.arn

  skip_final_snapshot = false
  final_snapshot_identifier = "corp-production-db-final-snapshot"
}

Al activar managemasteruser_password = true, AWS RDS crea automáticamente el secreto en AWS Secrets Manager, rota la clave periódicamente y la contraseña jamás pasa por Terraform ni se escribe en el archivo de estado.


3. Análisis Estático de Seguridad IaC con Checkov en CI/CD

El análisis estático de código de infraestructura (Static Application Security Testing - SAST para IaC) permite detectar malas prácticas antes de que los recursos sean creados en la nube. Checkov (de Palo Alto Networks / Bridgecrew) analiza archivos HCL, CloudFormation, Kubernetes y Terraform Plans frente a más de 1.000 políticas de seguridad alineadas con CIS Benchmarks.

Integración en GitHub Actions:

El siguiente flujo ejecuta validaciones automáticas ante cualquier Pull Request que modifique código Terraform:

name: "Terraform DevSecOps Quality Gate"

on:
  pull_request:
    paths:
      - 'terraform/**'

permissions:
  contents: read
  pull-requests: write

jobs:
  security-scan:
    name: "Checkov Security & Compliance Scan"
    runs-on: ubuntu-latest
    steps:
      - name: "Checkout Code"
        uses: actions/checkout@v4

      - name: "Run Gitleaks Secret Detector"
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: "Set up Python & Install Checkov"
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: "Run Checkov Static Analysis"
        run: |
          pip install checkov
          checkov --directory terraform/ \
                  --framework terraform \
                  --output cli \
                  --output github_failed_only \
                  --soft-fail false \
                  --skip-check CKV_AWS_144 # Justificación documentada si aplica

Si un desarrollador introduce una regla de Security Group con cidr_blocks = ["0.0.0.0/0"] en el puerto 3306 o crea un bucket S3 sin cifrado KMS, Checkov falla el pipeline con código de salida 1, impidiendo que el código se fusione a la rama principal.


4. OpenTofu: Cifrado Nativo del Estado y Compatibilidad

Tras el cambio de licencia de Terraform por parte de HashiCorp en 2023 hacia la licencia BSL (Business Source License), la comunidad de código abierto y la Linux Foundation lanzaron OpenTofu.

OpenTofu mantiene compatibilidad drop-in con el lenguaje HCL y los proveedores existentes, pero introduce una funcionalidad de seguridad largamente demandada por los equipos de ciberseguridad: Cifrado nativo del archivo de estado desde el cliente.

Configuración del Cifrado de Estado en OpenTofu:

A diferencia de Terraform tradicional, donde el estado viaja en texto plano hacia S3 y depende exclusivamente del cifrado de S3 en reposo, OpenTofu puede cifrar el estado antes de enviarlo al backend, utilizando claves AES-GCM o claves de AWS KMS:

# Exclusivo de OpenTofu (v1.7+)
terraform {
  encryption {
    key_provider "aws_kms" "production_kms" {
      kms_key_id = "arn:aws:kms:eu-west-1:123456789012:key/b2c3d4e5-6789-01ab-cdef-222222222222"
      region     = "eu-west-1"
    }

    method "aes_gcm" "state_encryption" {
      keys = key_provider.aws_kms.production_kms
    }

    state {
      method   = method.aes_gcm.state_encryption
      enforced = true
    }

    plan {
      method   = method.aes_gcm.state_encryption
      enforced = true
    }
  }
}

Con esta directiva, incluso si un atacante obtiene acceso de lectura al bucket S3 del backend o descarga un archivo de plan tfplan, no puede descifrar los datos sin permisos explícitos de kms:Decrypt sobre la clave KMS designada.


5. Buenas Prácticas para la Gobernanza de Infraestructura en Producción

Para alcanzar un nivel de madurez elevado en IaC corporativa, se recomienda consolidar las siguientes prácticas operativas:

  1. Despliegues Exclusivos vía CI/CD: Revocar permisos de Apply manual a los ingenieros en sus cuentas individuales de AWS. Solo los runners de CI/CD autenticados mediante OIDC (OpenID Connect federado con GitHub Actions o GitLab) deben tener permisos para modificar recursos de producción.
  2. Pipelines Basadas en Plan Cifrado: El comando terraform apply nunca debe ejecutarse a ciegas. Debe consumir un archivo de plan pre-generado y aprobado (terraform plan -out=tfplan), garantizando que lo que se aplica es idéntico a lo que fue revisado en la Pull Request.
  3. *Control de Deriva Automatizado (Drift Detection):* Ejecutar un job programado (cron nocturno) que ejecute terraform plan -detailed-exitcode. Si detecta modificaciones manuales no declaradas en código, alerta automáticamente al equipo de seguridad en Slack o PagerDuty.
  4. Principio de Mínimo Privilegio en Roles de Aprovisionamiento: Segmentar los pipelines por capas arquitectónicas (red, base de datos, cómputo) para que el runner de Terraform encargado de la capa de datos no tenga permisos sobre la capa de identidad ni sobre los clústeres de Kubernetes.

Resumen y Checklist de Seguridad para Terraform y OpenTofu

  • [x] Backend remoto S3 configurado con bloqueo de acceso público, versionado y cifrado SSE-KMS.
  • [x] Concurrencia de despliegues protegida mediante tabla de bloqueo en DynamoDB.
  • [x] Secretos y contraseñas delegados a AWS Secrets Manager con managemasteruser_password = true, evitando su persistencia en el archivo tfstate.
  • [x] Escaneo estático SAST obligatorio en CI/CD con Checkov y detección de credenciales con Gitleaks.
  • [x] Despliegues ejecutados exclusivamente mediante pipelines automatizados federados vía OIDC, sin credenciales estáticas de AWS en runners.
  • [x] Detección programada de deriva de configuración (drift detection) para revertir cambios manuales fuera de código.

Si necesitas auditar la infraestructura como código de tu organización, implementar pipelines DevSecOps seguros en AWS o migrar tus despliegues a OpenTofu con cifrado nativo, puedes consultar mis Servicios de Ciberseguridad y Consultoría o ponerte en contacto conmigo a través de Sobre mí.