以前の記事でTerraform 1.10.0からEphemeral resourceが追加されたことを紹介しました。 Terraform 1.10時点ではData Sourceの置き換えとしてのEpehemeral Resourceであって、通常のリソースでパスワード的なものをStateを残さないようにはできません。これを実現するのが、Terraform 1.11から追加されるWrite-only Argumentsです。
今回はWrite-only Argumentsを使って、通常のリソースでもStateに値を残さなくする検証メモです。
2025/3/4追記
Terraform 1.11がリリースされました。Write-only arguments使っていきましょう。
まとめ
あまりに長いので簡単まとめです。
- Terraform 1.11から各種リソースがWrite-only attributeを用意してくれればtfstateから値を消せる
- Write-only attributeは
ただの文字列
とEphemeral variable
どちらも渡せる - 従来の非Write-only attributeからWrite-only attributeに変更する時は
changed
であることが期待できる(かも)1 - Write-only attributeはバージョンを変えないと変更検知されないので注意が必要
- AWS Providerは5.87.0時点でaws_rds_clusterとaws_ssm_parameterに対応している
Write-only Argumentsの基本
次の環境で挙動を確認します。
$ terraform version Terraform v1.11.0-rc1 on linux_amd64 + provider registry.terraform.io/hashicorp/aws v5.87.0
Write-only Argumentを使った書き方
はじめにどのように書くのか見てみましょう。
AWS Provider 5.87.0でWrite-only attributes2がaws_ssm_parameter
とaws_rds_cluster
の2リソースに追加されています。Write-only attributesはリソースごとに対応が必要なので、AWS Providerの対応待ちになるリソースがしばらく多そうです。
コードを見ると大きな特徴が2つあります。このあたりはドキュメント通りです。
value_wo
という書き込み専用のattributeが、従来のattributevalue
とは別に用意されているvalue_on_version
のようにペアになるversion attributeが用意されている3
さっそく書いてみましょう。
# main.tf locals { value = "foo" value_version = 1 } resource "aws_ssm_parameter" "main" { name = "test-bar" type = "String" value_wo = local.value value_wo_version = local.value_version } provider "aws" { region = "ap-northeast-1" access_key = var.AWS_ACCESS_KEY secret_key = var.AWS_SECRET_KEY default_tags { tags = { "Test" = "true" } } } terraform { required_providers { aws = { version = "= 5.87.0" source = "hashicorp/aws" } } required_version = ">= 1.11.0" # RC版を指定する。1.11系じゃないとwrite only argumentsが使えない。 }
実行してみましょう。value_wo
がwrite-only attributeになって、value
がマスクされています。
$ terraform apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_ssm_parameter.main will be created + resource "aws_ssm_parameter" "main" { + arn = (known after apply) + data_type = (known after apply) + has_value_wo = (known after apply) + id = (known after apply) + insecure_value = (known after apply) + key_id = (known after apply) + name = "test-bar" + tags_all = { + "Test" = "true" } + tier = (known after apply) + type = "String" + value = (sensitive value) + value_wo = (write-only attribute) + value_wo_version = 1 + version = (known after apply) } Plan: 1 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes aws_ssm_parameter.main: Creating... aws_ssm_parameter.main: Creation complete after 1s [id=test-bar]
Stateファイルを見てみます。value
が空文字列とvalue_wo
がnullになっているのを確認できます。値が残ってないですね、期待通りです。
{ "version": 4, "terraform_version": "1.11.0", "serial": 3, "lineage": "b8b893c3-65db-6bc4-7f99-f0a022de2e35", "outputs": {}, "resources": [ { "mode": "managed", "type": "aws_ssm_parameter", "name": "main", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "instances": [ { "schema_version": 0, "attributes": { "allowed_pattern": "", "arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/test-bar", "data_type": "text", "description": "", "has_value_wo": true, "id": "test-bar", "insecure_value": null, "key_id": "", "name": "test-bar", "overwrite": null, "tags": null, "tags_all": { "Test": "true" }, "tier": "Standard", "type": "String", "value": "", "value_wo": null, "value_wo_version": 1, "version": 1 }, "sensitive_attributes": [ [ { "type": "get_attr", "value": "value" } ], [ { "type": "get_attr", "value": "value_wo" } ] ], "private": "bnVsbA==" } ] } ], "check_results": null }
System Managerパラメーターストアを見ると指定通り設定されています。Stateには存在しないけど、値はある、すばらしい。
apply後にplanしても、期待通り変更ないと表示されます。
$ terraform plan aws_ssm_parameter.main: Refreshing state... [id=test-bar] No changes. Your infrastructure matches the configuration. Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
いろいろなパターンを試す
いくつか気になるパターンがあるので試しましょう。いったん、先ほどのssmは消してから行います。
1.10ではWrite-only argumentsが使えない
terraform 1.10にすると、まだwrite_only variablesは使えないので怒られます。いいはなし。
# main.tf terraform { required_providers { aws = { version = "= 5.87.0" source = "hashicorp/aws" } } required_version = ">= 1.10.5" # 1.11系じゃないとwrite only argumentsが使えない }
ちゃんと1.11以上にしろといってくれます。
$ terraform apply ╷ │ Error: Write-only Attribute Not Allowed │ │ with module.ssm.aws_ssm_parameter.main, │ on modules/ssm/main.tf line 15, in resource "aws_ssm_parameter" "main": │ 15: value_wo = each.value.value │ │ The resource contains a non-null value for write-only attribute "value_wo" Write-only attributes are only supported in Terraform 1.11 and later.
ephemeralではないvariables経由で渡す
localsではなくvariablesを経由する場合、どうなるかみましょう。呼び出しは先ほどのリソースをモジュールにしただけです。
# main.tf locals { value = "foo" value_version = 1 } module "ssm" { source = "./modules/ssm" value1 = local.value value1_version = local.value_version } provider "aws" { region = "ap-northeast-1" access_key = var.AWS_ACCESS_KEY secret_key = var.AWS_SECRET_KEY default_tags { tags = { "Test" = "true" } } } terraform { required_providers { aws = { version = "= 5.87.0" source = "hashicorp/aws" } } required_version = ">= 1.11.0" # 1.11系じゃないとwrite only argumentsが使えない }
value_wo
に渡すvariablesはephemeral
をfalseにした「ただの文字列」で実行してみましょう。ephemeralを省略してもこの状態です。
# modules/ssm/main.tf variable "value1" { description = "ephemeral secret value" type = string ephemeral = false # ただの文字列 nullable = false } variable "value1_version" { description = "secret value's version" type = number } resource "aws_ssm_parameter" "main" { name = "test-bar" type = "String" value_wo = var.value1 value_wo_version = var.value1_version } resource "aws_ssm_parameter" "main" { name = "test-bar" type = "String" value_wo = var.value1 value_wo_version = var.value1_version }
先ほど同様、問題なく実行できています。
$ terraform apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # module.ssm.aws_ssm_parameter.main will be created + resource "aws_ssm_parameter" "main" { + arn = (known after apply) + data_type = (known after apply) + has_value_wo = (known after apply) + id = (known after apply) + insecure_value = (known after apply) + key_id = (known after apply) + name = "test-bar" + tags_all = { + "Test" = "true" } + tier = (known after apply) + type = "String" + value = (sensitive value) + value_wo = (write-only attribute) + value_wo_version = 1 + version = (known after apply) } Plan: 1 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.ssm.aws_ssm_parameter.main: Creating... module.ssm.aws_ssm_parameter.main: Creation complete after 0s [id=test-bar] Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Stateファイルを見てみます。value
が空文字列とvalue_wo
がnullになっているのを確認できます。
{ "version": 4, "terraform_version": "1.11.0", "serial": 7, "lineage": "b8b893c3-65db-6bc4-7f99-f0a022de2e35", "outputs": {}, "resources": [ { "module": "module.ssm", "mode": "managed", "type": "aws_ssm_parameter", "name": "main", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "instances": [ { "schema_version": 0, "attributes": { "allowed_pattern": "", "arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/test-bar", "data_type": "text", "description": "", "has_value_wo": true, "id": "test-bar", "insecure_value": null, "key_id": "", "name": "test-bar", "overwrite": null, "tags": null, "tags_all": { "Test": "true" }, "tier": "Standard", "type": "String", "value": "", "value_wo": null, "value_wo_version": 1, "version": 1 }, "sensitive_attributes": [ [ { "type": "get_attr", "value": "value_wo" } ], [ { "type": "get_attr", "value": "value" } ] ], "private": "bnVsbA==" } ] } ], "check_results": null }
ephemeralなvariables経由で渡す
value_wo
に渡すvariablesは、ephemeral
をtrueにした「Ephemeral variable」で実行してみましょう。
# modules/ssm/main.tf variable "value1" { description = "ephemeral secret value" type = string ephemeral = true # エフェメラルにする nullable = false } variable "value1_version" { description = "secret value's version" type = number } resource "aws_ssm_parameter" "main" { name = "test-bar" type = "String" value_wo = var.value1 value_wo_version = var.value1_version }
渡す側はversionだけ変えます。
locals { value = "foo" value_version = 2 }
こちらも問題なく実行できます。
$ terraform apply module.ssm.aws_ssm_parameter.main: Refreshing state... [id=test-bar] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # module.ssm.aws_ssm_parameter.main will be updated in-place ~ resource "aws_ssm_parameter" "main" { ~ has_value_wo = true -> (known after apply) id = "test-bar" name = "test-bar" tags = {} ~ value_wo_version = 1 -> 2 # (11 unchanged attributes hidden) } Plan: 0 to add, 1 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.ssm.aws_ssm_parameter.main: Modifying... [id=test-bar] module.ssm.aws_ssm_parameter.main: Modifications complete after 0s [id=test-bar] Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
Stateファイルを見てみます。value
が空文字列とvalue_wo
がnullになっているのを確認できます。
versionだけ変わっています。
{ "version": 4, "terraform_version": "1.11.0", "serial": 10, "lineage": "b8b893c3-65db-6bc4-7f99-f0a022de2e35", "outputs": {}, "resources": [ { "module": "module.ssm", "mode": "managed", "type": "aws_ssm_parameter", "name": "main", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "instances": [ { "schema_version": 0, "attributes": { "allowed_pattern": "", "arn": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/test-bar", "data_type": "text", "description": "", "has_value_wo": true, "id": "test-bar", "insecure_value": null, "key_id": "", "name": "test-bar", "overwrite": null, "tags": {}, "tags_all": { "Test": "true" }, "tier": "Standard", "type": "String", "value": "", "value_wo": null, "value_wo_version": 2, "version": 2 }, "sensitive_attributes": [ [ { "type": "get_attr", "value": "value" } ], [ { "type": "get_attr", "value": "value_wo" } ] ], "private": "bnVsbA==" } ] } ], "check_results": null }
配列やmap経由で渡す
文字列が渡せたということは配列やmapも問題ないわけです。一度先ほど作ったSSMパラメーターは消してから、次のコードを試します。
# main.tf locals { secrets = [ { name = "test-value1" value = "value1" version = 1 }, { name = "test-value2" value = "value2" version = 2 } ] } module "ssm" { source = "./modules/ssm" secrets = local.secrets }
モジュールもlist(object)
で受け取るようにします。
# modules/ssm/main.tf variable "secrets" { description = "secret value" type = list(object({ name = string value = string version = number })) nullable = false } resource "aws_ssm_parameter" "main" { for_each = {for s in var.secrets: s.name => s} name = each.value.name type = "String" value_wo = each.value.value value_wo_version = each.value.version }
こちらも問題なく実行できます。
$ terraform apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # module.ssm.aws_ssm_parameter.main["test-value1"] will be created + resource "aws_ssm_parameter" "main" { + arn = (known after apply) + data_type = (known after apply) + has_value_wo = (known after apply) + id = (known after apply) + insecure_value = (known after apply) + key_id = (known after apply) + name = "test-value1" + tags_all = { + "Test" = "true" } + tier = (known after apply) + type = "String" + value = (sensitive value) + value_wo = (write-only attribute) + value_wo_version = 1 + version = (known after apply) } # module.ssm.aws_ssm_parameter.main["test-value2"] will be created + resource "aws_ssm_parameter" "main" { + arn = (known after apply) + data_type = (known after apply) + has_value_wo = (known after apply) + id = (known after apply) + insecure_value = (known after apply) + key_id = (known after apply) + name = "test-value2" + tags_all = { + "Test" = "true" } + tier = (known after apply) + type = "String" + value = (sensitive value) + value_wo = (write-only attribute) + value_wo_version = 2 + version = (known after apply) } Plan: 2 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.ssm.aws_ssm_parameter.main["test-value1"]: Creating... module.ssm.aws_ssm_parameter.main["test-value2"]: Creating... module.ssm.aws_ssm_parameter.main["test-value2"]: Creation complete after 1s [id=test-value2] module.ssm.aws_ssm_parameter.main["test-value1"]: Creation complete after 1s [id=test-value1] Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Stateファイルが長いのでスクショですが、value
が空文字列とvalue_wo
がnullになっているのを確認できます。
valueからvalue_woに変更する
もともとvalueで指定していたものをvalue_wo
にするとそのまま更新できるか気になりますね?いったん先ほどのSSMパラメーターを消してから試しましょう。
まずはvalueでSSMパラメーターを作成します。
# modules/ssm/main.tf variable "secrets" { description = "secret value" type = list(object({ name = string value = string version = number })) nullable = false } resource "aws_ssm_parameter" "main" { for_each = {for s in var.secrets: s.name => s} name = each.value.name type = "String" value = each.value.value # value_wo = each.value.value # value_wo_version = each.value.version }
valueで作成します。Warning: Available Write-only Attribute Alternative
って注意出してくれるようになっていますね、いいですね!
$ terraform apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # module.ssm.aws_ssm_parameter.main["test-value1"] will be created + resource "aws_ssm_parameter" "main" { + arn = (known after apply) + data_type = (known after apply) + has_value_wo = (known after apply) + id = (known after apply) + insecure_value = (known after apply) + key_id = (known after apply) + name = "test-value1" + tags_all = { + "Test" = "true" } + tier = (known after apply) + type = "String" + value = (sensitive value) + value_wo = (write-only attribute) + version = (known after apply) } # module.ssm.aws_ssm_parameter.main["test-value2"] will be created + resource "aws_ssm_parameter" "main" { + arn = (known after apply) + data_type = (known after apply) + has_value_wo = (known after apply) + id = (known after apply) + insecure_value = (known after apply) + key_id = (known after apply) + name = "test-value2" + tags_all = { + "Test" = "true" } + tier = (known after apply) + type = "String" + value = (sensitive value) + value_wo = (write-only attribute) + version = (known after apply) } Plan: 2 to add, 0 to change, 0 to destroy. ╷ │ Warning: Available Write-only Attribute Alternative │ │ Warning: Available Write-only Attribute Alternative │ Warning: Available Write-only Attribute Alternative │ │ with module.ssm.aws_ssm_parameter.main, │ on modules/ssm/main.tf line 15, in resource "aws_ssm_parameter" "main": │ 15: value = each.value.value │ │ The attribute value has a write-only alternative value_wo available. Use the write-only alternative of the attribute when possible. │ │ (and 2 more similar warnings elsewhere) ╵ Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.ssm.aws_ssm_parameter.main["test-value2"]: Creating... module.ssm.aws_ssm_parameter.main["test-value1"]: Creating... module.ssm.aws_ssm_parameter.main["test-value1"]: Creation complete after 0s [id=test-value1] module.ssm.aws_ssm_parameter.main["test-value2"]: Creation complete after 0s [id=test-value2] ╷ │ Warning: Available Write-only Attribute Alternative │ │ with module.ssm.aws_ssm_parameter.main["test-value1"], │ on modules/ssm/main.tf line 15, in resource "aws_ssm_parameter" "main": │ 15: value = each.value.value │ │ The attribute value has a write-only alternative value_wo available. Use the write-only alternative of the attribute when possible. │ │ (and one more similar warning elsewhere) ╵ Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Stateファイルのスクショですが、value
に値が入っちゃっています。
では、value
からvalue_wo
に変更してみましょう。
# modules/ssm/main.tf resource "aws_ssm_parameter" "main" { for_each = {for s in var.secrets: s.name => s} name = each.value.name type = "String" # value = each.value.value value_wo = each.value.value value_wo_version = each.value.version }
差分はchangedですね、期待通りでうれしいです。
$ terraform apply module.ssm.aws_ssm_parameter.main["test-value1"]: Refreshing state... [id=test-value1] module.ssm.aws_ssm_parameter.main["test-value2"]: Refreshing state... [id=test-value2] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # module.ssm.aws_ssm_parameter.main["test-value1"] will be updated in-place ~ resource "aws_ssm_parameter" "main" { + has_value_wo = (known after apply) id = "test-value1" name = "test-value1" tags = {} + value_wo_version = 1 # (11 unchanged attributes hidden) } # module.ssm.aws_ssm_parameter.main["test-value2"] will be updated in-place ~ resource "aws_ssm_parameter" "main" { + has_value_wo = (known after apply) id = "test-value2" name = "test-value2" tags = {} + value_wo_version = 2 # (11 unchanged attributes hidden) } Plan: 0 to add, 2 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.ssm.aws_ssm_parameter.main["test-value2"]: Modifying... [id=test-value2] module.ssm.aws_ssm_parameter.main["test-value1"]: Modifying... [id=test-value1] module.ssm.aws_ssm_parameter.main["test-value2"]: Modifications complete after 0s [id=test-value2] module.ssm.aws_ssm_parameter.main["test-value1"]: Modifications complete after 0s [id=test-value1] Apply complete! Resources: 0 added, 2 changed, 0 destroyed.
Stateファイルのスクショですが、value
が空文字列とvalue_wo
がnullになっているのを確認できます。
バージョンを更新せず文字列を変えても変更されない
先の値を変えてversionを変更しない場合、どうなるか気になります。value1
からvalue10
のように変えてみます。
# main.tf locals { secrets = [ { name = "test-value1" value = "value10" version = 1 }, { name = "test-value2" value = "value20" version = 2 } ] }
変更検知されません、ドキュメントどおりです。
$ terraform plan module.ssm.aws_ssm_parameter.main["test-value2"]: Refreshing state... [id=test-value2] module.ssm.aws_ssm_parameter.main["test-value1"]: Refreshing state... [id=test-value1] No changes. Your infrastructure matches the configuration. Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
versionを変えると変更検知されます、ドキュメントの仕様通りです。
# main.tf locals { secrets = [ { name = "test-value1" value = "value10" version = 2 }, { name = "test-value2" value = "value20" version = 3 } ] }
変更検知されて、SSMパラメーターも期待通り更新しています。
$ terraform apply module.ssm.aws_ssm_parameter.main["test-value2"]: Refreshing state... [id=test-value2] module.ssm.aws_ssm_parameter.main["test-value1"]: Refreshing state... [id=test-value1] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # module.ssm.aws_ssm_parameter.main["test-value1"] will be updated in-place ~ resource "aws_ssm_parameter" "main" { ~ has_value_wo = true -> (known after apply) id = "test-value1" name = "test-value1" tags = {} ~ value_wo_version = 1 -> 2 # (11 unchanged attributes hidden) } # module.ssm.aws_ssm_parameter.main["test-value2"] will be updated in-place ~ resource "aws_ssm_parameter" "main" { ~ has_value_wo = true -> (known after apply) id = "test-value2" name = "test-value2" tags = {} ~ value_wo_version = 2 -> 3 # (11 unchanged attributes hidden) } Plan: 0 to add, 2 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.ssm.aws_ssm_parameter.main["test-value2"]: Modifying... [id=test-value2] module.ssm.aws_ssm_parameter.main["test-value1"]: Modifying... [id=test-value1] module.ssm.aws_ssm_parameter.main["test-value1"]: Modifications complete after 1s [id=test-value1] module.ssm.aws_ssm_parameter.main["test-value2"]: Modifications complete after 1s [id=test-value2] Apply complete! Resources: 0 added, 2 changed, 0 destroyed.
VS CodeのLanguage Serverが対応していない
VS CodeのTerraform拡張をいれても、Language Serverがまだ対応していないようで、value_wo
がwrite-only attribute
として認識されません。これは仕方ない、対応待ちで。