DevOps Peach

Advanced Terraform · OpenTofu, Terragrunt and Security · Lesson 1 of 4

Secrets: sensitive, ephemeral and write-only

sensitive only hides values in output; ephemeral values and write-only arguments keep secrets out of plan and state, and secret stores keep them out of code.

Practice interactively →

Secrets leak from Terraform in three places: code (a password in a .tf or .tfvars file), output (plan and CI logs) and state. Each tool below closes a different gap.

flowchart LR
  VS["Vault / Secrets Manager"] -->|"ephemeral resource"| EP["value used during the run only"]
  EP -->|"write-only argument"| RES["database password set"]
  RES --> ST["state: no password stored"]
  SV["sensitive variable"] --> LOG["plan output shows (sensitive value)"]
  %% step VS: The secret lives in a secret store, never in the repository.
  %% step EP: An ephemeral resource reads it for this run only; Terraform never writes it to plan or state.
  %% step RES: A write-only argument sends the value to the API without storing it.
  %% step ST: The state keeps only a version number, so reading the state reveals nothing.
  %% step SV: sensitive = true hides a value in CLI output and logs, but on its own it does not keep it out of state.

sensitive (hides output only)

variable "db_password" {
  type      = string
  sensitive = true
}
output "db_url" {
  value     = "postgres://app:${var.db_password}@db:5432/shop"
  sensitive = true          # required: an output built from a sensitive value must be marked
}

Plan prints (sensitive value). The value is still in state in plain text, and terraform output -raw db_url still prints it.

ephemeral (Terraform 1.10+)

Ephemeral variables, outputs and resources exist only during a run and are never written to plan files or state.

ephemeral "random_password" "db" {
  length = 24
}

variable "api_token" {
  type      = string
  ephemeral = true           # may only be used where ephemeral values are allowed
}

Ephemeral values can be used in provider configuration, in other ephemeral blocks, and in write-only arguments.

Write-only arguments (Terraform 1.11+)

resource "aws_db_instance" "shop" {
  # ...
  password_wo         = ephemeral.random_password.db.result
  password_wo_version = 1     # bump to rotate the password
}

The provider sends password_wo to the API but Terraform stores only password_wo_version. Look for _wo arguments in provider docs; not every resource has them yet.

Where secrets should come from

Source Ends up in state?
Literal in .tf / committed .tfvars Yes, and in Git: never do this
TF_VAR_db_password from the CI secret store Yes, if used in a normal argument
data "aws_secretsmanager_secret_version" Yes: data source results are stored
ephemeral "aws_secretsmanager_secret_version" + write-only argument No
Let the service generate and keep it (managed master password) No

Protect the state anyway

Even with all of this, older providers and many resources still store secrets. Encrypt the backend (S3 SSE-KMS), restrict who can read it, enable versioning and audit access, or use OpenTofu's client-side state encryption (topic 5.2).

Traps: nonsensitive() removes the protection and prints the value in logs; and an output that depends on a sensitive value fails with an error until you mark the output sensitive too.

Practice interactively →