Ephemeral resource で Terraform State ファイルを守る
こんにちは、クラウドエース 第一開発部の阿部です。
この記事では、 Terraform v1.10 以降で導入された Ephemeral resource について説明します。
はじめに
Terraform では、 State ファイルによって管理対象リソースの状態を追跡しています。
State ファイルは JSON 形式で記録され、Terraform 上のリソース名、および、クラウドリソースの固有 ID などが記録されています。
State ファイルには他にも、クラウドリソースの API で取得可能な属性情報が記録されています。
Google Cloud Provider では、API で取得可能な値は原則全て取得され State ファイルに保存されます。
State ファイルに保存される情報は、 terraform_remote_state
Data リソースを使用することで管理ディレクトリを横断してリソースを参照できるため便利な反面、機密情報といった情報も入ってしまうため、セキュリティリスクがあります。
例えば、サービスアカウントキーのリソースを作成すると、秘密鍵の情報が State ファイルにも保存されます。
もちろん、State ファイルに機密情報が保存されたから即脆弱、というわけではありません。
Google Cloud で Terraform を使用する場合、State の保存先は Cloud Storage を使用することが推奨されています。
その際に、Cloud Storage バケットの IAM を適切に設定することで、State ファイルが漏洩するリスクを軽減できます。
ただ、よりセキュリティを考慮するのであれば、State ファイルに機密情報を保存しない方がよいです。
Ephemeral resource とは
Terraform v1.10 で新しく Ephemeral resource が導入されました。
Ephemeral resource は、State ファイルや Plan ファイルに情報を保存しないリソースです。
ephemeral
ブロックを使用して、リソースを定義します。
使用方法は Data リソースに似ています。
-
ephemeral
ブロックではクラウドリソースの固有 ID を TF ファイルに記述します。 - Terraform が Ephemeral リソースにアクセスする必要がある場合、そのときのみリソースを参照して情報を取得します。
- Ephemeral リソースの情報が不要になったら、Terraform はクラウドリソースへのアクセスを終了し、情報を破棄します。
Google Cloud Provider でサポートされる Ephemeral resource
Google Cloud Provider では v6.13.0 から Ephemeral resource をサポートしています。
記事執筆時点(2025/03/03)では、以下のリソースを使用可能です。
- google_service_account_access_token
- google_service_account_id_token
- google_service_account_jwt
- google_service_account_key
参考: Ephemeral Resources in the Google Cloud provider
残念ながら、まだ Secret Manager version を参照する Ephemeral resource は存在しないようです。
Feature Request は存在するので、今後のアップデートに期待しましょう。
Ephemeral resource を使ってみる
検証環境について
以下の環境で検証しました。
- Terraform v1.10.5
- Google Cloud Provider v6.23.0
- Ubuntu 24.04 LTS (Windows 11 WSL2)
これまでの方法で実装
現時点では、サービスアカウントに関連する Ephemeral resource がサポートされています。
以下は、google_service_account_access_token
を使用して、 Impersonated Service Account のアクセストークン使って Provider を分離するパターンです。
これまでの方法として、 Data resource を使って実装します。
variables.tf
variable "project_id" {
type = string
description = "Google Cloud Project ID"
}
provider.tf
terraform {
required_version = "1.10.5"
required_providers {
google = {
source = "hashicorp/google"
version = "6.23.0"
}
}
}
provider "google" {
project = var.project_id
}
provider "google" {
alias = "impersonate"
access_token = data.google_service_account_access_token.test.access_token
}
services.tf
locals {
service_apis = [
"iam.googleapis.com",
"iamcredentials.googleapis.com",
"serviceusage.googleapis.com",
]
}
resource "google_project_service" "main" {
for_each = toset(local.service_apis)
service = each.value
disable_dependent_services = false
disable_on_destroy = false
}
service_accounts.tf
resource "google_service_account" "test" {
account_id = "ephemeral-resource-test"
display_name = "ephemeral-resource-test"
depends_on = [google_project_service.main]
}
resource "google_service_account_iam_member" "test" {
service_account_id = google_service_account.test.name
role = "roles/iam.serviceAccountTokenCreator"
member = "user:YOUR_GOOGLE_ACCOUNT"
}
data "google_service_account_access_token" "test" {
target_service_account = google_service_account.test.email
scopes = ["cloud-platform"]
depends_on = [google_service_account_iam_member.test]
}
※ YOUR_GOOGLE_ACCOUNT
は、ご自身の Google アカウントに置き換えてください。
なお、このコードを実行すると、 google_service_account_access_token
が 403 エラーになる場合があります。
google_service_account_iam_member
で設定したサービスアカウントトークン作成者ロールの反映にタイムラグがあるためです。
エラーが出た場合は、数分待ってから再度実行してください。
上記が正常終了したあと、State ファイルを参照してみます。
terraform state pull > tfstate.before.json
以下のような内容が State ファイルに記録されていることが確認できます。(全てを載せると長いため一部を抜粋しています。また、アクセストークン文字列もマスクしています。)
{
"version": 4,
"terraform_version": "1.10.5",
"serial": 22,
"lineage": "06b0a7dd-c445-f71e-554b-7b42cb01cdb7",
"outputs": {},
"resources": [
{
"mode": "data",
"type": "google_service_account_access_token",
"name": "test",
"provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"access_token": "ya29.c.(アクセストークン文字列)",
"delegates": null,
"id": "projects/-/serviceAccounts/ephemeral-resource-test@EXAMPLE_PROJECT.iam.gserviceaccount.com",
"lifetime": "3600s",
"scopes": [
"cloud-platform"
],
"target_service_account": "ephemeral-resource-test@EXAMPLE_PROJECT.iam.gserviceaccount.com"
},
"sensitive_attributes": [
[
{
"type": "get_attr",
"value": "access_token"
}
]
]
}
]
},
...(snip)...
}
google_service_account_access_token
Data リソースは、access_token
属性に、サービスアカウントから取得したアクセストークン文字列を保持しています。
アクセストークンはクラウドリソースへアクセスするための認可情報です。(補足として、アクセストークン文字列は通常 1 時間程度で期限切れになります。)
もし仮に、このサービスアカウントが高い権限を持っていて、かつ、State ファイルが漏洩してしまった場合は、サービスアカウントの権限を悪用されてしまうリスクがあります。
Ephemeral resource を使った実装
Ephemeral resource を使った実装は、以下のようになります。
variables.tf
variable "project_id" {
type = string
description = "Google Cloud Project ID"
}
provider.tf
terraform {
required_version = "1.10.5"
required_providers {
google = {
source = "hashicorp/google"
version = "6.23.0"
}
}
}
provider "google" {
project = var.project_id
}
provider "google" {
alias = "impersonate"
project = var.project_id
access_token = ephemeral.google_service_account_access_token.test.access_token
}
services.tf
locals {
service_apis = [
"iam.googleapis.com",
"iamcredentials.googleapis.com",
"serviceusage.googleapis.com",
]
}
resource "google_project_service" "main" {
for_each = toset(local.service_apis)
service = each.value
disable_dependent_services = false
disable_on_destroy = false
}
service_accounts.tf
resource "google_service_account" "test" {
account_id = "ephemeral-resource-test"
display_name = "ephemeral-resource-test"
depends_on = [google_project_service.main]
}
resource "google_service_account_iam_member" "test" {
service_account_id = google_service_account.test.name
role = "roles/iam.serviceAccountTokenCreator"
member = "user:abe@cloud-ace.jp"
}
ephemeral "google_service_account_access_token" "test" {
target_service_account = google_service_account.test.email
scopes = ["cloud-platform"]
depends_on = [google_service_account_iam_member.test]
}
※ YOUR_GOOGLE_ACCOUNT
は、ご自身の Google アカウントに置き換えてください。
差分は以下の通りです。
===================================================================
diff --git a/ephemeral-values/main.tf b/ephemeral-values/main.tf
--- a/ephemeral-values/main.tf (revision 26a6c0318caecb79e8597edf36fbfe62e814c07d)
+++ b/ephemeral-values/main.tf (revision 60d72c629495b88c79387e4abce219bf9731b2bb)
@@ -11,7 +11,7 @@
member = "user:abe@cloud-ace.jp"
}
-data "google_service_account_access_token" "test" {
+ephemeral "google_service_account_access_token" "test" {
target_service_account = google_service_account.test.email
scopes = ["cloud-platform"]
depends_on = [google_service_account_iam_member.test]
===================================================================
diff --git a/ephemeral-values/versions.tf b/ephemeral-values/versions.tf
--- a/ephemeral-values/versions.tf (revision 26a6c0318caecb79e8597edf36fbfe62e814c07d)
+++ b/ephemeral-values/versions.tf (revision 60d72c629495b88c79387e4abce219bf9731b2bb)
@@ -15,5 +15,5 @@
provider "google" {
alias = "impersonate"
project = var.project_id
- access_token = data.google_service_account_access_token.test.access_token
+ access_token = ephemeral.google_service_account_access_token.test.access_token
}
差分を見ていただいたとおり、data ブロックの代わりに ephemeral ブロックを使用して記述します。
参照する際も data.~
で記述するところを ephemeral.~
に変更することで、Ephemeral resource の value を参照できます。
この内容を apply して、 State ファイルの差分も確認してみます。
terraform apply
terraform state pull > tfstate.after.json
--- tfstate.before.json 2025-03-03 18:04:30.549048696 +0900
+++ tfstate.after.json 2025-03-03 18:22:41.468787506 +0900
@@ -1,40 +1,11 @@
{
"version": 4,
"terraform_version": "1.10.5",
- "serial": 22,
+ "serial": 23,
"lineage": "06b0a7dd-c445-f71e-554b-7b42cb01cdb7",
"outputs": {},
"resources": [
{
- "mode": "data",
- "type": "google_service_account_access_token",
- "name": "test",
- "provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
- "instances": [
- {
- "schema_version": 0,
- "attributes": {
- "access_token": "ya29.c.(アクセストークン文字列)",
- "delegates": null,
- "id": "projects/-/serviceAccounts/ephemeral-resource-test@EXAPLE_PROJECT.iam.gserviceaccount.com",
- "lifetime": "3600s",
- "scopes": [
- "cloud-platform"
- ],
- "target_service_account": "ephemeral-resource-test@EXAPLE_PROJECT.iam.gserviceaccount.com"
- },
- "sensitive_attributes": [
- [
- {
- "type": "get_attr",
- "value": "access_token"
- }
- ]
- ]
- }
- ]
- },
- {
"mode": "managed",
"type": "google_project_service",
"name": "main",
State ファイルに、 google_service_account_access_token
の情報そのものが存在していないことがわかりました。
Ephemeral resource はドキュメント通り、中途半端に管理情報を残すようなことはせず、必要なときだけ情報を取得するようになっています。
その他の Ephemeral resource の特徴
先ほど検証した、State ファイルに保存されないという特徴以外にも、Ephemeral resource には以下のような特徴があります。
Ephemeral value について
Ephemeral resource から参照できる値を Ephemeral value と呼びます。
Ephemeral value は、Terraform 内部で「Ephemeral な値である」とマークされています。
Ephemeral value が厄介な点は、通常のリソースの引数に指定できないことです。
Ephemeral value をリソースの引数に指定すると、以下のようなエラーが発生し、plan に失敗します。
╷
│ Error: Invalid use of ephemeral value
│
│ with google_secret_manager_secret_version.test,
│ on main.tf line 30, in resource "google_secret_manager_secret_version" "test":
│ 30: secret_data = ephemeral.google_service_account_access_token.test.access_token
│
│ Ephemeral values are not valid in resource arguments, because resource
│ instances must persist between Terraform phases.
╵
よって、 Ephemeral value は State ファイルや plan ファイルに保存されない箇所にのみ使用することができます。
具体的には以下の箇所で使用できます。
- write-only argument
- 別の ephemeral resource
- local values
- ephemeral variables
- ephemeral outputs
- providers ブロックの設定
- provisioner と connection ブロック
write-only argument は、Terraform v1.11 からサポートされた新しい機能です。これについては、別のブログ記事で紹介したいと思います。
また、ドキュメントでは Ephemeral output で使用できると記載されていますが、これはあくまで Child module として output を使用する場合の話です。
Root module の output (つまり、Terraform を実行するディレクトリで定義した output) では使用できません。
使用した場合は、以下のようなエラーが発生します。
╷
│ Error: Ephemeral value not allowed
│
│ on outputs.tf line 8, in output "ephemeral_sa_access_token":
│ 8: value = ephemeral.google_service_account_access_token.test.access_token
│
│ This output value is not declared as returning an ephemeral value, so it cannot be set to a result derived from an ephemeral value.
このように、 Ephemeral value はどこでも参照できるわけではなく、限られた箇所でのみ使用できることに注意してください。
まとめ
Terraform の Ephemeral resource、 Ephemeral value について説明しました。
これまで、Terraform の State ファイルは機密情報を含むことがあり、セキュリティリスクがあるとされてきました。
Ephemeral resource を活用することで、State ファイルに機密情報を保存せずに、クラウドリソースの情報を取得できるようになります。
ただ、まだ対応しているリソースは限定的であること、使いどころは限られていることに注意して活用していきましょう。
この記事が、Terraform を利用する方の参考になれば幸いです。
Discussion