Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
IT Security NachrichtenWatchGuard AP: Befehlsschmuggel-Lücken und umgehbare Authentifizierung(29.09.2026 um 07:57 Uhr)
••
IT NachrichtenMac mini M6 review: truly mighty but now more pricey(29.09.2026 um 08:00 Uhr)
••••••••
IT Security NachrichtenWatchGuard AP: Befehlsschmuggel-Lücken und umgehbare Authentifizierung(29.09.2026 um 07:57 Uhr)
••
IT NachrichtenMac mini M6 review: truly mighty but now more pricey(29.09.2026 um 08:00 Uhr)
••••••••
Intelligence View
⚡ tsecurity.de Intelligence

Migrating a Terraform Module to Ephemeral Resources Without Breaking Existing Users

Terraform's ephemeral resources are a genuinely useful addition — secrets that are used during an apply but never written to state. If you're a module maintainer, adopting ephemerals isn't as simple as swapping one block for another. You …

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Terraform's ephemeral resources are a genuinely useful addition — secrets that are used during an apply but never written to state.



If you're a module maintainer, adopting ephemerals isn't as simple as swapping one block for another. You have existing users. Their state files already have the old resource tracked. Changing to an ephemeral version of the resource will blow away their managed resource.



To illustrate the issue, we'll pretend to be updating a module that generates a TLS private key and stores it in a Vault KV secret. This post documents the problem and a solution with an extra tid-bit that I learned.









The Setup



The original module was straightforward:




resource "tls_private_key" "legacy" {
algorithm = "RSA"
rsa_bits = 4096
}

resource "vault_kv_secret_v2" "legacy" {
mount = "kvv2"
name = "mytls"
data_json = jsonencode({ private_key = tls_private_key.legacy.private_key_pem })
}






The private key ends up in state. Vault KV ends up with a copy. Works fine, but the key is visible to anyone with state access.



The goal: keep this working for existing users, while letting new users opt into ephemeral key generation — where the key is used to write to Vault but is never persisted to state.



For solution TL;DR see The Complete Solution below









The Naive Approach (and Why It Fails)



The obvious move is a variable toggle with a shared local to unify both paths:




variable "use_ephemeral_key" {
type = bool
default = false
}

resource "tls_private_key" "legacy" {
count = var.use_ephemeral_key ? 0 : 1
algorithm = "RSA"
rsa_bits = 4096
}

ephemeral "tls_private_key" "ephemeral" {
count = var.use_ephemeral_key ? 1 : 0
algorithm = "RSA"
rsa_bits = 4096
}

locals {
private_key_pem = (
var.use_ephemeral_key
? ephemeral.tls_private_key.ephemeral[0].private_key_pem
: tls_private_key.legacy[0].private_key_pem
)
}

resource "vault_kv_secret_v2" "legacy" {
count = var.use_ephemeral_key ? 0 : 1
mount = "kvv2"
name = "mytls"
data_json = jsonencode({ private_key = local.private_key_pem })
}






Running this produces:




│ Error: Invalid use of ephemeral value
│
│ with vault_kv_secret_v2.legacy,
│ on updated.tf line 34, in resource "vault_kv_secret_v2" "legacy":
│ 34: data_json = jsonencode({ private_key = local.private_key_pem })
│
│ Ephemeral values are not valid for "data_json", because it is not a
│ write-only attribute and must be persisted to state.









Why This Happens



The fundamental rule is: ephemeral values can only flow into write-only attributes. data_json must be persisted to state, so it can never accept an ephemeral value — full stop. That's true whether the ephemeral value arrives directly or through a local.



The shared local makes this worse in a subtle way. Terraform's ephemeral taint propagation is static, not runtime. Because one branch of the local.private_key_pem conditional references an ephemeral value, the entire local is marked ephemeral — regardless of which branch actually executes at runtime. So even if you reasoned "when use_ephemeral_key = false, the local resolves to a non-ephemeral value," Terraform doesn't do branch-aware type narrowing. The local is tainted, and the error fires before it even reaches data_json.



The fix is to remove the shared local entirely and have each vault resource reference its own source directly, keeping ephemeral and non-ephemeral values in completely separate expressions:




