A Terraform plan can look clean while the secret is still embedded in an artifact. That is the first control failure this audit is designed to catch. sensitive = true is useful, but it is a presentation control: HashiCorp documents that sensitive values are redacted in normal CLI output while remaining in plan and state. Terraform 1.10 added ephemeral variables, child-module outputs and ephemeral blocks that can omit values from those artifacts, and Terraform 1.11 added managed-resource write-only arguments. Those are feature minima, not a reason to standardize on an old patch release. HashiCorp Developer, Manage sensitive data in your configuration (living documentation; accessed September 17, 2026).
The engineering question is therefore not “Can operators see the password in the plan?” It is “Across the complete value path, which systems retain the value, which Terraform artifacts omit it, who owns each copy, and can the application still authenticate and rotate safely?” HashiCorp’s dated explanation of ephemeral values was published March 10, 2025, so this is an established capability to qualify in an estate, not a September 2026 launch. HashiCorp, Ephemeral values in Terraform (published March 10, 2025; accessed September 17, 2026).
The worked example below is synthetic and proposed, not executed. It uses a deliberately non-sensitive canary and a version-pinned AWS provider reference only where the documented argument has been checked. The scope is HashiCorp Terraform; no OpenTofu parity is assumed. The goal is a defensible persistence boundary, not a claim that every historical copy has been erased.
Set the Persistence Boundary Before Changing Configuration
Start by writing the acceptance claim before editing HCL. A useful claim is narrow: “For credential X, the clear value must be absent from the new Terraform plan and current state, may exist transiently in the provider process during the operation, must exist in approved destination Y, and must remain retrievable by consumer Z under defined authorization.” That wording separates Terraform non-persistence from the credential’s intended existence.
HashiCorp explicitly separates redaction from omission: ephemeral values are available at runtime but omitted from state and plan, while remote-state encryption is a separate storage control. Encryption protects a retained artifact; it does not make the value absent from that artifact.
Boundary point | Required decision | Evidence to retain |
Configuration / input | No real secret literal in tracked HCL; approved injection path identified | Commit ID, variable definition, CI/input owner |
Plan | Secret canary absent where ephemerality is claimed | Saved-plan artifact ID and restricted scan result |
Current state | Canary absent from inspected current state paths | State version/workspace ID and scan result |
Provider runtime | Secret may exist transiently only as required to call the API | Provider/resource schema evidence; logging review |
Intended secret store | Credential exists intentionally and is access-controlled | Secret identifier/version, read-policy owner |
Managed database | Password is usable by the database after apply | Synthetic connection test record, never the password |
Historical copies | Reviewed separately; no universal erasure claim | Version/backup/log inventory and disposition |
Local policy: accept “Terraform secret persistence reduced” only for the specific artifact set actually inspected. Do not turn “not found in current state” into “does not exist anywhere.” Backups, state-history versions, CI attachments, terminal capture and debug output belong to separate retention domains.
That boundary also answers a common design mistake: if Terraform writes a password into AWS Secrets Manager through a write-only argument, the password is supposed to survive there. “Omitted by Terraform” and “deleted everywhere” are different outcomes. The destination is part of the system’s credential lifecycle, not an exposure defect merely because it persists.
A good boundary statement names the subject value and the representation being tested. A password may be transformed into JSON, passed through a local, or copied into a connection string. Searching only for an input variable name is therefore inadequate. In the synthetic rehearsal, record the literal canary plus any deterministic representation the configuration intentionally creates. Do not add hashes or encodings merely to broaden a scan unless the transformation is known; a negative search across guessed encodings creates confidence without evidence.
Inventory the Secret Path and Its Owners
Before migration, draw the path at value level rather than resource level. A database resource may end with a write-only password field while an upstream ordinary random_password resource, normal variable output, generated file, or CI artifact still retains the same value. HashiCorp notes that write-only arguments accept both ephemeral and non-ephemeral inputs; therefore the sink’s write-only status does not retroactively make the producer ephemeral. HashiCorp Developer, Use temporary write-only arguments (living documentation; accessed September 17, 2026).
Use the existing estate map, not a generic tool inventory. Related Terraform and Ansible fundamentals can provide broader IaC context, but this audit is narrower: identify every edge that can carry this one credential.
Value-flow edge | What to inventory | Primary owner | Failure to flag |
Producer | Root input, external secret store, generator, ordinary resource | Secret owner | Producer itself persists the value |
Module boundary | Variable type, sensitive, ephemeral, child output | Module maintainer | Ephemeral value converted into persistent output/context |
Provider call | Provider version, managed/ephemeral resource, exact field | Terraform maintainer | Field unsupported or ordinary |
Terraform artifacts | Plans, current state, local state, caches | State-access owner | Canary found or artifact inaccessible |
Diagnostic path | CI console, debug logs, crash files, attachments | CI/platform owner | Unreviewed copy or overbroad retention |
Intended destination | Secret manager, database endpoint | Service owner | Secret cannot be recovered by authorized consumer |
Application use | Retrieval identity, refresh/restart behavior | Application owner | New credential stored but not consumed |
Assign two ownership roles explicitly. The secret owner decides what the authoritative credential is, who may retrieve it, and when the old version becomes invalid. The state-access owner controls who can read Terraform plans/state and how current and historical artifacts are retained. One person may hold both roles locally, but the responsibilities should remain distinct in the evidence record.
The deliverable is a value-flow table with a disposition for each edge: persistent-approved, ephemeral, write-only, unknown, or exception. “Sensitive” is not a persistence disposition. An edge marked unknown blocks approval because a clean final database resource cannot compensate for an unclassified upstream source.
Also inventory fan-out. A single variable can feed a database password, a templated file, a module output and a secret-store resource. Migrating one branch to a write-only argument leaves the other branches unchanged. The audit should therefore assign one row per consumer edge, not one row per variable name. That is the difference between checking a configuration label and tracing the actual secret data flow.
Separate Sensitive, Ephemeral and Write-Only Behavior
The three mechanisms solve different problems. HashiCorp’s current documentation lists Terraform 0.15+ for sensitive on variables/outputs, 1.10+ for ephemeral variables, child-module outputs and ephemeral blocks, and 1.11+ for managed-resource write-only arguments. Provider support is an additional prerequisite.
Mechanism | Display behavior | Persistence and permitted use |
sensitive = true | Normal CLI/UI presentation is redacted | Value is still stored in plan/state; expressions remain sensitive |
ephemeral = true / ephemeral block | Can be combined with sensitive for redaction | Value omitted from plan/state; references restricted to documented ephemeral contexts |
Provider write-only argument | Secret value has no stored plan/state value for Terraform to compare | Passed to provider during an operation; exact field and version behavior are provider-specific |
An ephemeral Terraform value is also not the same thing as a short-lived cloud object. An ephemeral resource block has a special Terraform lifecycle and is omitted from plan/state; that does not imply that a password sent to a remote database expires when the Terraform run ends. The remote system’s credential lifetime is governed by that system and your rotation policy. HashiCorp Developer, Ephemeral values in resources (living documentation; accessed September 17, 2026).
Redaction Is a Presentation Control
Use a synthetic canary such as TF_EPHEMERAL_CANARY_DO_NOT_USE_7KQ9 in an isolated workspace. With only sensitive = true, the expected CLI presentation is redacted; HashiCorp nevertheless documents that sensitive values remain in state and plan, and that some explicit output modes can reveal sensitive outputs in clear text.
Observation | What it establishes | What it does not establish |
Plan shows (sensitive value) | Normal presentation redaction works | Secret is absent from saved plan |
Apply output is redacted | Normal operation log masks the value | State, debug logs and history are clean |
Current state scan misses canary | Canary absent from inspected representation | Historical/encoded/external copies do not exist |
Do not test this by dumping real secret-bearing state to a terminal. The proposed lab contains no production secrets, and its artifacts remain restricted because the same inspection procedure will later be reused under change control.
Non-Persistence Must Survive the Whole Path
HashiCorp restricts ephemeral references to specific contexts, including ephemeral variables, child-module ephemeral outputs, managed-resource write-only arguments, ephemeral blocks, provider configuration, and provisioner/connection contexts. This restriction is valuable: it prevents an ephemeral value from silently flowing into an ordinary persistent argument.
Classify every edge before rewriting it. If a path is ephemeral input -> ephemeral child output -> write-only secret-store argument -> ephemeral secret-store read -> write-only database argument, it is a candidate. If an ordinary stateful producer sits at the start, the path is not end-to-end ephemeral even though the last edge is write-only. The design must change, or the retained producer becomes an explicit exception.
Verify the Terraform and Provider Compatibility Contract
Language support and provider support are separate gates. On September 17, 2026, HashiCorp’s living Terraform docs selector presents v1.16.x as the latest stable documentation line and v1.17.x as beta. The 1.10/1.11 numbers are minimum feature versions, not production recommendations. Record the actual terraform version, not the documentation selector, during the lab.
The provider contract must be equally concrete. HashiCorp’s AWS provider release page lists v6.65.0 as latest, released September 16, 2026. Its pinned v6.65.0 documentation marks aws_db_instance.password_wo as Write-Only and requires password_wo_version; it also marks aws_secretsmanager_secret_version.secret_string_wo as Write-Only and requires secret_string_wo_version.
Contract item | Synthetic candidate | Evidence required before apply |
Terraform CLI | Organization-approved 1.16.x patch | terraform version captured in run record |
AWS provider | hashicorp/aws = 6.65.0 | .terraform.lock.hcl selection + checksum |
Secret destination | aws_secretsmanager_secret_version.secret_string_wo | Pinned v6.65.0 resource reference + installed schema |
DB sink | aws_db_instance.password_wo | Pinned v6.65.0 reference + installed schema |
Companion versions | secret_string_wo_version, password_wo_version | Provider docs and local rotation test |
Ephemeral producer/read | Ephemeral input; ephemeral "aws_secretsmanager_secret_version" | Terraform/Core support + provider ephemeral schema |
Terraform’s dependency lock file records selected provider versions and package checksums, and terraform providers schema -json prints detailed schemas for providers in the current configuration, including ephemeral-resource schemas. Capture both as evidence. Do not treat a web example as proof that the installed provider exposes the same field. HashiCorp Developer, Dependency Lock File and terraform providers schema command (living documentation; accessed September 17, 2026).
There is a material documentation inconsistency worth recording. The pinned AWS v6.65.0 aws_db_instance reference labels password_wo Write-Only and requires the version field, yet adjacent prose still says the password will be stored in state. That sentence conflicts with Terraform Core’s documented write-only contract. I would not quietly choose one text: mark the discrepancy, inspect the installed schema, and require the synthetic canary test before approval.
Schema capture should be reproducible. Preserve the configuration commit, lock file, CLI version, provider package selection and a restricted copy or digest of the relevant schema extract. A reviewer should be able to answer “which exact binary contract was approved?” without depending on what latest means later. The lock file does not pin Terraform itself, so the run record must carry both CLI and provider evidence.
This is also why state and backend migration boundaries stay out of scope: changing engines or backends is not evidence that a particular secret-bearing value is ephemeral.
Carry Temporary Values Across Module Boundaries
The proposed module contract should make persistence intent part of the type boundary. Terraform 1.10+ permits ephemeral = true on input variables and child-module outputs, and the same blocks can also be marked sensitive = true for presentation redaction.
The following is a synthetic, unexecuted module fragment. It assumes an organization-approved Terraform 1.16.x patch. It is deliberately small so the review can focus on value semantics rather than infrastructure completeness.
# modules/credential_passthrough/variables.tf
variable "db_password" {
type = string
sensitive = true
ephemeral = true
}
# modules/credential_passthrough/outputs.tf
output "db_password" {
value = var.db_password
sensitive = true
ephemeral = true
}At the root, the input has the same classification:
variable "db_password" {
type = string
sensitive = true
ephemeral = true
}
module "credential_passthrough" {
source = "./modules/credential_passthrough"
db_password = var.db_password
}Validation step | Expected result | Acceptance evidence |
terraform validate on positive fixture | Configuration accepted on pinned CLI/provider set | Command status and version metadata |
Reference output in a write-only argument | Allowed ephemeral flow | Schema + plan evidence |
Negative fixture routes value into ordinary persistent argument | Validation/planning rejected | Error class recorded, secret value not copied into report |
Search proposed artifacts for canary | Canary absent from inspected plan/state | Artifact IDs and scoped scan status |
Keep Child-Module Outputs Ephemeral
The child output above is a transit edge, not a secret-export API. HashiCorp explicitly documents that child-module outputs can be ephemeral but root-module outputs cannot be marked ephemeral. A root output therefore cannot be used as a general mechanism to export a non-persistent password from Terraform.
Module boundary | Allowed design | Review action |
Root input -> child input | Both ephemeral | Verify declarations and consumer context |
Child output -> root write-only argument | Child output ephemeral | Verify no ordinary intermediate output |
Root output of secret | Not an ephemeral export mechanism | Reject; use approved destination/retrieval path |
The practical rule is to terminate Terraform’s knowledge of the secret at an approved write-only or ephemeral context, while arranging durable recovery through the secret system intended to own the credential.
Keep Provider Authentication Separate from Resource Data
Terraform can use ephemeral variables in provider configuration, which is useful for short-lived API tokens, but that credential path is separate from the database password configured on a managed resource. Fixing one does not validate the other.
Credential class | Consumer | Persistence question | Owner |
Terraform provider credential | Cloud/provider API client | Is the token persisted in Terraform inputs, logs or runner artifacts? | Platform/IAM owner |
Managed DB password | Database service and application | Is the password retained only in approved destination and remote DB? | DB/secret owner |
Local policy: maintain two evidence rows even if both values originate from the same secret platform. Provider authentication is about Terraform’s authority to act; the database credential is application data being configured by that action.
Design a Write-Only Credential Destination
A usable non-persistence design needs an authoritative destination. HashiCorp’s documented pattern stores an ephemeral password in AWS Secrets Manager through secret_string_wo, reads it with an ephemeral aws_secretsmanager_secret_version, and passes it to RDS through password_wo. Terraform omits the write-only values while AWS Secrets Manager intentionally retains the secret value.
For the pinned AWS provider v6.65.0 used in this article, the resource reference says secret_string_wo stores encrypted text in the secret version and requires secret_string_wo_version; the ephemeral resource reference exposes the decrypted secret_string transiently and supports secret_id.
This synthetic, unexecuted fragment uses only those verified argument names. Surrounding database, network, backup and access configuration is intentionally omitted, so it is not an apply-ready production template.
terraform {
required_version = ">= 1.16, < 1.17"
required_providers {
aws = {
source = "hashicorp/aws"
version = "= 6.65.0"
}
}
}
variable "credential_version" {
type = number # persistent change token; not the password
}
resource "aws_secretsmanager_secret_version" "db" {
secret_id = aws_secretsmanager_secret.db.id
secret_string_wo = module.credential_passthrough.db_password
secret_string_wo_version = var.credential_version
}
ephemeral "aws_secretsmanager_secret_version" "db" {
secret_id = aws_secretsmanager_secret_version.db.secret_id
}
resource "aws_db_instance" "db" {
# Required non-secret DB settings intentionally omitted.
password_wo = ephemeral.aws_secretsmanager_secret_version.db.secret_string
password_wo_version = aws_secretsmanager_secret_version.db.secret_string_wo_version
}Source strategy | Terraform persistence target | Recovery property | Decision |
Externally generated, ephemeral input | Omit from plan/state; write to approved secret store | External source or stored destination must be authoritative | Proceed if retry semantics are stable |
Secret-manager-backed existing value | Read through verified ephemeral resource; write-only to DB | Secret manager remains authoritative | Preferred when consumer retrieval already exists |
Ephemeral generated value | Omit from Terraform artifacts | Must be captured exactly once in approved destination before loss | Hold until retry/partial-failure behavior is proven |
Ordinary stateful producer -> write-only sink | Sink omits, producer still retains | Terraform state remains a secret store | Exception or redesign |
The application should obtain the password from the approved secret destination under its own runtime identity, not through a Terraform root output. That is an authorization and application-design decision, separate from Terraform omission. Broader database administration automation is related operational context, but this design reviews only the credential edge.
Rehearse Rotation Without Losing the Application Credential
Rotation is where a “clean state” design can fail operationally. Use two synthetic values, CANARY_OLD and CANARY_NEW, never production passwords. The rehearsal must prove that the intended secret version is recoverable, the application can adopt it, the database can accept it, and partial failures have an owner.
Local policy: the rotation sequence is: acquire the intended new value or authoritative secret version; stage the consumer to retrieve it; review the persistent version token; apply the write-only update; verify new authentication; assess old authentication according to service policy; preserve only non-secret recovery evidence.
Rotation checkpoint | Evidence | Stop condition |
Intended version selected | Secret/version identifier and change record | New value would be regenerated on retry |
Consumer prepared | Retrieval authorization and refresh method reviewed | Application cannot fetch intended version |
Terraform change reviewed | Companion version change visible; secret value absent | Ordinary secret-bearing edge appears |
New access verified | Synthetic authentication result | New credential unusable |
Old access assessed | Expected invalid/valid status per DB policy | Unexpected dual-validity or outage |
Recovery captured | Authoritative version + reconciliation owner | Only Terraform state could reconstruct intent |
An empty secret diff does not mean no operational change. Terraform cannot compare a write-only value it does not store. HashiCorp documents that write-only arguments are sent to the provider during operations and that providers typically persist companion version fields so change tracking can occur without persisting the secret itself.
For generated credentials, retry semantics deserve their own test. An ephemeral generator can produce a different value when evaluated again; HashiCorp’s historical explanation explicitly discusses ephemeral resources running during plan and apply. A durable secret destination can solve that only if the workflow knows which stored version became authoritative before downstream consumers change. This is why a rotation runbook should identify the intended destination version, not merely say “Terraform generated a new password.”
Version the Change, Not the Secret in the Plan
For AWS provider v6.65.0, password_wo_version is required when password_wo is set, and changing it triggers an update to password_wo. The Secrets Manager resource has the analogous secret_string_wo_version. Those are provider-qualified fields, not universal Terraform counters.
variable "credential_version" {
type = number
}
# The secret value remains write-only; the version token is persisted.
password_wo = ephemeral.aws_secretsmanager_secret_version.db.secret_string
password_wo_version = var.credential_versionTest case | Expected review conclusion |
Secret changes and version increments | Eligible rotation candidate; verify provider and remote behavior |
Secret changes but version is unchanged | Hold: Terraform cannot prove the secret change from a stored diff; do not claim rotation occurred |
Version changes but intended secret cannot be reacquired | Hold: retry could apply the wrong/unrecoverable value |
The second case is intentionally negative. This article does not claim an executed AWS outcome; it establishes that an unchanged change token is insufficient evidence of an approved rotation.
Recover After a Partial Rotation
Model failures between secret-store write, database update and application refresh. Restoring an older Terraform state file does not, by itself, restore the remote password, because state is a record used by Terraform, not a transactional backup of remote credential effects.
Failure point | Authoritative fact to reacquire | Reconciliation owner |
Secret stored, DB not updated | Intended secret version + version token | Secret owner + DB owner |
DB updated, app not refreshed | Current intended secret version | Application owner |
App refreshed, verification failed | DB accepted version and app retrieval result | Incident/change owner |
Terraform run interrupted | Approved version token and remote observations | Terraform maintainer |
Local policy: one named reconciliation owner controls the decision to retry, roll forward or invoke the service’s approved credential recovery process. Never “fix” a partial rotation by generating a fresh ephemeral password on each retry unless the destination and consumer protocol is explicitly designed for that behavior.
Inspect Current Plans and State with a Synthetic Canary
Artifact inspection needs a bounded scope and a harmless search value. Use an isolated workspace containing no real credentials, inject TF_EPHEMERAL_CANARY_DO_NOT_USE_7KQ9, and record every artifact before changing the configuration. HashiCorp warns that state/plan can contain sensitive values and should be treated as sensitive artifacts; this lab avoids turning inspection output into a new leak.
A proposed procedure is: create the baseline with the ordinary/sensitive path; capture workspace, plan and state identifiers; scan only restricted copies for the synthetic canary; migrate the path to ephemeral/write-only constructs; create a new plan/apply under pinned versions; then scan the new artifacts and every upstream Terraform producer. Do not paste raw JSON, state or plan output into tickets.
Artifact/result | Found | Not found | Uninspected / Inaccessible |
Baseline saved plan | Confirms persistence in baseline path | Re-check inspection method | Record reason; no conclusion |
Baseline current state | Confirms persistence in baseline path | Re-check producer/representation | Record reason; no conclusion |
Candidate saved plan | Acceptance failure | Scoped evidence for omission | Blocks full claim |
Candidate current state | Acceptance failure | Scoped evidence for omission | Blocks full claim |
Upstream producer state | Acceptance failure if same credential retained | Supports end-to-end path | Unknown edge remains |
CI/debug artifact | Separate logging failure | Scoped evidence only | Logging risk unresolved |
A search miss is not mathematical proof of absence. It means that the exact canary string was not found in the inspected representation. Encoding, transformation, remote copies and artifacts outside the inventory remain outside that claim.
For saved-plan inspection, keep the lab isolated. HashiCorp’s terraform show documentation warns that JSON output can display sensitive values in plain text, so redirecting a real secret-bearing plan/state into ordinary CI output would create the very exposure this audit is meant to prevent.
Local policy: publish only artifact identifiers, scan status, tool/version metadata and reviewer sign-off. The canary itself may be documented because it is intentionally non-sensitive; no real value should enter the evidence package.
Audit Logs, Crash Files and Historical Copies Separately
Ephemeral semantics define what Terraform stores in current plan/state for those values; they do not promise that every surrounding system will omit bytes from every diagnostic or historical artifact. Treat observability and retention as a separate control plane. HashiCorp’s state guidance recommends access controls and audit logs for state access when sensitive values are retained.
Build an artifact-owner register before cleanup. Deleting state history simply to make a scanner green can destroy recovery evidence and still miss copies elsewhere.
Artifact class | Owner | Question | Disposition evidence |
Terraform debug/trace output | Platform/CI | Was enhanced logging enabled during secret flow? | Canary scan + retention rule |
Crash files | Runner owner | Was a crash artifact produced and collected? | Artifact inventory/status |
Saved plans | Change-system owner | Which plan IDs are retained and for how long? | Retention/access record |
Prior state versions | Backend/state owner | Does this backend/account retain earlier versions? | Version inventory, not deletion assumption |
Local workspaces | Engineer/runner owner | Are stray state/plan files possible? | Restricted host/workspace review |
Backups/snapshots | Backup owner | Are Terraform artifact stores backed up? | Backup scope + retention owner |
CI attachments/caches | CI owner | Could artifacts be uploaded or cached? | Job configuration + scan result |
Historical-copy review is an additional operating control, not part of the current ephemeral guarantee. A candidate can legitimately pass the new-value path while an old state version still contains the previous credential. That creates a remediation and access-control task; it does not mean the new write-only field is persisting the new value.
Credential invalidation and artifact deletion are different remediation levers. Rotating a leaked or historically persisted password can remove its future authentication value even when retention policy requires old state to remain protected for a period. Conversely, deleting a historical artifact does not prove that another copy is absent. Record both decisions separately: credential lifecycle status belongs to the secret/service owner; artifact retention status belongs to the state or platform owner.
Local policy: protect existing state backups until recovery requirements, retention obligations and credential invalidation are understood. Cleanup is approved per artifact class, with the state-access owner documenting what was deleted, expired, restricted, or deliberately retained. This article makes no promise to erase all historical copies.
Keep Plan and Apply Recovery Consistent
Ephemeral values force recovery design to become explicit because Terraform intentionally does not retain the value in the plan/state. HashiCorp’s March 2025 explanation notes that ephemeral resources can run during plan and apply; that reinforces a practical rule: a resumable workflow must be able to reacquire the intended value rather than rely on a saved secret artifact.
Do not infer saved-plan behavior beyond what your pinned Terraform version actually demonstrates. Because ephemeral values are omitted from plans, the safe operational assumption is that the authoritative value must remain reacquirable at the point of apply; the exact failure mode for a missing input should be tested in the isolated fixture.
Failure | Fact to reacquire | Safe recovery gate |
Ephemeral input unavailable at apply | Exact intended credential/version from approved source | Do not regenerate opportunistically |
Provider credential expired | Fresh provider auth with equivalent authorized role | Re-plan/re-apply per change procedure |
Secret store unavailable | Intended secret version and service health | Stop; avoid divergent DB update |
Write-only target update fails | Remote DB status + intended version token | Reconcile before retry |
CI job resumes on another runner | Same reviewed configuration, lock selection and intended version | Verify resumed operation still matches approved change |
This is where infrastructure-from-code ownership becomes concrete: recovery responsibilities belong with the code path and service owners, not an undefined “DevOps handoff.”
Local policy: every rotation change record carries the intended non-secret version token, secret-store identifier, Terraform/provider versions, plan identifier, and recovery owner. It does not carry the secret. When a run is resumed, reviewers compare those facts with the approved change before allowing another write-only update.
Approve a Limited Migration with Explicit Exceptions
A controlled migration should produce one of three decisions: proceed, hold, or escalate. “Unsupported provider field” is not a reason to remove sensitive labeling or hide evidence; it is an exception to resolve. Likewise, a provider that lacks write-only support may force continued state persistence until a different approved pattern exists.
Gate | Proceed | Hold | Escalate |
Terraform/provider contract | Versions pinned; exact fields supported | Schema not captured | Documentation/schema conflict unresolved |
Complete value path | Every edge classified | Unknown upstream producer | Architecture requires persistent secret edge |
Artifact inspection | Candidate canary absent in scoped plan/state | Artifact inaccessible | Canary found after expected migration |
Consumer access | Application can retrieve and use intended credential | Refresh path untested | Production dependency cannot rotate safely |
Rotation recovery | Partial failures rehearsed | Recovery owner missing | No authoritative credential source |
Historical artifacts | Access/retention owners documented | Inventory incomplete | Required cleanup conflicts with recovery/compliance |
Local policy: migrate only a small, explicitly named cohort after the sandbox evidence passes. The cohort size is an organization decision; this article does not prescribe a percentage. Preserve protected state backups and change records during the migration, because the ability to investigate a failed rollout is more important than producing a cosmetically clean scan.
Exceptions need an expiry or review event, an owner, and a compensating control. For example, an ordinary password argument may remain temporarily because the provider lacks write-only support; the exception should say that the current state remains credential-bearing, define who can access it, and identify the condition that triggers redesign. Calling the value sensitive is still worthwhile for display hygiene, but it is not the compensating persistence control.
Run a Thirty-Day Evidence and Rotation Plan
The timeline below is a local-policy planning model, not a HashiCorp recommendation. Its purpose is to sequence evidence, not promise a security percentage. Track how many secret paths are fully classified, how many artifacts remain uninspected, and how many unsupported sinks remain open.
Local-policy window | Owner | Work | Expected artifact | Rollback/hold decision |
Days 1–5 | Terraform maintainer + secret owner | Map one credential path and classify every edge | Value-flow register | Hold if any edge is unknown |
Days 6–10 | Platform engineer | Build isolated canary baseline and candidate; capture versions/schema | Compatibility bundle + artifact IDs | Hold on unsupported field or canary persistence |
Days 11–15 | DB/application owners | Rehearse old/new synthetic rotation | Connection results + version record | Hold if consumer cannot adopt intended version |
Days 16–20 | Change/recovery owner | Inject partial failures and rehearse reconciliation | Failure matrix with owner decisions | Hold if authoritative source is unclear |
Days 21–30 | Service owner + security reviewer | Migrate a small approved production cohort under change control | Approval packet and post-change evidence | Roll back/stop cohort on acceptance failure |
The broader infrastructure-as-code operating skills are useful context, but the measurable output here is narrower: evidence coverage for secret-bearing edges and unresolved persistence sinks.
Local policy: for each phase, record one responsible owner, the exact expected artifact, and a stop/rollback decision before work begins. Do not use “no incidents observed” as proof that secrets are absent. The useful metrics are structural: classified paths, verified write-only fields, inspected artifacts, successful consumer rotations, unresolved historical stores, and exceptions awaiting provider or architecture changes.
The final production cohort should remain small enough for the service owner to reconcile manually under the approved incident/change process. That is deliberately qualitative because acceptable blast radius depends on service criticality, database recovery characteristics and application deployment topology.
Connect the Audit to Cloud Security Foundations
This Terraform exercise depends on controls that exist beyond Terraform: identity for secret retrieval, encryption for retained stores, monitoring for access, and incident response for partial rotation. For readers who want structured study of those foundations, the Refonte Learning Cloud Security Engineer Program currently lists a three-month period, 10–12 hours per week, and competencies including IAM, data encryption, cloud monitoring and logging, incident response planning, and cloud compliance/governance. The page does not establish a dedicated Terraform ephemeral/write-only module, so the exercises in this article should be treated as independent practice.
Confirmed foundation on program page | Independent practice in this audit |
Identity and Access Management | Separate Terraform provider identity from application secret-read identity |
Data encryption | Distinguish encryption of retained state/secret stores from Terraform omission |
Cloud monitoring and logging | Register CI/debug/state-access evidence without publishing secret values |
Incident response planning | Reconcile partial rotations and choose roll-forward/recovery owners |
Compliance and governance | Record exceptions, retention ownership and approval evidence |
The useful connection is control reasoning: who can read what, what is retained, what evidence proves an action, and who recovers when the credential path is split across systems.
Close with the Secret-Persistence Decision Checklist
A migration is ready only when the evidence describes the full value path, not merely the last resource. Use this checklist as the approval record.
Decision item | Evidence required |
Terraform behavior | Installed CLI version; sensitive/ephemeral classification; documented context rules |
Provider behavior | Locked provider version; exact managed/ephemeral resource arguments; companion version semantics |
Current artifacts | Restricted canary results for plan, state and upstream producers |
Intended destination | Secret identifier/version, authorized retrieval path and owner |
Consumer viability | New credential authentication result and refresh procedure |
Rotation recovery | Partial-failure rehearsal, authoritative source and reconciliation owner |
Historical exposure | Separate inventory of retained plans, state versions, logs, backups and CI artifacts |
Exceptions | Owner, compensating control, review/expiry event and reason |
Is sensitive enough? No, not when the requirement is non-persistence. sensitive = true is a display-redaction control; HashiCorp documents that those values can still be stored in plan and state. Use it alongside ephemeral controls when you need both redaction and omission.
Does ephemeral mean the remote password expires? No. Ephemeral means Terraform does not persist the value in the relevant plan/state path and restricts where it can flow. A database password written through a write-only argument continues to exist at the managed service until the service or your rotation process changes it. The Terraform runtime property is not a credential-expiry policy.
Does a clean current state remove old copies? No. It establishes, at most, that the inspected current state representation did not contain the tested canary. Historical state versions, saved plans, backups, logs and CI artifacts require separate review. HashiCorp’s current-state security guidance emphasizes protecting state with remote storage, encryption, access control and auditing when it contains sensitive values; it does not turn a new ephemeral path into retroactive erasure.
Can every provider use write-only arguments? No. Terraform 1.11 supplies the language/runtime capability, but providers define which resources expose write-only arguments and how companion version fields behave. Verify the exact installed schema and Registry/provider documentation; do not infer a universal _wo convention or OpenTofu equivalence.
The acceptance packet should therefore contain: the value-flow register; Terraform and provider version evidence; lock-file and schema evidence; pinned resource-field references; synthetic canary results for current plans/state and upstream producers; intended secret-store and consumer-access evidence; a successful proposed rotation/recovery rehearsal record once executed; the historical-artifact register; and explicit exceptions. Until those facts line up, a redacted plan is only a cleaner display; it is not proof that Terraform has stopped persisting the credential.
The strongest approval statement is deliberately scoped: “For the pinned configuration and versions, the synthetic canary was absent from the inspected current Terraform plan/state artifacts along the reviewed path; the credential remained present in the approved secret destination and was usable by the intended consumer; historical and diagnostic stores are governed by the attached register.” That is auditable, recoverable and falsifiable. It avoids the much larger, and usually unprovable, claim that a hidden value can never be recovered anywhere.
