Resumen
Escribir infraestructura como código (IaC) es cómodo, repetible y versionable. Pero aquí es donde reside el peligro: un error de configuración también se vuelve repetible. Un bucket S3 público, un Security Group abierto a 0.0.0.0/0 o un contenedor corriendo como root se desplegarán tantas veces como ejecutes tu pipeline.
En esta guía vamos a ver cómo puedes escanear tu IaC con Checkov y TFLint para detectar esas misconfigurations antes de que lleguen a producción. El objetivo es claro: aplicar el principio shift-left del enfoque DevSecOps para que la seguridad no sea algo que ocurre al final, sino parte del desarrollo.
Prerrequisitos
Antes de empezar, asegúrate de tener lo siguiente:
- Terraform y/o Ansible instalados y un repositorio con código IaC.
- Python 3.8+ (para Checkov) y
pip. - Familiaridad con pipelines CI/CD (GitHub Actions).
¿Qué es el escaneo de IaC y por qué importa?
El escaneo de IaC analiza estáticamente los ficheros de definición (HCL de Terraform, playbooks de Ansible, manifiestos de Kubernetes, Dockerfiles) buscando patrones inseguros: cifrado desactivado, logging ausente, permisos excesivos, secretos en claro, etc.
A diferencia del escaneo de vulnerabilidades que analiza imágenes y dependencias ya construidas, aquí trabajamos sobre el plano de definición, mucho antes de que el primer recurso sea siquiera creado en la nube.
Shift-left en una frase
Cuanto más tarde encuentras un fallo de seguridad, más caro es corregirlo. Detectar un Security Group abierto en el editor o en el git commit cuesta segundos; detectarlo tras un incidente en producción cuesta mucho más.

