Terraformデータソースgoogle_client_configはうかつに使うと危険
概要
-
google_client_config
は適切な運用を行わないとセキュリティリスクがあります。 - リージョンやゾーンの設定を取得するのに便利そうですが、実際のところはほとんどの場合、使わないほうが良いでしょう。
背景
-
Terraform を使用している時、プロバイダーで指定したパラメーターと同じ値をリソースで使用するケースがあります:
provider "google" { project = "your-project-id" } resource "google_project_iam_member" "project" { # provider で指定したのと同じ値を指定することが多い project = "your-project-id" role = "roles/editor" member = "user:jane@example.com" }
-
こういったパラメーターは1回だけ指定するように実装したくなりますが (いわゆる DRY)、残念ながら Terraform ではプロバイダーで指定したパラメーターを参照して再利用する機能がありません。
- Expose provider configuration for referencing #1199 で過去に機能要望が出ているが、対応しないとされているため、今後も機能の拡張が行われることはなさそう。
-
他の方法の一つとして、データソースを使用することで provider に設定した値を取得する方法があります。プロジェクトIDの場合、
google_project
が利用できます:provider "google" { project = "your-project-id" } data "google_project" "project" { } resource "google_project_iam_member" "project" { project = data.google_project.project.project_id role = "roles/editor" member = "user:jane@example.com" }
- プロジェクトIDをプロバイダーで設定せずに環境変数
CLOUDSDK_CORE_PROJECT
で設定する場面でも通用するのもこの方法の魅力です。
- プロジェクトIDをプロバイダーで設定せずに環境変数
google_client_config はリージョンやゾーンの参照の解決になるか?
リージョンやゾーンについても同様にデータソースで参照して再利用したい…という場合に、 一見すると google_client_config
が使えそうに見えます。
実装例: https://github.com/ikedam/zenn_snippets/tree/google_client_config
provider "google" {
region = "asia-northeast2"
zone = "asia-northeast2-a"
}
data "google_client_config" "current" {
}
output "region" {
value = data.google_client_config.current.region
}
output "zone" {
value = data.google_client_config.current.zone
}
けれども実際には google_client_config
はこれらの値の他に アクセストークンも保存する ため、セキュリティ的にかなり扱いづらいものになっています。
上記のような Terraform で作成されるステートファイルの中身を見ると、以下のようなデータが保存されてしまっていることが確認できます:
$ jq -r '.resources[]|select(.type=="google_client_config")|.instances[].attributes' terraform.tfstate
{
"access_token": "ya29.(以下省略)",
"id": "projects/\"your-project-id\"/regions/\"asia-northeast2\"/zones/\"asia-northeast2-a\"",
"project": "your-project-id",
"region": "asia-northeast2",
"zone": "asia-northeast2-a"
}
アクセストークンってなあに
アクセストークンを使用することで、この Terraform を apply するときに使用した Google アカウントやサービスアカウントの権限を行使することができます。
特に、 もとの Terraform テンプレートで対象とした Google Cloud プロジェクトとは別の Google Cloud プロジェクト の API も呼び出し可能です。
例えば以下のように、他の Google Cloud プロジェクトのGCSバケットの一覧取得)を行えます:
$ curl -s \
-H "Authorization: Bearer $(jq -r '.resources[]|select(.type=="google_client_config") |.instances[].attributes.access_token' terraform.tfstate)" \
https://storage.googleapis.com/storage/v1/b?project=your-another-project-id \
|jq -r .items[].name
asia.artifacts.your-another-project-id.appspot.com
your-another-project-id.appspot.com
staging.your-another-project-id.appspot.com
アクセストークンの情報を取得する と以下のことが分かります:
- スコープはユーザー情報と Google Cloud 関連だけになっていること。
- このため、 Google Cloud 以外のサービス(例えば Google Drive など) にこのトークンを使うことはできません。
- トークンの有効期限が約1時間であること
$ curl -s "https://oauth2.googleapis.com/tokeninfo?access_token=$(jq -r '.resources[]|select(.type=="google_client_config")|.instances[].attributes.access_token' terraform.tfstate)"
{
"azp": "xxxxxxxxxx.apps.googleusercontent.com",
"aud": "xxxxxxxxxx.apps.googleusercontent.com",
"sub": "xxxxxxxxxx",
"scope": "openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/sqlservice.login https://www.googleapis.com/auth/accounts.reauth",
"exp": "1723423915",
"expires_in": "3594",
"email": "xxxxxxx@gmail.com",
"email_verified": "true",
"access_type": "offline"
}
google_client_config を使うと困るケース
google_client_config
はアクセストークンを保存してしまうため、以下の両方を満たすケースで問題が起きます:
- Terraform テンプレートをチーム内で共有しており、ステートファイルをリモート(例えば GCS など)に保存している場合。
- ステートファイルを参照することでアクセストークンを入手できます。
- 参照権限さえあれば取得できるので、プロジェクトの Viewer 権限があれば閲覧できます。
- Terraform の実行をユーザーの Google アカウントの権限で行う場合がある場合。
- CI/CD で実行する場合はサービスアカウントの権限で実行しているケースが多く、基本的にサービスアカウントの権限は範囲が制限されているため影響範囲が抑えられます。
- 特に、他の Google Cloud プロジェクトへの影響が出ないケースが多い。
- ただし Terraform 用のサービスアカウントの権限は強く設定している場合も多く(実質該当 Google Cloud プロジェクトの全権限を保有している場合も多い)、影響度合いは個別の判断が必要。
- 更新頻度が少ないので CI/CD を構築しておらず適用を手動で行っているケースや、Terraform の動作確認のためにローカルで実行する場合があるケースだと、個人の Google アカウントのアクセストークンがチーム内で共有される状態になってしまい問題になる。
- CI/CD で実行する場合はサービスアカウントの権限で実行しているケースが多く、基本的にサービスアカウントの権限は範囲が制限されているため影響範囲が抑えられます。
リスクの度合いを整理すると以下のようになるでしょう:
Googleアカウントで実行することがある | サービスアカウントでのみ実行している | |
---|---|---|
Terraformテンプレート/ステートを共有していない | リスクなし | リスクなし |
Terraformテンプレート/ステートを共有している ゆるいアクセス制限のみかけている |
リスク高 | リスク中 |
Terraformテンプレート/ステートを共有していない 極めて厳格なアクセス制限をしている |
リスク高 | リスクなし |
対策
google_client_config
は使わないようにするのが簡単です。
今後の展望としては以下のようなチケットが切られているので、 Terraform や Google プロバイダー側で解決される可能性もあります:
-
Ability to not store certain attributes in TF state #30469
- Terraform のステートファイルに特定の属性を保存しないように指定する機能の提案。
-
A "safe" version of google_client_config #15624
-
google_client_config
のリージョンとゾーンだけを保存する別データソースの提案。
-
DRY を実現するには、リージョンやゾーンの設定は変数や、ローカル変数で行うようにするのが良いでしょう:
variable "region" {
value = "ap-northeast-2"
description = "region of Google Cloud"
}
provider "google" {
region = var.region
}
locals {
region = "ap-northeast-2"
}
provider "google" {
region = local.region
}
また、環境変数 CLOUDSDK_COMPUTE_REGION
や CLOUDSDK_COMPUTE_ZONE
を用いた Terraform の運用はできない場面が出てきます。 CI/CD や Makefile などで TF_VAR_region
などに転記するなどの対処が必要です。
Discussion