Chapter 3: State
Topic 21

Sensitive Data in State

SecretsState

Terraform state stores resource attributes in plaintext, and some of those attributes are secrets — RDS master passwords, generated private keys, access tokens. Marking a value sensitive hides it from CLI output and does nothing whatsoever to the bytes on disk.

Protecting secrets in Terraform is therefore not about the sensitive flag. It is about controlling the state backend — encryption, access, audit — and minimizing how many secrets ever land in state to begin with.

What actually protects a secret
sensitive flag
Hides the value in CLI and log output only. The same secret is still written to state in full plaintext — cosmetic for the file itself.
Encrypted backend
SSE-KMS on the state bucket, tight access policy, and audit logging. The real control over who can read the plaintext on disk.

What Ends Up in State

State records every attribute a resource exposes, including the ones you most want hidden. An aws_db_instance with an inline password, a tls_private_key's PEM, a random_password result — all of them sit in the state file in the clear, exactly as the provider returned them. There is no per-attribute filter that keeps a "secret" attribute out of state; if Terraform manages the resource, the secret is in its state.

The sensitive Flag's Real Scope

The sensitive flag controls display, not storage. On a variable or an output it replaces the value with (sensitive value) in plan and apply output and in terraform output, which keeps the secret out of your terminal and out of CI logs. That is genuinely worth doing — leaked logs are a real exposure path — but it stops at the screen. The same value is written to state in full plaintext, so anyone who can read the state file reads the secret regardless of the flag.

sensitive suppresses output — not state
output "db_password" {
  value     = aws_db_instance.main.password
  sensitive = true   # hides it from CLI/logs; still plaintext in state
}

Securing the Backend

Because the flag does not protect the file, the file itself is what you protect. On an S3 backend that means encryption at rest with SSE-KMS, a bucket policy that grants read to the smallest possible set of principals, blocked public access, and access logging so you can see who read state. KMS encryption is the part that actually keeps the plaintext secrets from being readable by anyone who merely lists the bucket. This is the real control; sensitive is cosmetic by comparison.

Minimizing Secrets in State

The strongest move is to keep secrets out of state in the first place. Rather than having Terraform generate a database password with random_password and hold it forever, store the secret in SSM Parameter Store or Secrets Manager and have the application read it at runtime. Terraform manages the secret's container and access policy, not the secret value, so the plaintext never lands in state. Every secret you keep out of state is one you do not have to encrypt, lock down, and rotate after a leak.

Reference a managed secret instead of generating one
# Terraform manages the secret container, not the value
data "aws_secretsmanager_secret_version" "db" {
  secret_id = "prod/db/password"
}

resource "aws_db_instance" "main" {
  username = "admin"
  password = data.aws_secretsmanager_secret_version.db.secret_string
}

OpenTofu State Encryption

This is one of the genuine divergences between the two tools. OpenTofu ships client-side state encryption: it can encrypt the state file's contents before they ever reach the backend, so the secrets are not plaintext even inside the bucket. Terraform does not have this yet — its protection stops at the backend's own encryption at rest, which the cloud provider holds the keys to. If client-side state encryption is a hard requirement, that is a concrete reason to weigh OpenTofu.

Common Mistakes
  • Believing sensitive = true encrypts the value, then leaking it through a state file stored without encryption.
  • Generating database passwords with random_password and leaving them in state with no backend encryption or access control.
  • Outputting a secret without sensitive, printing it into CI logs that are retained and searchable.
  • Granting broad read access to the state bucket, so anyone with bucket read can extract every secret in plaintext.
  • Never rotating a secret that lived for months in an under-protected state file, leaving a known-exposed credential in use.
Best Practices
  • Encrypt state at rest with S3 and KMS, and lock the bucket down to the smallest set of principals.
  • Keep secrets out of state where possible — reference Secrets Manager or SSM at runtime rather than managing the value in Terraform.
  • Mark every secret output sensitive so it does not print into logs.
  • Audit and rotate any secret that has lived in an under-protected state file.
  • Weigh OpenTofu's client-side state encryption when plaintext-in-the-bucket is unacceptable for your threat model.
Comparable tools CloudFormation uses dynamic references to Secrets Manager/SSM to avoid embedding secrets Pulumi encrypts secrets in its state by default OpenTofu adds client-side state encryption Terraform lacks

Knowledge Check

What does marking a value sensitive actually do?

  • Suppresses it from plan, apply, and output display — it does not encrypt the value in state
  • Encrypts the value inside the state file using a KMS key you supply
  • Keeps the value out of the state file entirely, so that it is never once persisted to disk anywhere
  • Rotates the value automatically on every apply that touches the resource

Why is state a secrets-exposure risk even when values are marked sensitive?

  • The flag only hides values from output; the same secrets are written to the state file in plaintext
  • Sensitive values are encrypted in state, but the decryption key is stored right next to them in the same file
  • The flag works only for input variables and never for resource attributes in state
  • It is not actually a risk, since sensitive values are omitted from the state file

What most reduces the number of secrets stored in state?

  • Storing secrets in Secrets Manager or SSM and reading them at runtime instead of generating them in Terraform
  • Marking every single secret output and variable as sensitive across every module in the full configuration tree
  • Enabling object versioning on the S3 bucket that holds the state file
  • Increasing the parallelism setting so that state is written out less often

How do Terraform and OpenTofu differ on state encryption?

  • OpenTofu offers client-side state encryption; Terraform relies on the backend's encryption at rest and lacks it
  • Terraform encrypts state client-side, while OpenTofu has no such feature and relies only on the backend's at-rest encryption
  • Both tools encrypt the state file client-side automatically by default
  • Neither tool can encrypt state at all; both store it in plaintext only

You got correct