|

Cómo almacenar el estado de Terraform en S3 cifrado

Almacenar el estado de Terraform en S3 con encriptación significa guardar el archivo tfstate en un bucket de Amazon S3 con server-side encryption activada (encrypt = true), bloqueo vía DynamoDB y políticas IAM. Protege credenciales, IDs de recursos y topología de red ante accesos no autorizados, locales y remotos.

El estado de Terraform es un archivo JSON (tfstate) que registra qué recursos creó Terraform y cómo se mapean a tu configuración. Adentro hay datos sensibles: credenciales de proveedor, IDs de recursos y direcciones IP. El backend S3 lo guarda en un bucket centralizado con encriptación del lado del servidor, bloqueo de concurrencia con DynamoDB y control de acceso granular vía IAM.

En 30 segundos

  • Cerca del 30% de los archivos tfstate se guardan sin encriptar, según una encuesta de HashiCorp de 2024 citada por dev.to.
  • El bloque backend "s3" con encrypt = true activa la encriptación del lado del servidor en una línea.
  • SSE-KMS te da auditoría en CloudTrail e IAM granular; SSE-S3 no. Para producción, KMS.
  • DynamoDB con un atributo LockID evita que dos personas pisen el estado al mismo tiempo.
  • La migración del estado local a S3 la maneja terraform init, que te pregunta antes de copiar.

¿Por qué el estado de Terraform sin encriptación es un riesgo de seguridad?

Ponele que tenés un tfstate en tu disco o en un bucket público sin cifrar. Cualquiera que lo abra ve, en texto plano, las claves de acceso de tu proveedor, los IDs de cada recurso y el mapa completo de tu red.

Eso es oro para un atacante.

El dato que asusta: aproximadamente el 30% de los archivos de estado se almacenan sin encriptación, según una encuesta de HashiCorp de 2024 citada por la nota técnica de dev.to. No es un descuido menor. Un tfstate filtrado puede contener contraseñas de bases de datos, tokens y la estructura de tu VPC entera.

Hay dos frentes de amenaza. El local, cuando el archivo vive en la máquina de alguien y termina en un commit por error (sí, pasa más de lo que uno admite). Y el remoto, cuando está en un bucket mal configurado, expuesto a internet o accesible por roles que no deberían tocarlo. Consultá nuestra guía sobre integración con tu pipeline de CI/CD.

Configuración del backend S3 para almacenar el estado de Terraform con encriptación

El backend remoto se define en un bloque corto. Esta es la base, tal como aparece en la documentación oficial de HashiCorp:

terraform {
 backend "s3" {
 bucket = "my-terraform-state-prod"
 key = "global/terraform.tfstate"
 region = "us-east-1"
 encrypt = true
 dynamodb_table = "tf-state-lock"
 }
}

Qué hace cada campo:

  • bucket: el nombre del bucket S3 que va a guardar el estado. Debe existir antes de correr init.
  • key: el path lógico dentro del bucket. Acá separás ambientes, por ejemplo prod/terraform.tfstate y dev/terraform.tfstate.
  • encrypt = true: activa la encriptación del lado del servidor. Sin esta línea, el archivo viaja y se guarda sin cifrar.
  • dynamodb_table: la tabla que maneja el bloqueo de concurrencia.

Después de definir el bloque, corrés terraform init. Ese comando inicializa el backend y, si venías de un estado local, te ofrece migrarlo. La clave key importa más de lo que parece: si manejás varios ambientes en el mismo bucket, un path bien armado evita que pises el estado de producción con el de testing.

SSE-S3 vs SSE-KMS: ¿cuál elegir para tu estado de Terraform?

Las dos cifran el archivo. La diferencia está en quién controla la clave y qué auditoría te queda.

CriterioSSE-S3SSE-KMS
Gestión de la claveAmazon (AES-256)Vos (customer-managed)
AuditoríaNo registra accesos a la claveCloudTrail registra cada uso
Control de accesoNo configurable por claveIAM granular por key policy
Compliance (PCI-DSS, HIPAA)LimitadoApto
Costo extraSin cargoCargo por clave y por request KMS
Recomendado paraPruebas, proyectos chicosProducción
almacenar estado terraform s3 diagrama explicativo

Para producción, la respuesta es SSE-KMS. El control de auditoría que te da CloudTrail vale el cargo extra de las requests, sobre todo si tenés que demostrar compliance. SSE-S3 zafa para un sandbox o un proyecto personal, donde no necesitás saber quién desencriptó qué.

¿Qué permisos KMS necesita Terraform para encriptar el estado?

Primero creás la clave KMS y anotás su ARN. Después, el rol que corre Terraform necesita estos cuatro permisos sobre esa clave:

  • kms:Encrypt: cifrar el estado al hacer apply.
  • kms:Decrypt: leer el estado al hacer plan o apply.
  • kms:GenerateDataKey: generar la clave de datos que cifra el objeto.
  • kms:DescribeKey: validar la clave antes de usarla.

Una vez que tenés la clave, la enlazás en el backend con kms_key_id:

backend "s3" {
 bucket = "my-terraform-state-prod"
 key = "prod/terraform.tfstate"
 region = "us-east-1"
 encrypt = true
 kms_key_id = "arn:aws:kms:us-east-1:111122223333:key/abcd-1234"
}

¿Cómo confirmás que la clave existe y está habilitada? Con aws kms describe-key --key-id <arn>. Si te devuelve Enabled, vas bien.

Bloqueo de estado con DynamoDB: evitar modificaciones simultáneas

