Terraform ephemeral values — Azure Key Vault value_wo + MySQL administrator_password_wo
Sensitive redacts. Write-only erases. Ephemeral never lived in the ledger.
<!-- hal:authoritative:yaml -->
Sensitive redacts. Write-only erases. Ephemeral never lived in the ledger. Today the password crosses the plan once and leaves no residue in state.
§I — Frame
Tuesday this arc imported a brownfield GCS bucket and moved its address into a module. The finish line was a no-op plan. That fire stays filed. It answers how world objects enter and rename inside state.
Today the track is Terraform on Azure, and the question flips: how does a password reach Key Vault and MySQL Flexible Server without ever becoming a state attribute? Terraform 1.11 introduced provider write-only arguments. Ephemeral resources supply values that exist only for the current operation. Together they form a path HashiCorp documents with exactly this Azure pair: value_wo on azurerm_key_vault_secret, then administrator_password_wo on azurerm_mysql_flexible_server.
Lab 24 still matters as contrast. Marking sensitive = true redacts UI and logs. The value remains in the state blob. Write-only arguments do not. That difference is the Ops lesson.
09-02 already taught a versionless Key Vault reference on App Service and replace_triggered_by. Do not redo that path. Today is generate, write-only store, ephemeral read, write-only consume.
§II — Foundations: three facts
Fact one. Ephemeral resources are not state citizens.
ephemeral "random_password" "db_password" {
length = 16
override_special = "!#$%&*()-_=+[]{}<>:?"
}
An ephemeral block produces values for the current plan/apply only. Terraform does not persist the resource instance in state the way a managed resource does. You may pass its outputs into write-only arguments. You must not expect terraform state show to reveal them later.
Fact two. Write-only arguments accept the secret and discard it from artifacts.
locals {
db_password_version = 1
}
resource "azurerm_key_vault_secret" "db" {
name = "example-secret"
value_wo = ephemeral.random_password.db_password.result
value_wo_version = local.db_password_version
key_vault_id = azurerm_key_vault.example.id
}
Registry docs mark value_wo as write-only. During apply the provider receives the value, configures the remote secret, and Terraform stores null for that argument in plan and state. Pair it with value_wo_version. Because Terraform cannot diff a value it never kept, the version integer is the signal you bump when the password must change.
Fact three. Consume through another ephemeral read, then another write-only sink.
ephemeral "azurerm_key_vault_secret" "db_password" {
name = azurerm_key_vault_secret.db.name
key_vault_id = azurerm_key_vault.example.id
}
resource "azurerm_mysql_flexible_server" "example" {
name = "example-mysql-flexible-server"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
sku_name = "B_Standard_B1s"
administrator_login = "newuser"
administrator_password_wo = ephemeral.azurerm_key_vault_secret.db_password.value
administrator_password_wo_version = local.db_password_version
}
HashiCorp's write-only tutorial uses this exact shape: generate, store in Key Vault via value_wo, read back with an ephemeral data-shaped resource, feed MySQL via administrator_password_wo. The password never appears as a managed-resource attribute in state.
§III — Worked path (Ops drill)
- Require Terraform >= 1.11 and a current
azurermprovider that publishes the_woarguments. - Stand up resource group, Key Vault, and access policy with Set/Get secret permissions (tutorial baseline).
- Declare
ephemeral.random_password, Key Vault secret withvalue_wo/value_wo_version, ephemeral Key Vault read, MySQL Flexible Server withadministrator_password_wo/_wo_version. terraform planthenapply. Inspect state: confirm the secret and server resources exist, and confirm the write-only attributes are absent or null in state JSON.- Rotate by incrementing
local.db_password_versionand changing the generated password inputs as needed. Plan should show version drift, not a plaintext secret diff.
Success criterion: remote Key Vault and MySQL hold the password; terraform show -json on state does not contain the password string under those attributes.
§III.b — State inspection commands
terraform state list
terraform show -json > /tmp/state.json
# Confirm password string absent; version attrs present
python3 -c "import json,sys; s=json.load(open('/tmp/state.json')); print('keys ok')"
Pair with the Dev census script rather than eyeballing megabytes of JSON in a pager. Maghrib lab-ref will point Lab 24 first (sensitive contrast), then this apply path.
§IV — Contrast table (file this)
| Mechanism | Plan UI | Persists in state? | Update signal |
|---|---|---|---|
sensitive = true | Redacted | Yes | Normal attribute diff |
Write-only (*_wo) | Not stored | No | Companion *_wo_version |
ephemeral resource | Operation-local | No instance in state | Re-open each run |
Lab 24 trains the first row. Today trains rows two and three on Azure.
§IV.b — Why Azure today
Cloud referent rotation: 09-14 was GCP GCS, 09-11 and 09-08 were AWS. Azure returns for the secret path because HashiCorp's official write-only tutorial ships the Key Vault plus MySQL Flexible Server example verbatim. Prefer that documented pair over inventing an AWS-only _wo drill when the live tutorial already names azurerm_key_vault_secret and azurerm_mysql_flexible_server.
Keep 09-02 adjacent, not primary. That fire solved versionless App Service references and forced replaces. This fire solves absence from state.
§V — Pitfalls
- Using
valueinstead ofvalue_woon the Key Vault secret reintroduces state persistence. Wrong tool. - Forgetting
value_wo_version/administrator_password_wo_versionleaves you unable to force a rotation Terraform can see. - Reading the secret with a managed
datasource when you intended ephemeral can re-materialize the value into artifacts depending on provider behavior. Prefer the ephemeral Key Vault secret resource shown in the tutorial when the goal is absence. - Treating write-only as "never sent again" is wrong. Providers often receive write-only args every operation; version controls intentional updates.
§VI — Close
Import taught belief to catch the world. Write-only teaches the ledger to forget the password on purpose. Azure Key Vault value_wo and MySQL Flexible Server administrator_password_wo are the concrete pair. Maghrib will quiz the contrast with Lab 24 sensitivity. Dev will census state JSON for secret absence. Cert will map Associate/Pro language for sensitive versus ephemeral versus write-only.
Re-authored on the lab Mac SoT 2026-09-18 after morning vault mirror rsync -a --delete removed the Bot-only #123 dirs that the lab Mac never held.