tech.guitarrapc.cóm

Technical updates

TerraformのEphemeral resourceをどう使うのか

Terraform 1.10.0からEphemeral resourceが追加されました。これは各プロバイダーが対応するEpheemeral resourceと組み合わせて使う安全なData Sourceと呼ぶべきものです。 今回はEphemeral resourceをどのように使うのか調べたメモです。

関連して、Terraform 1.11.0から入るWrite-only Argumentsについて記事を書いたのでどうぞ。

Data Sourceの問題点

TerraformのData Sourceは、Terraformの実行時にリソースを読み取るためのものです。Data Sourceは取得した値をStateやplanファイルに保存しその値を使ってリソースを作成します。このためData Sourceを使う場合は、planファイルやStateファイルに機密情報が残ることに注意が必要です。Ephemeral Resourceの動作を見る前に、Data Sourceで取得した値がStateに書き込まれるのを確認してみましょう。

まずはSSM Parameter Storeにbarというキーでsecret-valueという値を保存します。Data Sourceで取得したいので、AWS Consoleやaws-cliでキーを作っておきます。

image

次に、Data Sourceを使って値を取得するTerraformコードを書いてApplyします。Data Sourceだけの変更ではterraform applyでStateファイルに書き込まれないので適当なリソースも追加します。

# data sourceで取得した値はStateファイルに...?
data "aws_ssm_parameter" "main" {
  name = "bar"
}

resource "aws_ssm_parameter" "main" {
  name  = "foo"
  type  = "String"
  value = data.aws_ssm_parameter.main.value
}

Stateファイルで確認すると、Data Sourceで取得した値がStateファイルに書き込まれていることがわかります。残念です。

{
  "module": "module.ssm",
  "mode": "data",
  "type": "aws_ssm_parameter",
  "name": "main",
  "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
  "instances": [
    {
      "schema_version": 0,
      "attributes": {
        "arn": "arn:aws:ssm:ap-northeast-1:xxxxxxxxxxxx:parameter/bar",
        "id": "bar",
        "insecure_value": "secret-value",
        "name": "bar",
        "type": "String",
        "value": "secret-value",
        "version": 1,
        "with_decryption": true
      },
      "sensitive_attributes": [
        [
          {
            "type": "get_attr",
            "value": "value"
          }
        ]
      ]
    }
  ]
}
// resource aws_ssm_parameter のjsonは省略

image

Ephemeral Resourceの挙動

Ephemeral ResourceはData Sourceと違い、Stateファイルに値を書き込みません。つまり、Terraformの実行時にリソースを取得しその値をその時だけ使う一時リソースなイメージです。「Ephemeral Resourceは固有のライフサイクルを持つ」とドキュメントにありますが、実際ライフサイクルを見ると利用用途がイメージしやすいです。まさにリソースに利用するため一時的にシークレットにアクセスして、その値を中継するというイメージです。

image

先ほどのData Sourceの例をEphemeral Resourceを使って書き換えてみましょう。

data "aws_region" "current" {}
data "aws_caller_identity" "current" {}

ephemeral "aws_ssm_parameter" "foo" {
  arn = "arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter/bar"
}

この内容をterraform applyしてもStateファイルにリソースが書きこまれません。やったー。

Ephemeral Resourceの値はResourceで使えない

ライフサイクルから考えると、従来のResourceやData SourceにEphemeral Resourceの値を使えないと予想できます。使ったらStateファイルに値が書き込まれてしまってコンセプトとずれますから当然ですね。試してみましょう。

data "aws_region" "current" {}
data "aws_caller_identity" "current" {}

ephemeral "aws_ssm_parameter" "foo" {
  arn = "arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter/bar"
}

resource "aws_ssm_parameter" "main" {
  name = "foo"
  type = "String"
  value = ephemeral.aws_ssm_parameter.foo.value
}

期待通り、ResourceでEphemeral Resourceの値を参照できません。

$ terraform validate
╷
│ Error: Invalid use of ephemeral value
│
│   with module.ssm.aws_ssm_parameter.main,
│   on ../modules/ssm/main.tf line 16, in resource "aws_ssm_parameter" "main":
│   16:   value = ephemeral.aws_ssm_parameter.foo.value
│
│ Ephemeral values are not valid in resource arguments, because resource instances must persist between Terraform phases.
╵

Ephemeral Resourceの値を参照する

Epheneral Resourceの値は、local values、ephemeral variables1、ephemeral outputs2、provider、provisionerとconnectionブロックで利用できます。

image

つまりプロバイダーが現実的な利用箇所ですね。3プロバイダーでしか使えないのは割と使い勝手が悪いですが、まぁしょうがない。ドキュメントでPostgreSQLへのDB接続のために利用しているのは、まさに典型ということです。4

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_version.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"]
}

私はEKSでKubernetesを使うので、kubernetes providerでEphemeral Resourceが使えるとうれしいです。幸いIssue #40343PR #40660からEphemeral Resourceaws_eks_cluster_authが追加されAWS Provider v5.84.0でリリースされます。確かにtokenは気になっていたのでうれしいですね。

ephemeral "aws_eks_cluster_auth" "example" {
  name = data.aws_eks_cluster.example.id
}

provider "kubernetes" {
  host                   = data.aws_eks_cluster.example.endpoint
  cluster_ca_certificate = base64decode(data.aws_eks_cluster.example.certificate_authority[0].data)
  token                  = ephemeral.aws_eks_cluster_auth.example.token
}

provider "helm" {
  kubernetes {
    host                   = data.aws_eks_cluster.example.endpoint
    cluster_ca_certificate = base64decode(data.aws_eks_cluster.example.certificate_authority[0].data)
    token                  = ephemeral.aws_eks_cluster_auth.example.token
  }
}

メモ

Ephemeral Resourceに対応するリソース実装を見ると、terraform-plugin-framework/ephemeralephemeral.MetadataRequestephemeral.MetadataResponseを使ってデータを取得するんですね。単純にstsClient経由で値を直接とってきて、リクエスト/レスポンスに詰めているだけって感じですが、なるほど都度通信入るのは既存のTerraform処理的にもあまり変わらないので違和感ないです。

まとめ

Ephemeral ResourceはData Sourceと違い、Stateファイルに値を書き込まないため機密情報を扱う際に安全です。Terraform 1.10以上、かつ対応するEphemeral Resourceが追加されないといけないので、しばらくはProviderの対応待ちになりそうですが、Data SourceでStateに保存されるのはずっと疑問だったので是正されてうれしい限りです。

できればResourceのpasswordとかのセクションに関しても、Stateファイルに平文で書き込まれなくなる未来が来るといいですね。これに関する話題はIssue #516があります。Ephemeral ResourceでData SourceのStateに対する対応が入ったので、次はResourceのフィールド対応が待たれます。5

参考


  1. Ephemeral variablesは、variableseにephemeral = trueを指定したものです。
  2. Ephemeral outputsは、outputsにephemeral = trueを指定したものです。モジュールのoutputsでのみ利用でき、root moduleのoutputsで利用できません。
  3. プロビジョナーやコネクションブロックを使っている人はそれほど多くないと予想しますが、使えてよかったよかった。
  4. DBのプロビジョニングにTerraformを使いたくないですが、用途があるのは確かにそう
  5. Stateファイル上で平文保持ではなく、対象フィールドに対して何かしらの暗号化が施せるようにするぐらいしか思いつかいないですが果たしてそれがベターかというと微妙そう