DevOps engineer auditing GitHub Actions OIDC identity claims and AWS trust policies at a multi-monitor workstation

GitHub OIDC Subjects Changed: Migrate AWS Trust Without Wildcards

Thu, Sep 17, 2026

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_version

Test 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.