# Legacy vault write — only ever touches the non-ephemeral resource
resource "vault_kv_secret_v2" "legacy" {
count = var.use_ephemeral_key ? 0 : 1
mount = "kvv2"
name = "mytls"
data_json = jsonencode({ private_key = tls_private_key.legacy[0].private_key_pem })
}

# Ephemeral vault write — data_json_wo is write-only, so ephemeral values are allowed
resource "vault_kv_secret_v2" "ephemeral" {
count = var.use_ephemeral_key ? 1 : 0
mount = "kvv2"
name = "mytls"
data_json_wo = jsonencode({ private_key = ephemeral.tls_private_key.ephemeral[0].private_key_pem })
}






Rule to internalize: ephemeral values can only ever terminate at write-only attributes. Don't route them through shared locals or any expression that also serves a non-write-only path.









The Second Problem: moved Blocks



Most modules — including this one — were not originally written with count on these resources. Adding count changes the resource address in state:




















Before After
tls_private_key.legacy tls_private_key.legacy[0]
vault_kv_secret_v2.legacy vault_kv_secret_v2.legacy[0]


Terraform treats these as different resources. Without intervention, it will destroy the old address and create a new one — generating a brand new private key in the process. For existing users, that's an unannounced key rotation.



The fix is a moved block for each affected resource:




moved {
from = tls_private_key.legacy
to = tls_private_key.legacy[0]
}

moved {
from = vault_kv_secret_v2.legacy
to = vault_kv_secret_v2.legacy[0]
}






These blocks tell Terraform to update the state address in place — no destroy, no create, no key rotation. On the next plan, existing users will see:




# tls_private_key.legacy has moved to tls_private_key.legacy[0]
# vault_kv_secret_v2.legacy has moved to vault_kv_secret_v2.legacy[0]






Leave the moved blocks in for at least one release cycle so users who haven't applied yet still get a clean migration. They can be removed in a future major version once the old address no longer exists in any user's state.









The Complete Solution






moved {
from = tls_private_key.legacy
to = tls_private_key.legacy[0]
}

moved {
from = vault_kv_secret_v2.legacy
to = vault_kv_secret_v2.legacy[0]
}

variable "use_ephemeral_key" {
description = "Use ephemeral key generation (key never written to state). New deployments should set true."
type = bool
default = false
}

# Legacy path — key stored in state
resource "tls_private_key" "legacy" {
count = var.use_ephemeral_key ? 0 : 1
algorithm = "RSA"
rsa_bits = 4096
}

# Ephemeral path — key never written to state
ephemeral "tls_private_key" "ephemeral" {
count = var.use_ephemeral_key ? 1 : 0
algorithm = "RSA"
rsa_bits = 4096
}

# Legacy vault write — data_json persists to state (acceptable, key is already in state)
resource "vault_kv_secret_v2" "legacy" {
count = var.use_ephemeral_key ? 0 : 1
mount = "kvv2"
name = "mytls"
data_json = jsonencode({ private_key = tls_private_key.legacy[0].private_key_pem })
}

# Ephemeral vault write — data_json_wo is write-only, key never touches state
resource "vault_kv_secret_v2" "ephemeral" {
count = var.use_ephemeral_key ? 1 : 0
mount = "kvv2"
name = "mytls"
data_json_wo = jsonencode({ private_key = ephemeral.tls_private_key.ephemeral[0].private_key_pem })
}












Versioning Strategy



With this in place, the recommended release approach is:





  • Current minor version: ship the toggle with default = false. Existing users are unaffected. New users can opt in with use_ephemeral_key = true.


  • Next major version: flip the default to true. Document the change. Users upgrading from the old version need to either set use_ephemeral_key = false explicitly to preserve legacy behavior, or accept that a new key will be generated on their next apply.



The moved blocks handle the state address migration for everyone regardless of which path they're on.

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Migrating a Terraform Module to Ephemeral Resources Without Breaking Existing Users

Thematisch verwandte Begriffe: Migrating, Terraform, Module, Ephemeral · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

💬 Kommentare werden geladen…
Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-101281 | A flaw has been found in Trusted Domain Project OpenDMARC up to 1.4.2. …
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag