terraform plan responde «no changes». Al mismo tiempo, en la consola de la nube, un security group tiene el SSH abierto a todo internet. Plan no está roto ni miente: sencillamente no puede verlo, porque la regla se creó a mano, no está en el state, y plan compara el state con la configuración, no la nube con tu intención.
Ese es uno de los cuatro puntos ciegos por los que plan -detailed-exitcode no sirve por sí solo como detector de drift.
Lo que plan no ve
- Recursos fuera del state. Lo creado al margen de Terraform no existe para él.
- Ficheros de state separados. El drift de uno no se ve desde otro; cuanto más limpiamente se reparte la infraestructura, más zonas ciegas aparecen entre las piezas.
- Cambios del lado del proveedor. El proveedor se actualizó, los valores por defecto se movieron: plan dice «no changes» mientras el recurso en la nube ya es distinto.
- Semántica. Plan compara lo deseado con lo real, pero no comprueba si lo deseado es siquiera admisible: el SSH público y el cifrado desactivado los muestra como el estado normal de las cosas.
Cuatro capas, no una herramienta
Cada punto ciego lo cierra su propia capa. Ninguna sustituye a las demás.
| Capa | Con qué | Qué cubre |
|---|---|---|
| L1 | plan -detailed-exitcode (código 2 = hay diff) | drift en recursos gestionados |
| L2 | inventario de la nube: AWS Config, Steampipe | recursos fuera del state, cumplimiento en toda la cuenta |
| L3 | motor de políticas: OPA, Conftest, Sentinel | semántica sobre la salida de plan |
| L4 | eventos: EventBridge → Lambda → notificación | cambios manuales en consola, en el momento del cambio |
L3 merece una nota aparte: trabaja contra el plan, no contra la nube. La tubería terraform show -json plan.tfplan | conftest test -p policies/ convierte la comprobación de políticas en una puerta previa al apply, el mismo mecanismo por el que la seguridad entra en la cadena en sus otras etapas.
La mitad de las causas del drift no son infracciones
La lista de causas suele leerse como un registro de faltas: cambios en la consola, hotfixes al margen del CI, automatización ajena, otras herramientas sobre los mismos recursos. Pero hay dos causas más en las que nadie ha infringido nada.
La primera son los valores por defecto del proveedor: la nube cambió un default entre versiones. La segunda son los cambios ligados al tiempo: rotación de contraseñas, renovación de certificados, autoescalado de capacidad. Unas veces queda en el state, otras deriva.
De ahí la conclusión práctica: un sistema que responde a todo drift con un rollback acabará revirtiendo con regularidad la vida normal de la infraestructura. Separar lo que se corrige solo de lo que se le muestra a una persona no es un detalle de configuración: es la decisión de diseño principal.
Qué empuja hacia la corrección automática
Una encuesta de ControlMonkey aporta tres cifras: el 71% de los equipos dice que las herramientas generativas aumentan su volumen de IaC, el 63% dice que esa infraestructura es más difícil de gobernar que la escrita a mano, y el 81% dice que la revisión manual no aguanta ese ritmo.
La salvedad es obligatoria: ControlMonkey vende una plataforma de IaC, y la conclusión de que la detección sin corrección se apagará en 2026 es su pronóstico, no un hallazgo neutral. Las cifras de volumen son verosímiles y coinciden con lo que se ve alrededor; la conclusión la saca quien vende la solución.
La misma observación en versión prudente: cuantos más cambios por unidad de tiempo, más cara sale una cola de alertas que nadie atiende. Una alerta sin destinatario no es observabilidad, es un incidente aplazado.
Remediation as code: dónde está la frontera
Corregir automáticamente no significa «revertirlo todo». En la práctica son tres decisiones distintas, y conviene tomarlas de antemano y no en el momento en que algo salta.
Qué revertir automáticamente. Una lista estrecha en la que el estado correcto se conoce y es inequívoco: acceso público, cifrado desactivado, etiquetas obligatorias ausentes. Aquí la automatización encaja, porque la alternativa es un agujero abierto esperando a una persona.
Qué absorber en el state. Un cambio manual legítimo no debe figurar como drift para siempre. Para eso está plan -refresh-only: sincroniza el state con la realidad y no planifica ninguna acción sobre los recursos. Después el cambio pasa al código mediante un PR normal.
Qué tratar como incidente. Todo lo demás. El drift en producción es un P2: capturar el artefacto, mirar en el registro de auditoría quién cambió qué y cuándo, decidir entre avanzar o revertir, escribir el postmortem. El postmortem aquí no es un ritual: es la única forma de notar que el mismo drift vuelve siempre por el mismo motivo.
La protección estructural gana a la detección
El drift más barato es el que no puede ocurrir.
- Prohibir las mutaciones a todos menos a Terraform. Una política IAM que rechaza las acciones modificadoras a cualquier principal no marcado como gestionado por Terraform. No es una comprobación, es un muro: el cambio manual no se detectará porque no llegará a producirse.
- Poner
ManagedBymediante losdefault_tagsdel proveedor: una vez en el proveedor y no en cada recurso. Sobre esas etiquetas se apoya el punto anterior. - Incorporar lo existente con bloques
importen lugar del historial de la shell: la importación se convierte en un cambio corriente, visible en un PR y discutible.
La política como código actúa aquí de acelerador y no de freno, pero solo si las reglas se escriben junto al código y no encima de él; sobre esa paradoja hay una conversación aparte.
Herramientas: una salvedad honesta
A menudo se recomienda driftctl como escáner independiente de los recursos que faltan en el state. La herramienta funciona, pero desde el 29 de junio de 2023 el proyecto está en modo mantenimiento: los autores dicen sin rodeos que no pueden prometer revisar las contribuciones. Para una auditoría puntual no es problema. Para una capa L2 permanente sí: no llegan tipos de recurso nuevos, y es justo ahí donde se acumula lo que pasa inadvertido. Una capa permanente se sostiene mejor sobre el inventario de la propia nube.
Aparte, sobre la tentación de dejarle el drift a un controlador GitOps. El patrón «Argo CD lanza un Job con terraform apply» parece elegante y se rompe en lo esencial: el Job se ejecuta una vez, no hay reconciliación continua. Los recursos de nube necesitan o una cadena de CI honesta o un control plane que reconcilie, pero eso ya es otra herramienta y otra conversación sobre el state.
Por dónde empezar si hoy solo tienes L1
- Un
plan -detailed-exitcodenocturno por cada state; el código 2 abre un ticket con el diff en el cuerpo. Es lo más barato y enseña la magnitud de inmediato. default_tagsconManagedByy la denegación IAM de mutaciones al margen de Terraform. Dos pasos, y el flujo de drift nuevo se estrecha mucho.- Una puerta de políticas sobre el plan antes del apply: empezar por dos o tres reglas que no se pueden romper nunca.
- Inventario de la nube para los recursos fuera del state. A estas alturas los hallazgos serán pocos y manejables.
La corrección automática va la última y sobre una lista estrecha, cuando ya se sabe qué drift es normal en tu infraestructura y cuál no. Si no, la automatización empieza a arreglar lo que nunca estuvo roto.