Hedronite · Cert-Prep Lesson · HashiCorp / Terraform Associate 003 + Pro-depth · Sun 2026-08-09 · Trio #84

TF Associate 003 — Sensitive Variables and Secrets in State — the flag and the file

What does sensitive = true protect? It protects the terminal.

Lesson Class: Cert-Prep (HashiCorp Terraform Authoring & Operations Associate 003)
Cert Priority: Seat 1 of the banked ordering: Terraform Pro → CKA/CKS → AWS SAP/DOP → GCP PCA → AZ-900
Objectives: 4 (outside core workflow) · 8 (read, generate, modify configuration) · 9 (HCP Terraform capabilities)
Practice Questions: 6 · tap to reveal
Paired Ops: Policy as Code for Terraform on Azure
Paired Dev: Python over the Terraform Plan JSON
Grounding: Brikman TU&R 3ed Ch.6 pp.308-345 · Ch.2 pp.120-121 · Ch.3 pp.150-151 · tfpro-labs 24 + 25
The flag and the file
sensitive = true redacts CLI and log output. The value still lands in state and in the plan file, in the clear.
Sensitivity propagates
A local built from a sensitive variable is sensitive. An unmarked output publishing it errors at plan.
Workspace beats the set
HCP Terraform precedence: variable set, then workspace variable, then command-line -var. The opposite of what people guess.
The flag governs display. The file governs exposure. Candidates who fuse them lose two questions and, later, a credential.

Section IFrame

The exam asks it as a one-liner and the wrong answer feels right: what does sensitive = true protect?

It protects the terminal.

That is the whole answer, and everything else in this lesson follows from it. sensitive = true tells Terraform to print (sensitive value) where it would otherwise print the value. The value still travels to the provider. The value still lands in the state file, in the clear. The value still lands in the plan file, in the clear. One flag, one scope: what a human sees on a screen and what a CI job writes into a build log.

Brikman calls the state situation an open issue since 2014 with no clear plan for a first-class solution (Ch. 6, Plan files, pp. 341-343). The exam does not ask you to fix it. The exam asks whether you know it is true.

Call the pair the flag and the file. The flag governs display. The file governs exposure. Candidates who fuse them lose two questions and, later, a credential.

Section IISensitive Variables (Objective 8)

Input variables

variable "db_password" {
  description = "Database password used by the application."
  type        = string
  sensitive   = true
}

Marking an input sensitive does four things. Terraform redacts it in plan and apply output. Terraform redacts any expression that derives from it, which is the propagation rule the exam likes. Terraform refuses to write it into an unmarked output. And the value still sits in terraform.tfstate as plain JSON.

Propagation is the part worth drilling. Take Lab 24's shape from the Terraform Pro set:

locals {
  connection_string = format("postgres://%s:%[email protected]:5432/app",
                             var.db_username, var.db_password)
}

local.connection_string is now sensitive because one of its inputs was. Terraform tracks the taint through the expression graph. An output that publishes it must declare sensitive = true or the plan errors out before anything runs.

Five ways to supply the value, in Terraform's own precedence order, lowest to highest: the variable's default, the TF_VAR_ environment variable, a terraform.tfvars file, an *.auto.tfvars file, then -var and -var-file on the command line in the order given. The exam tests precedence. Operationally, only the environment variable and a secret store belong anywhere near a real password, because a .tfvars file holding a credential ends up committed by somebody eventually.

Outputs

output "connection_string" {
  value     = local.connection_string
  sensitive = true
}

Brikman states the mechanism plainly in the chapter-two walkthrough: setting the parameter instructs Terraform not to log this output at plan or apply, which is useful when the output carries a password or private key (pp. 120-121).

Then the trap, straight out of Lab 24:

output "password_debug" {
  value = nonsensitive(var.db_password)
}

nonsensitive() strips the marking. It exists for the legitimate case where an operator has satisfied themselves that a derived value carries no secret and the redaction is now hiding a diagnostic they need. Applied directly to a password, it is a deliberate publication. The function does exactly what it says and gives no warning, because the author asserted the value was safe by calling it.

TrapExam tell: a question that shows nonsensitive() wrapping something that came from a sensitive = true variable is asking whether you noticed. The answer is always that the value is now printed.

The third place sensitivity lives

Providers mark attributes sensitive in their own schemas. azurerm_storage_account.primary_access_key, aws_db_instance.password, google_service_account_key.private_key: these arrive redacted whether or not any variable was marked. Terraform respects the provider's declaration. This is why a plan can print (sensitive value) for an attribute the configuration never marked.

Section IIISecrets in State and Plan (Objective 4)

What the state file holds

Every attribute of every managed resource, as returned by the provider, in plain JSON. Password fields included. The sensitive flag never touches this layer.

Three consequences the exam draws on:

  1. The state file must be treated as a secret. Remote backends that encrypt at rest and restrict access by IAM are the answer, and Brikman notes that most remote backends natively support encryption in transit and at rest and expose access controls (Ch. 3, pp. 150-151). Never in version control.
  2. terraform_remote_state reads everything. A data source pointing at another stack's state gives the reader the whole file, not the outputs they asked for. Access to a state file is access to every secret inside it.
  3. terraform state pull and terraform show -json render the same contents to a local terminal. The 08-03 lesson used state pull for surgery. It is also the fastest way to print a database password to stdout.

What the plan file holds

The same secrets. terraform plan -out=tfplan writes the resolved values into the binary artifact, and Brikman's caution runs alongside the terraform apply example.plan workflow: the secrets pass into resources and data sources, so they are in there (Ch. 6, pp. 341-343).