Checkov
Checkov (de Prisma Cloud / Bridgecrew) es un escáner de análisis estático que lo cubre casi todo: Terraform, Ansible, Kubernetes, Dockerfile, CloudFormation, Helm, ARM y Serverless. Viene con más de mil policies predefinidas mapeadas a estándares como CIS Benchmarks, PCI-DSS o HIPAA.
Instalación
Puedes instalarlo fácilmente mediante pip o brew:
# Con pip (recomendado)
pip install checkov
# Con Homebrew (macOS)
brew install checkov
# Verificar
checkov --version
Uso básico
Una vez instalado, puedes empezar a escanear con estos comandos:
# Escanear todo el directorio actual (autodetecta el tipo de IaC)
checkov -d .
# Escanear un único fichero
checkov -f main.tf
# Limitar a un framework concreto
checkov -d . --framework terraform
checkov -d . --framework ansible
checkov -d . --framework kubernetes
checkov -d . --framework dockerfile
# Salida en formato máquina para CI
checkov -d . -o json --output-file-path resultados/
# Fallar solo con severidad alta o superior (requiere API key para severidades,
# alternativamente filtra por checks concretos)
checkov -d . --compact --quiet
Silenciar falsos positivos
Si tienes un caso de uso legítimo donde quieres saltarte un check, no necesitas desactivarlo globalmente. Puedes añadir un comentario inline en el recurso Terraform:
resource "aws_s3_bucket" "logs" {
# checkov:skip=CKV_AWS_18:El logging se gestiona en la cuenta central
bucket = "mi-bucket-logs"
}
Ejemplo real: hallazgo en Terraform y su corrección
Imagina que tienes este código inseguro:
resource "aws_s3_bucket" "data" {
bucket = "frikiteam-data"
}
Checkov no se quedará de brazos cruzados y te reportará algo como esto:
Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"
FAILED for resource: aws_s3_bucket.data
Check: CKV_AWS_21: "Ensure all data stored in the S3 bucket have versioning enabled"
FAILED for resource: aws_s3_bucket.data
Check: CKV2_AWS_6: "Ensure that S3 bucket has a Public Access block"
FAILED for resource: aws_s3_bucket.data
Para solucionarlo, la versión corregida debería verse así:
resource "aws_s3_bucket" "data" {
bucket = "frikiteam-data"
}
resource "aws_s3_bucket_versioning" "data" {
bucket = aws_s3_bucket.data.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_public_access_block" "data" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_logging" "data" {
bucket = aws_s3_bucket.data.id
target_bucket = aws_s3_bucket.logs.id
target_prefix = "log/"
}
Ejemplo en Ansible
Si trabajas con Ansible, un playbook inseguro como este:
- name: Descargar binario
ansible.builtin.get_url:
url: http://example.com/app.tar.gz # HTTP sin cifrar
dest: /opt/app.tar.gz
validate_certs: no # Verificación TLS desactivada
Checkov marcará CKV2_ANSIBLE_1 (por el uso de HTTP en lugar de HTTPS) y la desactivación de la verificación de certificados. La solución es simple: usa https:// y establece validate_certs: yes.
TFLint
TFLint es un linter específico para Terraform. Ojo, no es exactamente lo mismo que Checkov: mientras Checkov busca seguridad, TFLint se encarga de detectar errores de Terraform (sintaxis obsoleta, código muerto, convenciones) y, gracias a sus plugins de proveedor, valida argumentos y tipos de instancia inválidos que Terraform no pillaría hasta que intentes hacer el apply.
Instalación
# Script oficial
curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash
# Homebrew (macOS)
brew install tflint
# Verificar
tflint --version
Configuración con reglas de proveedor
Para sacarle partido, necesitas un fichero .tflint.hcl en la raíz de tu proyecto:
plugin "aws" {
enabled = true
version = "0.34.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
plugin "azurerm" {
enabled = true
version = "0.27.0"
source = "github.com/terraform-linters/tflint-ruleset-azurerm"
}
config {
call_module_type = "local"
}
rule "terraform_naming_convention" {
enabled = true
}
Uso básico
# Descargar los plugins declarados en .tflint.hcl
tflint --init
# Ejecutar el linting sobre el directorio actual
tflint
# Recorrer módulos recursivamente
tflint --recursive
# Salida para CI
tflint --format compact
Un ejemplo de un error típico que TFLint te salvaría de un despliegue fallido:
Error: "t9.micro" is an invalid value as instance_type (aws_instance_invalid_type)
on main.tf line 12:
12: instance_type = "t9.micro"
Terraform no detectaría que esa instancia no existe hasta llegar al apply; TFLint te lo dice en segundos.
Integración en pre-commit hooks
Para que esto sea parte de tu flujo diario, instala pre-commit (pip install pre-commit) y crea un archivo .pre-commit-config.yaml:
repos:
- repo: https://github.com/bridgecrewio/checkov
rev: 3.2.0
hooks:
- id: checkov
args: ["--framework", "terraform", "--framework", "ansible", "--quiet"]
- repo: https://github.com/terraform-linters/tflint
rev: v0.53.0
hooks:
- id: tflint
Luego, actívalo con:
pre-commit install # activa el hook en git commit
pre-commit run --all-files
El hook no sustituye al pipeline
Recuerda que un desarrollador puede saltarse los hooks con git commit --no-verify. Los hooks son tu primera capa de comodidad y rapidez, pero la barrera bloqueante real debe vivir siempre en tu CI/CD.
Integración en CI/CD (GitHub Actions)
Aquí tienes un ejemplo de cómo automatizar este escaneo en .github/workflows/iac-security.yml:
name: IaC Security Scan
on:
pull_request:
paths:
- "**/*.tf"
- "**/*.yml"
- "**/*.yaml"
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkov
uses: bridgecrewio/checkov-action@v12
with:
directory: .
framework: terraform,ansible,dockerfile
output_format: cli,sarif
output_file_path: console,results.sarif
soft_fail: false # falla el job ante hallazgos
- name: Subir SARIF a Code Scanning
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: results.sarif
- name: TFLint
uses: terraform-linters/setup-tflint@v4
with:
tflint_version: v0.53.0
- run: |
tflint --init
tflint --recursive --format compact
SARIF y la pestaña Security
Si exportas en formato SARIF, los hallazgos aparecerán directamente en la pestaña Security → Code scanning de GitHub, con anotaciones integradas en el Pull Request.
Comparativa de herramientas
Si tienes dudas de qué herramienta elegir, aquí tienes un resumen comparativo:
| Característica | Checkov | TFLint | tfsec | Terrascan |
|---|---|---|---|---|
| Enfoque principal | Seguridad/compliance | Linting + errores TF | Seguridad | Seguridad/compliance |
| Terraform | Sí | Sí (nativo) | Sí | Sí |
| Ansible | Sí | No | No | No |
| Kubernetes / Helm | Sí | No | Sí (limitado) | Sí |
| Dockerfile | Sí | No | No | Sí |
| Reglas de proveedor (AWS/Azure) | Vía policies | Sí (plugins) | Vía reglas | Vía policies |
| Política personalizada | Python / YAML | Reglas TFLint | Rego (custom) | Rego (OPA) |
| Estado del proyecto | Activo | Activo | Fusionado en Trivy | Activo |
| Lenguaje policies | YAML / Python | HCL | JSON / Rego | Rego |
¿Cuál elijo?
No son excluyentes. De hecho, la combinación ganadora suele ser Checkov + TFLint: Checkov para la seguridad multi-framework y TFLint para el linting profundo de Terraform. Un detalle: tfsec ahora forma parte de Trivy (trivy config), así que si ya usas Trivy para escaneo de vulnerabilidades, puedes aprovecharlo también para IaC.
Buenas prácticas
Para mantener tu infraestructura realmente segura, te recomiendo:
- Ejecuta siempre el escaneo en pre-commit (por rapidez) y de forma bloqueante en CI (como garantía).
- Versiona tus archivos de configuración (
.tflint.hcly.pre-commit-config.yaml) junto al código. - Si usas un
checkov:skip, documenta siempre el porqué; los skips sin motivo son deuda de seguridad pura. - Aprovecha el formato SARIF para centralizar todo en tu plataforma de CI.
- Mantente al día: revisa las nuevas policies cada vez que actualices las versiones de las herramientas.
Enlaces relacionados
- Introducción a DevSecOps
- Escaneo de Vulnerabilidades
- Terraform Base
- Ansible Base
