こちらは エーピーコミュニケーションズ Advent Calendar 2024 の 1 日目の記事です。

Ephemeral values の登場

先日リリースされた Terraform v1.10.0 で Ephemeral values という新しい機能が登場しました。

Terraform 1.10 improves handling secrets in state with ephemeral values

Terraform 1.10 is generally available, and it includes ephemeral values along with improvements to plan and apply performances.


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 Providerazurerm_resource_group が該当します。

resourcedata に加えて、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 ファイルを開くとそのままパスワードが見えてしまいますね。

alt text

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_passwordsensitive = 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 ファイルにパスワードが格納されます。

alt text

alt text

このように 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 ブロック内の構成
  • provisionerconnection ブロック

事前準備で 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 ファイル管理のハードルが下がるなら秘匿情報が含まれないほうが当然いいですよね。

また、できると思っていた使い方ができないことが分かって、実際に触ってみることは大事だなと思いました。