Acá viene un problema clásico de equipos. Dos personas corren terraform apply casi al mismo tiempo, y sin bloqueo el estado queda corrupto porque cada uno escribió encima del otro.

DynamoDB resuelve eso. Creás una tabla con un atributo de clave primaria llamado LockID (de tipo string) y la referenciás en el backend con dynamodb_table. Cuando alguien arranca una operación, Terraform escribe un lock; cuando termina, lo libera. Si otra persona intenta correr al mismo tiempo, recibe un error de bloqueo y tiene que esperar.

Es automático. No tenés que hacer nada en cada corrida, salvo crear la tabla una vez.

Control de acceso: políticas IAM que protegen el bucket

La encriptación cifra el archivo, pero IAM decide quién lo toca. La mejor práctica, según la guía prescriptiva de AWS, combina varias capas:

  • Deny sin SSE-KMS: una policy que rechaza cualquier PutObject que no venga cifrado con tu clave KMS.
  • Acceso restringido al rol de Terraform: solo ese rol puede hacer GetObject y PutObject sobre el bucket de estado.
  • Block Public Access: activado a nivel cuenta y bucket, sin excepciones.
  • Roles separados por ambiente: el rol de dev no llega al estado de prod, y al revés.

Si gestionás esta infraestructura junto con servidores o dominios de un proveedor de hosting como donweb.com, mantené el mismo criterio: el principio de mínimo privilegio aplica igual al bucket que a cualquier credencial de tu stack.

¿Cómo migrar el estado local a S3 encriptado sin perder datos?

El proceso es directo, pero hacé el backup primero. Siempre.

  • Respaldá el estado actual: terraform state pull > backup.tfstate antes de tocar nada.
  • Definí el backend S3: agregá el bloque backend "s3" con encrypt = true a tu configuración.
  • Inicializá: corré terraform init. Va a detectar el estado local y te pregunta si querés migrarlo al backend remoto. Respondé yes.
  • Verificá: terraform state list debe mostrar los mismos recursos que antes.
  • Limpiá: una vez confirmado, borrá el terraform.tfstate local para que no quede una copia sin cifrar dando vueltas.

Si algo sale mal, tenés el backup.tfstate para hacer rollback. Por eso el primer paso no es opcional.

Errores comunes al encriptar el estado de Terraform

Configurás el backend, corrés terraform init, migrás el estado, todo parece andar, y de golpe el pipeline de un compañero falla con un Access Denied porque la policy de KMS nunca le dio permiso de Decrypt y nadie lo había notado hasta ese momento. Estos son los tropiezos que más se repiten:

  • Permisos KMS incompletos: falta kms:Decrypt o kms:GenerateDataKey y el plan revienta con Access Denied. Revisá la key policy y el rol.
  • Olvidar encrypt = true: el archivo se guarda sin cifrar y nadie se da cuenta hasta una auditoría. Confirmá la línea en el bloque.
  • Usar SSE-S3 en producción: te quedás sin auditoría de CloudTrail justo cuando más la necesitás.
  • No versionar el bucket: si un apply corrompe el estado, sin versionado no hay vuelta atrás. Activá versioning en S3.
  • No probar el restore: un backup que nunca restauraste no es un backup, es una ilusión. Testealo.

Preguntas Frecuentes

¿Puedo usar SSE-S3 en lugar de SSE-KMS para el estado de Terraform?

Sí, SSE-S3 cifra el estado con AES-256 gestionado por Amazon y funciona sin configuración extra. La limitación es que no te da auditoría por clave ni control de acceso granular. Para producción conviene SSE-KMS; para pruebas, SSE-S3 alcanza.

¿Qué pasa si borro la tabla de bloqueo de DynamoDB?

Terraform pierde la capacidad de bloquear el estado y dos operaciones simultáneas pueden corromperlo. El backend va a tirar un error al intentar adquirir el lock. La solución es recrear la tabla con el atributo LockID de tipo string antes de seguir operando.

¿Necesito versionar el bucket si ya tengo bloqueo de estado?

Sí, son cosas distintas. El bloqueo evita escrituras concurrentes, pero no te protege de un estado corrupto o borrado por error. El versionado de S3 guarda copias previas del objeto y te permite recuperar una versión anterior. Activá las dos.

¿Cómo verifico que mi estado está realmente encriptado?

Consultá los metadatos del objeto con aws s3api head-object --bucket <bucket> --key <key>. En la respuesta, el campo ServerSideEncryption debe mostrar aws:kms (o AES256 si usás SSE-S3). Si no aparece, el archivo no está cifrado.

¿Cuánto cuesta usar SSE-KMS para el estado?

AWS cobra por la clave KMS gestionada (un cargo mensual por clave) más un cargo por cada request de cifrado y descifrado. Para un estado de Terraform, donde las operaciones son esporádicas, el costo de las requests es marginal. Consultá la calculadora de AWS para tu región.

Conclusión

Encriptar el estado de Terraform no es un lujo, es la diferencia entre un tfstate que protege tus credenciales y uno que las regala. Con cinco líneas en el bloque backend "s3", encrypt = true, una clave KMS y una tabla DynamoDB, cerrás el agujero por donde se filtra el 30% de los estados según HashiCorp.

Si todavía tenés el estado en local o en un bucket sin cifrar, el orden es claro: backup, definir el backend, terraform init, verificar con state list, borrar la copia local. Y para producción, SSE-KMS con auditoría, no SSE-S3. El día que tengas que demostrar quién accedió al estado, esa decisión te va a ahorrar el dolor de cabeza.

Fuentes

Te puede interesar...