こちらは エーピーコミュニケーションズ Advent Calendar 2024 の 1 日目の記事です。
Ephemeral values の登場
先日リリースされた Terraform v1.10.0 で Ephemeral values という新しい機能が登場しました。
Ephemeral values は次の要素で構成されています。
- Ephemeral input variables / Ephemeral output variables
- Ephemeral resources
- Write-only attribute (v1.11 で追加予定)
この中でも今回は Ephemeral resources に焦点を当ててみます。
Ephemeral resources
Terraform にはクラウドプロバイダー等が提供しているリソースを管理する resource
ブロックと、リソースを参照するための data
ブロックがあります。
例えば、Azure のリソースグループ “example” を Terraform で作成する場合は resource
ブロックを次のように記述します。
resource "azurerm_resource_group" "example" {
name = "example"
location = "japaneast"
}
一方、Terraform ではリソースグループを管理せず、既存のリソースグループを参照するだけの場合は data
ブロックを使います。
data "azurerm_resource_group" "example" {
name = "existing"
}
output "id" {
value = data.azurerm_resource_group.example.id
}
リソース用のブロックには Terraform Provider が提供するリソース名を指定します。
上記の例では Azure リソースを管理するための AzureRM Provider の azurerm_resource_group
が該当します。
resource
と data
に加えて、Ephemeral values の構成要素の一つ、Ephemeral resources では ephemeral
というブロックを使ってリソースの情報を扱います。
ただし、どのリソースを Ephemerarl resources で扱えるかは Terraform Provider に依存しています。
Azure の場合、現在利用できるリソースは Azure Key Vault で管理されるシークレット azurerm_key_vault_secret
と証明書 azurerm_key_vault_certificate
の 2 種類だけになります。
これらの Ephemeral resources は AzureRM Provider の v4.11.0 で追加されています。
試す場合は Terraform 自体のバージョンとあわせて Provider のバージョンにも注意してください。
Azure Key Vault の Ephemeral resources を試す
というわけで、Azure Key Vault シークレットの Ephemeral resources を試してみます。
Azure Key Vault に格納されているシークレット “password” を管理者のパスワードに使って SQL Database を作成します。
事前準備
事前準備として Azure Key Vault シークレットを作ります。 Azure ポータルで作ってもよいですが、State ファイルにパスワードが残ってしまうことも確認したいので Terraform を使いました。
次の構成ファイルを Terraform Apply すると Ephemer@1-p@ssw0rd
という値をもつ Azure Key Vault シークレットが作られます。
今回はお試しとして構成内に直接シークレット値を記述していますが、実際にシークレットを使う場合は変数などで渡すようにして構成ファイルにはシークレットなどの秘匿情報は残さないようにしてください!
# リソースグループ作成
resource "azurerm_resource_group" "example" {
name = "terraform-ephemeral-resource"
location = "japaneast"
}
# Terraform を実行しているクライアント情報を参照
data "azurerm_client_config" "current" {}
# Azure Key Vault 作成
resource "azurerm_key_vault" "example" {
name = "ephemeralkv"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
enable_rbac_authorization = true
}
# Terraform 実行クライアントに Azure Key Vault の権限を追加
resource "azurerm_role_assignment" "example" {
scope = azurerm_key_vault.example.id
role_definition_name = "Key Vault Administrator"
principal_id = data.azurerm_client_config.current.object_id
}
# Azure Key Vault シークレット作成
resource "azurerm_key_vault_secret" "password" {
name = "password"
value = "Ephemer@1-p@ssw0rd"
key_vault_id = azurerm_key_vault.example.id
}
Apply が完了するとローカルのディレクトリに terraform.tfstate
という State ファイルが作られます。
この State ファイルには Terraform が管理しているリソースの情報が記録されていきますが、秘匿情報もプレーンテキストで残ってしまいます。
今回は resource "azurerm_key_vault_secret"
というブロックにパスワードを記述していて、State ファイルを開くとそのままパスワードが見えてしまいますね。
data ブロックを使う場合
Ephemeral resources を試す前に、data
ブロックを使った場合はどのような形で秘匿情報が扱われるか確認してみます。
さきほどとは異なるディレクトリで次の Terraform 構成ファイルを作ります。
# 事前準備で作成した Azure Key Vault を参照
data "azurerm_key_vault" "example" {
name = "ephemeralkv"
resource_group_name = "terraform-ephemeral-resource"
}
# 事前準備で作成した Azure Key Vault シークレットを参照
data "azurerm_key_vault_secret" "password" {
name = "password"
key_vault_id = data.azurerm_key_vault.example.id
}
# 管理者パスワードに data ブロックの Azure Key Vault シークレットを使った論理サーバー作成
resource "azurerm_mssql_server" "example" {
name = "ephemeral-sqlserver"
resource_group_name = data.azurerm_key_vault.example.resource_group_name
location = data.azurerm_key_vault.example.location
version = "12.0"
administrator_login = "ephemeral-user"
administrator_login_password = data.azurerm_key_vault_secret.password.value
}
# SQL Database 作成
resource "azurerm_mssql_database" "example" {
name = "ephemeral-db"
server_id = azurerm_mssql_server.example.id
max_size_gb = 1
sku_name = "Basic"
}
こちらの構成ファイルにはパスワードは直接記述せず、事前準備で作った Azure Key Vault シークレットを data
ブロックを使って参照します。
administrator_login_password = data.azurerm_key_vault_secret.password.value
Terraform Plan を確認しつつ、Plan 結果を Apply で使えるように Plan ファイルを使う場合もあるかと思うので、-out オプションをつけて Plan ファイルも出力するようにします。
$ terraform plan -out=plan-data.txt
data.azurerm_key_vault.example: Reading...
data.azurerm_key_vault.example: Read complete after 0s [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/terraform-ephemeral-resource/providers/Microsoft.KeyVault/vaults/ephemeralkv]
data.azurerm_key_vault_secret.password: Reading...
data.azurerm_key_vault_secret.password: Read complete after 1s [id=https://ephemeralkv.vault.azure.net/secrets/password/fdf0cb32ac764b55baec96f43e64a050]
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:
# azurerm_mssql_database.example will be created
+ resource "azurerm_mssql_database" "example" {
(省略)
}
# azurerm_mssql_server.example will be created
+ resource "azurerm_mssql_server" "example" {
+ administrator_login = "ephemeral-user"
+ administrator_login_password = (sensitive value) <- パスワード
+ connection_policy = "Default"
+ fully_qualified_domain_name = (known after apply)
+ id = (known after apply)
+ location = "japaneast"
+ minimum_tls_version = "1.2"
+ name = "ephemeral-sqlserver"
+ outbound_network_restriction_enabled = false
+ primary_user_assigned_identity_id = (known after apply)
+ public_network_access_enabled = true
+ resource_group_name = "terraform-ephemeral-resource"
+ restorable_dropped_database_ids = (known after apply)
+ version = "12.0"
}
Plan: 2 to add, 0 to change, 0 to destroy.
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: plan-data.txt
To perform exactly these actions, run the following command to apply:
terraform apply "plan-data.txt"
Terraform Plan の標準出力にはパスワードは (sensitive value)
と表示されていて、秘匿情報が見えないようになっています。
これは AzureRM Provider の azurerm_mssql_server リソースで administrator_login_password
に sensitive = true
という属性がついていて、パスワードなどが直接表示されないような仕組みになっているためです。
terraform show
コマンドで Plan 実行時に作成された Plan ファイルの中身も見てみます。
$ terraform show plan-data.txt
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:
# azurerm_mssql_database.example will be created
+ resource "azurerm_mssql_database" "example" {
(省略)
}
# azurerm_mssql_server.example will be created
+ resource "azurerm_mssql_server" "example" {
+ administrator_login = "ephemeral-user"
+ administrator_login_password = (sensitive value) <- パスワード
+ connection_policy = "Default"
+ fully_qualified_domain_name = (known after apply)
+ id = (known after apply)
+ location = "japaneast"
+ minimum_tls_version = "1.2"
+ name = "ephemeral-sqlserver"
+ outbound_network_restriction_enabled = false
+ primary_user_assigned_identity_id = (known after apply)
+ public_network_access_enabled = true
+ resource_group_name = "terraform-ephemeral-resource"
+ restorable_dropped_database_ids = (known after apply)
+ version = "12.0"
}
Plan: 2 to add, 0 to change, 0 to destroy.
Plan 実行時と同じくパスワードを設定した場所は (sensitive value)
になっていますね。
administrator_login_password = (sensitive value)
一見問題なさそうですが、terraform show
コマンドに -json オプションをつけて JSON 形式で Plan ファイルを見てみます。
長いので一部省略しています。
$ terraform show -json plan-data.txt | jq
{
"format_version": "1.2",
"terraform_version": "1.10.0",
"planned_values": {
"root_module": {
"resources": [
(省略)
{
"address": "azurerm_mssql_server.example",
"mode": "managed",
"type": "azurerm_mssql_server",
"name": "example",
"provider_name": "registry.terraform.io/hashicorp/azurerm",
"schema_version": 0,
"values": {
"administrator_login": "ephemeral-user",
"administrator_login_password": "Ephemer@1-p@ssw0rd", <- パスワード
(省略)
},
"sensitive_values": {
"administrator_login_password": true,
"azuread_administrator": [],
"identity": [],
"restorable_dropped_database_ids": []
}
}
]
}
},
"resource_changes": [
(省略)
{
"address": "azurerm_mssql_server.example",
"mode": "managed",
"type": "azurerm_mssql_server",
"name": "example",
"provider_name": "registry.terraform.io/hashicorp/azurerm",
"change": {
"actions": [
"create"
],
"before": null,
"after": {
"administrator_login": "ephemeral-user",
"administrator_login_password": "Ephemer@1-p@ssw0rd", <- パスワード
(省略)
},
(省略)
}
}
],
"prior_state": {
"format_version": "1.0",
"terraform_version": "1.10.0",
"values": {
"root_module": {
"resources": [
(省略)
{
"address": "data.azurerm_key_vault_secret.password",
"mode": "data",
"type": "azurerm_key_vault_secret",
"name": "password",
"provider_name": "registry.terraform.io/hashicorp/azurerm",
"schema_version": 0,
"values": {
(省略)
"value": "Ephemer@1-p@ssw0rd", <- パスワード
"version": "fdf0cb32ac764b55baec96f43e64a050",
"versionless_id": "https://ephemeralkv.vault.azure.net/secrets/password"
},
"sensitive_values": {
"tags": {},
"value": true
}
}
]
}
}
},
(省略)
}
JSON で出力してみると、Plan ファイルには SQL Database のパスワードと data
ブロックで参照している Azure Key Vault シークレットのパスワードもプレーンテキストで格納されていることが分かりますね。
シークレットが格納されているリソースを data
ブロックで参照しているから安全、という訳ではようです。
この Plan ファイルを使って Terraform Apply すると State ファイルにパスワードが格納されます。
このように State ファイルだけでなく Plan ファイルにも秘匿情報が含まれるので管理には十分に気をつけましょう。
ehphemeral ブロックを使う場合
ということで、data
ブロックの代わりに ephemeral
ブロックを使ってみます。
Azure Key Vault シークレットの Ephemeral resource はブロック名を変更するだけです。
data "azurerm_key_vault" "example" {
name = "ephemeralkv"
resource_group_name = "terraform-ephemeral-resource"
}
# data を ephemeral に変更
ephemeral "azurerm_key_vault_secret" "password" {
name = "password"
key_vault_id = data.azurerm_key_vault.example.id
}
resource "azurerm_mssql_server" "example" {
name = "ephemeral-sqlserver"
resource_group_name = data.azurerm_key_vault.example.resource_group_name
location = data.azurerm_key_vault.example.location
version = "12.0"
administrator_login = "ephemeral-user"
administrator_login_password = ephemeral.azurerm_key_vault_secret.password.value # data を ephemeral に変更
}
resource "azurerm_mssql_database" "example" {
name = "ephemeral-db"
server_id = azurerm_mssql_server.example.id
max_size_gb = 1
sku_name = "Basic"
}
早速 Terraform Plan してみましょう。
$ terraform plan -out=plan-ephemeral.txt
╷
│ Error: Invalid use of ephemeral value
│
│ with azurerm_mssql_server.example,
│ on azure.tf line 18, in resource "azurerm_mssql_server" "example":
│ 18: administrator_login_password = ephemeral.azurerm_key_vault_secret.password.value
│
│ Ephemeral values are not valid in resource arguments, because resource instances must persist between Terraform phases.
╵
おや、エラーになってしまいましたね……。
メッセージを見てみると Ephemeral values はリソースの引数に設定できないようですし、Ephemeral resources のドキュメントにも Ephemeral resources を参照できる場所は次に限定されていると書かれています。
- 他の
ephemeral
ブロック- Local 値
- Ephemeral 変数
- Ephemeral 出力
provider
ブロック内の構成provisioner
とconnection
ブロック
事前準備で Azure Key Vault シークレットを作成したときのように、resource
ブロックで作ったリソースは秘匿情報を State ファイルにプレーンテキストで残してしまうため、折角 State ファイルに残らないようにする Ephemeral resources を使っても意味がなくなってしまいます。
ということで、Ephemeral resources を参照してリソース作成はできないようです。
エラーにならないように構成ファイルから SQL Database 部分を削って、Ephemeral resources が State ファイルに残らないことを確認します。 さきほどと同じように Plan ファイルを作ります。
$ terraform plan -out=plan-ephemeral.txt
data.azurerm_key_vault.example: Reading...
data.azurerm_key_vault.example: Read complete after 2s [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/terraform-ephemeral-resource/providers/Microsoft.KeyVault/vaults/ephemeralkv]
ephemeral.azurerm_key_vault_secret.password: Opening...
ephemeral.azurerm_key_vault_secret.password: Opening complete after 1s
ephemeral.azurerm_key_vault_secret.password: Closing...
ephemeral.azurerm_key_vault_secret.password: Closing complete after 0s
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.
Ephemeral resources を resource
ブロックで参照しなければエラーになりません。
出力された Plan ファイルを見てみます。
$ terraform show -json plan-ephemeral.txt | jq
{
"format_version": "1.2",
"terraform_version": "1.10.0",
"planned_values": {
"root_module": {}
},
"prior_state": {
"format_version": "1.0",
"terraform_version": "1.10.0",
"values": {
"root_module": {
"resources": [
{
"address": "data.azurerm_key_vault.example",
"mode": "data",
"type": "azurerm_key_vault",
"name": "example",
"provider_name": "registry.terraform.io/hashicorp/azurerm",
"schema_version": 0,
"values": {
(省略)
}
}
]
}
}
},
(省略)
}
Ephemeral resources は Plan ファイルに出力されませんでしたし、こちらの Plan ファイルを使った Terraform Apply 後の State ファイルにも Ephemeral resources はありませんでした。
どこで使うのか
今回 Ephemeral resources を試してみて、Ephemeral resources は State ファイルに残らないこと、resource
ブロックから参照できないことを確認できました。
HashiCorp のブログには Ephemeral resources で参照したシークレットを PostgreSQL 用の Provider postgresql
で使用する例が載っていました。
ephemeral "aws_secretsmanager_secret_version" "db_master" {
secret_id = data.aws_db_instance.example.master_user_secret[0].secret_arn
}
locals {
credentials = jsondecode(ephemeral.aws_secretsmanager_secret.db_master.secret_string)
}
provider "postgresql" {
host = data.aws_db_instance.example.address
port = data.aws_db_instance.example.port
username = local.credentials["username"]
password = local.credentials["password"]
}
このように、Ephemeral resources で参照した値を Terraform がクラウドプロバイダー等にアクセスするための認証情報として利用するのがメインになるかと思います。
言い換えると、Ephemeral resources で取得した値を埋め込んでリソースを作成できないので、AzureRM Provider だけ使って Azure 環境を Terraform で管理する場合には Ephemeral resources を利用する機会がない気もします。
おわりに
Terraform v1.10.0 で追加された Ephemeral resources を Azure Key Vault でお試ししてみました。 State ファイルには秘匿情報が含まれて当然、という考えでいたのですが、State ファイル管理のハードルが下がるなら秘匿情報が含まれないほうが当然いいですよね。
また、できると思っていた使い方ができないことが分かって、実際に触ってみることは大事だなと思いました。