This is the direct interlock with today's other two lessons. Both of them read the plan file, one through a policy engine and one through Python. A plan artifact uploaded to a build-artifact store, or left in the workspace for the next job, has handed the credential to every reader of that store.

The four ways to supply a secret, and what each costs

Brikman's chapter-six comparison closes on the working rule: for resources and data sources, use environment variables, encrypted files, or a secret store (pp. 343-345).

Mechanism Where the secret lives Cost
Environment variables (TF_VAR_db_password) The CI system's secret store Free. Nothing durable to manage. Not everything is expressible; the CI system becomes the secret store by default.
Encrypted files (SOPS, aws kms, sops + KMS key) Version control, encrypted Works across providers. Key rotation is manual, and compromise of the key exposes every secret ever encrypted with it (pp. 328-329, pp. 334-337).
Secret store (Vault, AWS Secrets Manager, Azure Key Vault) The store Access auditing, rotation, fine-grained policy. Runs money, and inside-vs-outside the cloud provider decides which options remain (pp. 343-345).
Hardcoded in .tf Version control, clear Never.

Brikman also separates the three kinds of secret at p. 308: personal secrets, customer secrets, and infrastructure secrets. Most tools store exactly one kind well. Forcing a tool to hold a kind it was not built for is a security problem rather than an inconvenience.

Section IVHCP Terraform Policy Enforcement (Objective 9)

The run pipeline

plan  →  cost estimation  →  policy check  →  apply

Policy runs after the plan because it reads the plan. Policy runs after cost estimation because a Sentinel rule can read the cost delta from tfrun and refuse on price.

Three enforcement levels

Level Effect on a failing run Override
advisory Logged, displayed, run continues Not applicable
soft-mandatory Run stops A user with override permission on the workspace
hard-mandatory Run stops None. Change the code or change the policy.

OPA policy sets on HCP Terraform carry advisory and mandatory only. A question naming three levels is asking about Sentinel.

Lab 25 of the Pro set frames the design question the exam only gestures at: map the producer-consumer workspace topology, then decide where policy checks should block applies, and choose plan-only versus apply permission boundaries. Those three decisions are one decision seen from three sides.

Variables in HCP Terraform

Workspace variables carry two flags the local workflow has no equivalent for. HCL marks the value as an expression rather than a string. Sensitive write-onlys the value: it can be set and replaced, never read back, not by the UI and not by the API.

Variable sets attach one collection of variables to many workspaces, which is how a cloud credential reaches forty workspaces without forty copies. Precedence runs from lowest to highest: variable set, then workspace variable, then command-line -var on an API-driven run. The workspace value beats the set. That ordering is exam material and it is the opposite of what people guess, because they reason from "the set is more central, so it must win."

Permissions as the third gate

Plan-only access and apply access are separate grants. A reviewer who can queue a plan and read its output, and cannot apply, is the human half of the same discipline the policy check automates. The 07-28 lesson took HCP Terraform on the provider and workspace side. This is the governance side.

Section VPractice Questions

Question 1
A variable block sets sensitive = true. After terraform apply, where can the value be read in plain text?
Tap to reveal
In the state file, and in any plan file written with -out. The flag redacts CLI and log output only. It changes nothing about storage.
Question 2
An output publishes a value derived from a sensitive variable through a local, and the output block has no sensitive argument. What happens?
Tap to reveal
terraform plan errors. Terraform propagates sensitivity through the expression graph, and refuses to publish a sensitive-derived value through an unmarked output. Add sensitive = true to the output, or use nonsensitive() if the derived value genuinely carries no secret.
Question 3
Which supplies a variable value at the highest precedence: TF_VAR_region, a terraform.tfvars entry, or -var="region=eastus"?
Tap to reveal
The command-line -var. Order lowest to highest: default, TF_VAR_, terraform.tfvars, *.auto.tfvars, then -var and -var-file in the order given on the line.
Question 4
A team publishes a database password through terraform_remote_state so a downstream stack can read it. What is the exposure?
Tap to reveal
The data source grants read access to the entire remote state file, not only the outputs referenced. Every secret in that state is now readable by every principal that can read the backend object. Pass the secret through a secret store and have the downstream stack read it directly.
Question 5
A Sentinel policy is attached at soft-mandatory. A run fails the policy. Who can proceed, and what is the record?
Tap to reveal
A user holding override permission on that workspace. The override is recorded against the run with the user's identity. hard-mandatory admits no override; advisory never stops the run in the first place.
Question 6
A variable is defined in a variable set attached to a workspace, and also as a workspace variable with a different value. Which applies?
Tap to reveal
The workspace variable. Workspace-level beats variable-set-level; a command-line -var on an API-driven run beats both.

Section VIExam Notes and Traps

Section VIIClosing

The flag governs the screen. The file governs the secret. Hold the two apart and six questions on this exam become one question you already answered.

Then carry the operational half into your own repositories. Put the state in a remote backend with encryption and access control, and treat every principal who can read that backend as a principal who knows every password inside it. Keep plan files out of build-artifact stores. Supply credentials through the environment or a secret store, and let the .tfvars file hold the region and the instance count and nothing that would matter if it appeared in a pull request.

Sit Lab 24 before the next TF day. Fix the outputs it breaks on purpose, then read the state file it produces and find the password anyway.

Examine well.

Related

🫡 ⚖️ 📜
Leo.Syri — Praetor Consulate, Imperium Luminaura
Filed 2026-08-09 · Fajr trio #84 · sprint day 18 · TF track
Paired: 01-Earth-DevOps/Synthesis-Lessons/2026-08-09-policy-as-code-for-terraform-on-azure/ · Polyglot-Dev/Python/2026-08-09-python-over-the-terraform-plan-json/