Terraform Audit Report
Informe de Auditoría IaC — NovaPay SaaS
Revisión completa de infraestructura AWS/Terraform antes de la ronda Serie A
12 jun 2026
Terraform 1.3.x
AWS eu-west-1
3 archivos .tf
Acción inmediata requerida: Se han detectado 7 hallazgos CRÍTICOS incluyendo credenciales AWS hardcodeadas en texto plano, contraseña de base de datos de producción expuesta, acceso SSH irrestricto desde internet, y política IAM con permisos de administrador total. Estos problemas representan un riesgo de seguridad inmediato y deben remediarse antes de cualquier proceso de due diligence.
0/100
Security Score
CRÍTICO
7 críticos · 5 altos · 3 medios
0/100
Module Quality
DEFICIENTE
8 altos · 9 medios · 4 bajos
13
Recursos
7
Variables
4
Outputs
3
Archivos .tf
aws
Provider
7
7
Crítico
!
13
Alto
~
12
Medio
i
4
Bajo
Hallazgos de Seguridad
15 hallazgos
CRÍTICO
AWS secret key hardcoded en la configuración del provider
Fix: Usar variables de entorno, perfiles AWS, o IAM roles para las credenciales. Nunca hardcodear en .tf
secret_key = "wJalrXUt..."
CRÍTICO
Contraseña de producción de RDS (MySQL) hardcodeada en texto plano
Fix: Usar variable con sensitive = true y obtener el valor desde AWS Secrets Manager o SSM Parameter Store
password = "NovaPay@2023!"
CRÍTICO
IAM policy con Action="*" — concede todos los permisos de AWS
Fix: Limitar Action a operaciones específicas: s3:GetObject, s3:PutObject, rds:Connect, etc.
Action = "*" Resource = "*"
CRÍTICO
IAM policy con Action=* AND Resource=* — acceso de administrador total para las instancias EC2
Fix: Aplicar least-privilege: solo los permisos necesarios para la aplicación (S3, RDS, SSM)
efectivamente: AdministratorAccess
CRÍTICO
Security group permite SSH (puerto 22) desde 0.0.0.0/0 — acceso a todos los servidores desde internet
Fix: Eliminar el ingress 22. Usar AWS SSM Session Manager para acceso a instancias sin abrir SSH
ingress port 22, cidr 0.0.0.0/0
CRÍTICO
Variable 'db_password' tiene valor por defecto hardcodeado ("NovaPay@2023!")
Fix: Eliminar el default value. Pasar en runtime via terraform.tfvars (en .gitignore) o TF_VAR_db_password
variable "db_password" default=...
CRÍTICO
Variable 'stripe_api_key' tiene clave de API de producción hardcodeada como valor por defecto
Fix: Rotar la clave de Stripe inmediatamente. Usar Secrets Manager para ingestión en runtime
default = "sk_live_NOVAPAY..."
ALTO
Base de datos RDS con publicly_accessible = true — expuesta directamente a internet
Fix: publicly_accessible = false. Acceder solo desde la VPC privada o bastion host
SEC040
ALTO
S3 bucket 'novapay-prod-uploads' sin cifrado server-side (datos de clientes sin protección)
Fix: Añadir aws_s3_bucket_server_side_encryption_configuration con AES256 o aws:kms
SEC030
ALTO
S3 bucket 'novapay-prod-backups' sin cifrado — backups de base de datos en claro
Fix: Cifrado obligatorio para backups. Añadir aws_s3_bucket_server_side_encryption_configuration
SEC030
ALTO
RDS sin storage_encrypted = true — datos de pago en disco sin cifrar (incumple PCI-DSS)
Fix: storage_encrypted = true con kms_key_id apuntando a CMK dedicada
SEC031
MEDIO
Variable 'db_password' no marcada como sensitive = true — aparece en plan output y logs
Fix: sensitive = true en la declaración de la variable
SEC050
MEDIO
Variable 'stripe_api_key' no marcada como sensitive = true
Fix: sensitive = true en la declaración de la variable
SEC050
MEDIO
Variable 'jwt_secret' no marcada como sensitive = true
Fix: sensitive = true en la declaración de la variable
SEC050
Hallazgos de Estructura y Calidad
21 hallazgos
ALTO
Falta versions.tf — sin constraint de versión de provider ni de Terraform
Fix: Crear versions.tf con terraform { required_version y required_providers { aws { version = "~> 5.0" } } }
versions.tf ausente
ALTO
Sin backend remoto configurado — el estado de Terraform se guarda localmente (o en Git)
Fix: Configurar S3 + DynamoDB backend con cifrado y bloqueo. Estado en Git es peligroso: expone secretos
terraform.tfstate local
ALTO
5 variables sin tipo declarado (environment, aws_region, instance_count, etc.) — aceptan cualquier valor
Fix: Añadir type = string | number | bool a cada variable. Usar validation { } donde corresponda
variable sin type constraint
MEDIO
Configuración de provider en main.tf — debe estar en providers.tf o versions.tf (root module only)
Fix: Mover el bloque provider "aws" a providers.tf
provider "aws" en main.tf
MEDIO
Sin required_version constraint — cualquier versión de Terraform puede aplicar este código
Fix: required_version = ">= 1.5.0" en versions.tf
required_version ausente
BAJO
4 outputs sin description — los consumidores del módulo no sabrán para qué sirven los valores
Fix: Añadir description = "..." a cada output block
4 outputs sin description
Estructura del Módulo
Archivos Presentes
main.tf
Recursos principales
variables.tf
Variables de entrada
outputs.tf
Valores de salida
versions.tf
FALTA — requerido
backend.tf
FALTA — estado local
locals.tf
Falta — naming DRY
Calidad de Variables (7 totales)
Con description
0 de 7
!
Con type constraint
2 de 7
Con validation block
0 de 7
Secretos con sensitive=true
0 de 3
Backend remoto
No configurado
Nomenclatura recursos
snake_case correcto
Plan de Remediación
Priorizado por riesgo
1
Rotar credenciales comprometidas
Urgente — 2h
Las claves AWS y la API key de Stripe están en el repositorio Git. Rotar ambas inmediatamente. Eliminar del historial con git-filter-repo o BFG.
# 1. Revocar en AWS Console # 2. Revocar en Stripe Dashboard # 3. Limpiar historial Git pip install git-filter-repo git filter-repo --path variables.tf --invert-paths
2
Migrar a backend remoto S3
Crítico — 4h
Crear backend.tf con S3 + DynamoDB. Migrar el estado local al bucket. Activar cifrado y versionado.
terraform { backend "s3" { bucket = "novapay-tf-state" key = "prod/terraform.tfstate" region = "eu-west-1" encrypt = true dynamodb_table = "novapay-tf-locks" } }
3
Cifrar RDS y S3
Alto — 3h
Habilitar storage_encrypted en RDS y añadir server-side encryption en ambos buckets S3.
# RDS storage_encrypted = true publicly_accessible = false # S3 (resource separado) resource "aws_s3_bucket_server_side_encryption_configuration" "uploads" { bucket = aws_s3_bucket.uploads.id rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" }} }
4
Least-privilege IAM + cerrar SSH
Alto — 4h
Reemplazar Action="*" por permisos específicos. Eliminar el ingress 22 y activar SSM Session Manager.
# IAM — scope específico Statement = [{ Effect = "Allow" Action = ["s3:GetObject", "s3:PutObject"] Resource = ["arn:aws:s3:::novapay-prod-*/*"] }] # Security group — eliminar port 22 # Usar SSM: aws ssm start-session --target i-xxx
5
Crear versions.tf y marcar sensitive
Medio — 1h
Añadir versions.tf con versiones de Terraform y provider. Marcar db_password, stripe_api_key, jwt_secret como sensitive = true.
terraform { required_version = ">= 1.5.0" required_providers { aws = { version = "~> 5.0" } } } variable "db_password" { type = string sensitive = true }
6
CI/CD para Terraform (GitHub Actions)
Medio — 6h
Añadir pipeline que ejecute terraform plan en PRs y terraform apply en merge a main. Eliminar los apply manuales desde portátiles.
# .github/workflows/terraform.yml jobs: plan: steps: - run: terraform init - run: terraform validate - run: terraform plan -out=tfplan apply: environment: production if: github.ref == 'refs/heads/main'