検証環境(検証日: 2026-10-22)
- Terraform v1.11.1 / v1.17.0
- hashicorp/tfe v0.82.0
- hashicorp/vault v5.13.0、Vault v2.2.0(コミュニティ版、Docker の dev モード。ephemeral リソースの検証のみ)
- 接続先: HCP Terraform
はじめに
以前、Terraform 1.11 で使えるようになった Write-only Arguments を使い、state から機密情報を排除する記事を書きました。
- Terraform 1.11 の Write-only Arguments と HCP Vault Secret で Terraform の状態ファイルから機密情報を排除
- その vault_generic_secret データソース、ephemeral リソースにできるかも?
1 本目の記事では、Write-only な値を更新するには value_wo_version を手で上げる必要がある、という点に触れました。time_static と値のハッシュを組み合わせて自動化も試みましたが、ephemeral が使えなくなるなどの制約があり、結局はローカル変数で version を管理するのが良さそうだ、という結論になっていました。
ところが、tfe provider の v0.82.0 で、tfe_variable の value_wo_version を 書かなくてもよくなりました。値を変えると、provider 側が自動で version を上げてくれるようです。
本当にそう動くのか、気になったので実際に試してみました。
Write-only な値の更新はなぜ難しいのか
Write-only Arguments に渡した値は state に保存されません。Terraform は state と config を比べて差分を出すので、state に値がないと、値を変えても変更に気づけません。
そのため、多くのリソースでは Write-only な属性に「version 用の属性」が用意されています。1 本目の記事で使った aws_ssm_parameter の value_wo_version がまさにそれで、値を変えたら version も上げて、更新を Terraform に伝えます。
この方式はシンプルですが、version を上げ忘れると値が更新されません。地味に気を使うところですね。
tfe provider の auto-managed mode
tfe provider v0.82.0 の CHANGELOG を見ると、tfe_variable について「value_wo_version を手でインクリメントする必要がなくなった」という変更が入っています(#2195)。
docs によると、仕組みはざっくり次のとおりです。
-
value_wo_versionを 書かない 場合は auto-managed mode になり、provider が version を管理する。作成時は1で、value_woの値が変わるたびに増える -
value_wo_versionを 書いた 場合は manual mode になり、これまでどおり利用者が version を管理する
値そのものは state に残せないので、provider は値のハッシュを、state の中にある provider 専用の領域(private state)に保存しているようです。plan のときに、今の値のハッシュと保存してあるハッシュを比べ、違っていれば version を上げる、という流れですね。
1 本目の記事で time_static を使って自前でやろうとしていたことを、provider がやってくれる形です。
なお、同じ v0.82.0 では、project / team の notification configuration の token_wo_version も同じように自動管理されるようになっています(#2123)。今回は tfe_variable だけを試しています。
構成例
HCP Terraform に Workspace を作り、その Workspace の変数を tfe_variable で管理します。state はローカルに置き、Organization 名は TF_VAR_organization 環境変数で渡しています。
HCP Terraform 側に本当に値が書き込まれたかを確かめるため、検証用の変数は sensitive = false にして、Variables API で値を読めるようにしています。値はすべて dummy-secret-v1 のようなダミーです。
サンプルコード
terraform {
required_version = ">= 1.11.0"
required_providers {
tfe = {
source = "hashicorp/tfe"
version = "0.82.0"
}
}
}
variable "organization" {
type = string
}
variable "secret" {
type = string
sensitive = true
ephemeral = true
default = "dummy-secret-v1"
}
resource "tfe_workspace" "this" {
name = "wo-version-exp01"
organization = var.organization
}
resource "tfe_variable" "this" {
key = "api_token"
value_wo = var.secret
category = "terraform"
workspace_id = tfe_workspace.this.id
# 値を API で確認するため、検証用に sensitive = false にしている
sensitive = false
}
output "workspace_id" {
value = tfe_workspace.this.id
}
output "variable_id" {
value = tfe_variable.this.id
}
value_wo_version を書いていないところがポイントです。
以降の出力は Terraform v1.17.0 のものです。v1.11.1 でも、同じ手順で同じ結果になりました。ID 類は XXXXXXXX に置き換えています。
動作確認
value_wo_version を書かずに作成してみる
まずはそのまま terraform apply してみましょう。
# tfe_variable.this will be created
+ resource "tfe_variable" "this" {
+ category = "terraform"
+ hcl = false
+ id = (known after apply)
+ key = "api_token"
+ sensitive = false
+ value_wo = (write-only attribute)
+ value_wo_version = 1
+ workspace_id = (known after apply)
# (3 unchanged attributes hidden)
}
書いていない value_wo_version が、plan の時点で 1 になっていますね。
apply 後の state を見ると、value は空文字、value_wo は null、value_wo_version は 1 でした。API で変数を読むと dummy-secret-v1 が入っていて、ちゃんと書き込まれています。state と apply の出力を grep しても、dummy-secret という文字列は出てきませんでした。
値を変えてみる
次に、値を dummy-secret-v2 に変えて plan してみます。
terraform plan -var 'secret=dummy-secret-v2'
# tfe_variable.this will be updated in-place
~ resource "tfe_variable" "this" {
id = "var-XXXXXXXX"
~ value_wo_version = 1 -> 2
# (9 unchanged attributes hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
version を何も書いていないのに、1 -> 2 の差分が出ました。apply すると、API で読んだ値も dummy-secret-v2 に変わっていました。
ちなみに、以前の値 dummy-secret-v1 に戻した場合も 2 -> 3 となり、値が戻りました。
値を変えずに plan してみる
同じ値のまま、もう一度 plan してみます。
No changes. Your infrastructure matches the configuration.
差分は出ません。-detailed-exitcode を付けても終了コードは 0 でした。値が変わったときだけ更新される、期待どおりの動きですね。
value_wo_version を明示した場合
value_wo_version を書いた場合(manual mode)も確認しておきます。value_wo_version を変数で渡すようにしました。
resource "tfe_variable" "this" {
key = "api_token"
value_wo = var.secret
value_wo_version = var.wo_version
category = "terraform"
workspace_id = tfe_workspace.this.id
# 値を API で確認するため、検証用に sensitive = false にしている
sensitive = false
}
version を 1 にしたまま値だけ dummy-secret-v2 に変えると、plan は No changes で、HCP Terraform の値も dummy-secret-v1 のままでした。version を 2 に上げると 1 -> 2 の差分が出て、値が更新されました。
こちらは従来どおり、version を上げたときだけ更新される動きです。
Vault から ephemeral に読んだ値を渡してみる
2 本目の記事では、Vault の KV v2 から ephemeral "vault_kv_secret_v2" で値を読み、aws_ssm_parameter の value_wo に渡しました。そのときは value_wo_version も一緒に指定していましたが、tfe provider ならこれも要らないはずです。
今回は手元の Docker Desktop で、Vault のコミュニティ版を dev モードで起動して試してみました。<root token> には任意の文字列を指定します。
docker run -d --name vault-wo-exp -p 127.0.0.1:8200:8200 --cap-add=IPC_LOCK \
-e VAULT_DEV_ROOT_TOKEN_ID=<root token> hashicorp/vault:2.2.0
Terraform や後述のスクリプトからは、環境変数 VAULT_TOKEN に同じ値を設定して接続します。
dev モードはその名のとおり検証用なので、本番では使わないようにしましょう。dev モードでは secret/ に KV v2 が最初からマウントされているので、そこに値を書き込みます。
#!/usr/bin/env bash
# 使い方: ./kv_put.sh <value>
# ローカルの Vault(コンテナ名 vault-wo-exp)の secret/wo-exp に api_token を書き込む
docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN="$VAULT_TOKEN" vault-wo-exp \
vault kv put -mount=secret wo-exp api_token="$1" | grep -E '^version|^created_time'
Terraform 側は次のようにしました。
# ローカルの Docker で起動した Vault(dev モード)
# トークンは環境変数 VAULT_TOKEN で渡す
provider "vault" {
address = "http://127.0.0.1:8200"
}
# Vault の KV v2 から値を読む。ephemeral なので state には残らない
ephemeral "vault_kv_secret_v2" "this" {
mount = "secret"
name = "wo-exp"
}
resource "tfe_variable" "this" {
key = "api_token"
value_wo = ephemeral.vault_kv_secret_v2.this.data["api_token"]
category = "terraform"
workspace_id = tfe_workspace.this.id
# 値を API で確認するため、検証用に sensitive = false にしている
sensitive = false
}
ここでも value_wo_version は書いていません。
まず Vault に dummy-secret-v1 を書き込んでから apply すると、value_wo_version = 1 で作成され、HCP Terraform の変数にも dummy-secret-v1 が入りました。
そのまま何も変えずに 2 回 plan してみると、どちらも No changes でした。ephemeral リソースは plan のたびに Vault から読み直しますが、値が同じならハッシュも同じなので差分にはならない、ということですね。
次に、Vault 側の値だけを dummy-secret-v2 に更新してみます。
./kv_put.sh dummy-secret-v2
created_time 2026-10-22T01:02:51.56239797Z
version 2
Terraform のコードは何も変えずに plan すると……
# tfe_variable.this will be updated in-place
~ resource "tfe_variable" "this" {
id = "var-XXXXXXXX"
~ value_wo_version = 1 -> 2
# (9 unchanged attributes hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
Vault の値が変わったことを検知して、version が上がりました。apply すると、HCP Terraform の変数も dummy-secret-v2 に更新されました。その後の plan は、また No changes です。
2 本目の記事の構成では、Vault の値を変えたら value_wo_version も手で上げる必要がありました。それが要らなくなるのはうれしいですね。
state を見ると、resources に入っているのは tfe_workspace と tfe_variable だけでした。vault_kv_secret_v2 は tfe_variable の dependencies に名前が出てくるだけで、Vault から読んだ値も、Vault のトークンも state には含まれていませんでした。
value から value_wo に移行してみる
すでに value で管理している変数を、value_wo に移行するケースも試しました。use_write_only 変数で、どちらを使うか切り替えられるようにしています。
resource "tfe_variable" "this" {
key = "api_token"
value = var.use_write_only ? null : var.plain_value
value_wo = var.use_write_only ? var.secret : null
category = "terraform"
workspace_id = tfe_workspace.this.id
# 値を API で確認するため、検証用に sensitive = false にしている
sensitive = false
}
まず value = "dummy-plain-v1" で作成してから、value_wo に切り替えます。すると、次のような差分になりました(sensitive = false にしているので、readable_value に値が見えています)。
# tfe_variable.this will be updated in-place
~ resource "tfe_variable" "this" {
id = "var-XXXXXXXX"
- readable_value = "dummy-plain-v1" -> null
- value = (sensitive value) -> null
+ value_wo_version = 1
# (7 unchanged attributes hidden)
}
作り直し(replace)ではなく in-place で更新され、apply 後の state の value は空になりました。
ただし、移行直後のローカルの terraform.tfstate.backup には、移行前の dummy-plain-v1 が残っていました。backup は 1 つ前の state なので、当然といえば当然ですね。
では、state を HCP Terraform に置いている場合はどうでしょうか。同じ構成に cloud ブロックを追加し、state 保存用の Workspace(実行モードは Local)で同じ移行をしてみました。
terraform {
required_version = ">= 1.11.0"
# state は HCP Terraform の Workspace に保存する(Organization は TF_CLOUD_ORGANIZATION で渡す)
cloud {
workspaces {
name = "wo-version-exp10-state"
}
}
...
}
value で作成し、value_wo に移行した後、State Versions API でこの Workspace の state のバージョンを全部ダウンロードして、dummy-plain-v1 が何か所あるか数えました。
| serial | タイミング |
dummy-plain-v1 の出現数 |
|---|---|---|
| 1 |
value で作成した後 |
2 |
| 2 |
value_wo に移行した後 |
0 |
最新の state(serial 2)からは消えていますが、移行前の state(serial 1)には平文がそのまま残っていました。HCP Terraform では過去の state もバージョンとして保存されるので、これも当然といえば当然です。
value_wo に移行すれば、それ以降の state には値が残りません。ただ、過去の state には残っているので、移行のタイミングで値そのものも変えておくと安心だと思います。
HCP Terraform 側で値を変えた場合
Terraform の外で値を変えた場合も見てみます。今回は UI ではなく、Variables API で値を dummy-secret-changed-outside に変えました。
config はそのままで plan すると No changes、apply しても 0 changed で、HCP Terraform の値は変えた値のままでした。
auto-managed mode が比べているのは「前回 Terraform から書き込んだ値」と「今の config の値」なので、外で変えられた分には気づけない、ということですね。UI での変更は試していませんが、同じ結果になると思われます。
state の中身を見てみる
最後に、ハッシュが state のどこに入っているのか見てみましょう。terraform state pull で state を取り出すと、tfe_variable のインスタンスに private という base64 のフィールドがありました。
デコードしていくと、中身は dummy-secret-v1 の SHA-256 そのものでした。terraform show や terraform show -json、plan の出力には出てきません。manual mode のときは、このフィールド自体がありませんでした。
値そのものは残らないものの、値のハッシュは state ファイルに残る という点は覚えておくと良さそうです。推測しやすい値だと、ハッシュから元の値を推測される可能性もゼロではないと思われます。気になる場合は manual mode を選ぶことになりますね。
まとめ
試した結果をまとめると、次のとおりです。Terraform v1.11.1 と v1.17.0 で、違いはありませんでした。
| 試したこと | 結果 |
|---|---|
value_wo_version を書かずに作成 |
version 1 で作成され、値は state に残らない |
| 値を変更 | version が自動で上がり、値が更新される |
| 値を変えずに plan | 差分なし |
value_wo_version を明示 |
従来どおり、version を上げたときだけ更新 |
| Vault から ephemeral に読んだ値 | Vault の値が変わったときだけ version が上がる |
value → value_wo
|
in-place で移行できる。ローカルの backup や HCP Terraform の過去の state バージョンには、移行前の平文が残る |
| HCP Terraform 側での変更 | 検知されない |
| state の中身 | 値のハッシュが private state に入る |
使い分けの目安としては、次のようになりそうです。
- 変数や Vault などのシークレットストアから値を渡していて、変わったときだけ更新したい →
value_wo_versionは書かない(auto-managed) -
state に値のハッシュも残したくない、または 更新のタイミングを自分で決めたい →
value_wo_versionを書く(manual)
おわりに
1 本目の記事で「version を自動で上げられないか」と試行錯誤していたことが、tfe provider では標準で使えるようになっていました。2 本目の記事の Vault + ephemeral の構成と組み合わせても、Vault の値を変えるだけで更新まで追従してくれます。version の上げ忘れを気にしなくて良いのは、地味にうれしいですね。
一方で、state に値のハッシュが残ることや、HCP Terraform 側での変更には気づけないことなど、試してみて分かったこともありました。使う前に一度、手元で挙動を確かめておくと安心だと思います。