Google Cloud との動的クレデンシャルをプライベートな Terraform Enterprise で利用する
Google Cloudとの動的クレデンシャルをGoogle Cloud環境に立てたプライベートなTerraform Enterprise(以下TFE)から利用する際の設定に関するメモです。
Prerequisites
TFEに関してはこちらに記載した内容をベースに構築しています。
TFE specific requirements with dynamic credentials
TFEで動的クレデンシャルを利用する際、以下の2つのエンドポイントに対して外部からのアクセスが必要とドキュメントに記載があります。
-
/.well-known/openid-configuration
: 標準的な OIDC メタデータ -
/.well-known/jwks
: クラウドプラットフォームが、TFEからのものであると主張するトークンの真正性を検証するために使用する、TFEの公開鍵
これらのエンドポイントにアクセスするとそれぞれ以下の様なレスポンスを返します。
{
"issuer": "https://${TFE_HOSTNAME}",
"jwks_uri": "https://${TFE_HOSTNAME}/.well-known/jwks",
"response_types_supported": [
"id_token"
],
"claims_supported": [
"sub",
"aud",
"exp",
"iat",
"iss",
"jti",
"nbf",
"ref",
"terraform_run_phase",
"terraform_workspace_id",
"terraform_workspace_name",
"terraform_organization_id",
"terraform_organization_name",
"terraform_project_id",
"terraform_project_name",
"terraform_run_id",
"terraform_full_workspace"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"scopes_supported": [
"openid"
],
"subject_types_supported": [
"public"
]
}
{
"keys": [
{
"kty": "RSA",
"n": "nZdUb...dQTYQ",
"e": "AQAB",
"kid": "5f93d...c62b6",
"use": "sig",
"alg": "RS256"
}
]
}
動的クレデンシャルをTFEで利用する場合、これらのエンドポイントに対してクラウドプラットフォームからアクセス出来る様にしておく必要があります。
TFEへのインバウンドアクセスをファイヤーウォール設定で絞っている中、Google Cloudとの動的クレデンシャルを利用しようとすると、terraform apply
を実行するフェーズで以下の様なエラーメッセージが出力されました。
Error: Error creating service account: Post "https://iam.googleapis.com/v1/projects/xxxxx/serviceAccounts?alt=json&prettyPrint=false": oauth2/google: unable to generate access token: Post "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/yyyyy@xxxxx.iam.gserviceaccount.com:generateAccessToken": oauth2/google: status code 400: {"error":"invalid_grant","error_description":"Error connecting to the given credential's issuer."}
動的クレデンシャルを利用するために、Google Cloudで利用されているグローバルIPアドレスをTFEに対するインバウンドアクセスで許可するというファイヤーウォール設定を行うのはあまり現実的では無いと思います。
また、動的クレデンシャルをVault-backend dynamic credentialsを介して利用する形であれば、TFE <-> Vault -> Cloud Platform というネットワークアクセスになるため、TFEに対しては、Vaultからのインバウンドアクセスのみで、パブリックからアクセスさせる必要は無くなるのですが、この場合は別途Vaultをご用意いただく必要があります。
Upload TFE OIDC JWK to Workload Identity Provider
Google Cloudにおいては、Workload Identity ProviderにOIDC JWKをアップロードするという事が出来ます。この機能を活用いただく事で、必要なインバウンドアクセスのみを許可したプライベートなTFEからGoogle Cloudとの動的クレデンシャルを利用する事が出来ます。
動的クレデンシャルの設定に関しては、以下のTerraformコードによって既に設定されている事を前提とします。
まずは、TFEのAPIエンドポイント/.well-known/jwks
にアクセスし、レスポンスされた内容をファイルに保存します。
curl https://${TFE_HOSTNAME}/.well-known/jwks | jq . > tfe-jwks.json
Google CloudのコンソールでWorkload Identity Federationを選択し、動的クレデンシャル用に作成したIdentity Providerを選択し、JWK file(JSON)
の設定項目に、保存したJSONファイルtfe-jwks.json
をアップロードし保存します。
TFEのJWKS JSONファイルのアップロード
設定自体はこれで完了です。この設定をして頂ければ、Google Cloudで動的クレデンシャルを利用する際に、TFEへのインバウンドアクセスをパブリックに開放して頂く必要は無くなります。
Rotation period of TFE JWKS
Google CloudにアップロードしたTFEのJWKSですが、以下のAPIドキュメントに記載がある通り90日毎にローテーションされる様です。
This key automatically rotates every 90 days, hitting this endpoint resets this timer.
そのため、上記方法も一度設定した後はそのまま使い続けられるわけではなく、90日に一回は更新させる必要がありそうです。
Google Cloudのコンソールから行った設定に関しては、google_iam_workload_identity_pool_provider
リソースのoidc
ブロックのjwks_json
パラメータで設定出来そうなので、動的クレデンシャルの設定をTerraformで行い、TerraformのワークフローはTFEで管理する様にしておけば、ローテーション時の作業負荷を軽減出来そうです。
例えば、以下の様な形でgoogle_iam_workload_identity_pool_provider
リソースを定義し、tfe_jwks
Terraform変数を、TFEのVariableとして登録します。
登録する際、Keyはtfe_jwks
、Valueは、TFEのエンドポイント/.well-known/jwks
で取得したJSONファイルを1行に変換した値(VS CodeだとCtrl+J
で変換出来ます)を登録します。
resource "google_iam_workload_identity_pool_provider" "tfe_provider" {
workload_identity_pool_id = google_iam_workload_identity_pool.tfe_pool.workload_identity_pool_id
workload_identity_pool_provider_id = "my-tfe-provider-id"
attribute_mapping = {
"google.subject" = "assertion.sub",
"attribute.aud" = "assertion.aud",
"attribute.terraform_run_phase" = "assertion.terraform_run_phase",
"attribute.terraform_project_id" = "assertion.terraform_project_id",
"attribute.terraform_project_name" = "assertion.terraform_project_name",
"attribute.terraform_workspace_id" = "assertion.terraform_workspace_id",
"attribute.terraform_workspace_name" = "assertion.terraform_workspace_name",
"attribute.terraform_organization_id" = "assertion.terraform_organization_id",
"attribute.terraform_organization_name" = "assertion.terraform_organization_name",
"attribute.terraform_run_id" = "assertion.terraform_run_id",
"attribute.terraform_full_workspace" = "assertion.terraform_full_workspace",
}
oidc {
issuer_uri = "https://${var.tfe_hostname}"
jwks_json = var.tfe_jwks
}
attribute_condition = "assertion.sub.startsWith(\"organization:${var.tfe_organization_name}:project:${var.tfe_project_name}:workspace:\")"
}
Rotate TFE JWKS
実際にこのAPIエンドポイントapi/v2/admin/oidc-settings/actions/rotate-key
を利用して、明示的にローテーションを行なってみると、.well-known/jwks
エンドポイントから得られる情報が更新されます。
curl \
--header "Authorization: Bearer $TFE_API_TOKEN" \
--header "Content-Type: application/vnd.api+json" \
--request POST \
https://${TFE_HOSTNAME}/api/v2/admin/oidc-settings/actions/rotate-key
ローテーションした後に、curl https://${TFE_HOSTNAME}/.well-known/jwks | jq .
コマンドで確認してみると、以下の通り、2つのキー情報がある事が確認できます。
{
"keys": [
{
"kty": "RSA",
"n": "nZdUb...dQTYQ",
"e": "AQAB",
"kid": "5f93d...c62b6",
"use": "sig",
"alg": "RS256"
},
{
"kty": "RSA",
"n": "v81lK...j5sBw",
"e": "AQAB",
"kid": "7f3ce...b19d8",
"use": "sig",
"alg": "RS256"
}
]
}
古いバージョンの鍵情報は30日経過すると自動で削除される様ですが、APIエンドポイント/api/v2/admin/oidc-settings/actions/trim-key
から明示的に削除する事も可能です。
curl \
--header "Authorization: Bearer $TFE_API_TOKEN" \
--header "Content-Type: application/vnd.api+json" \
--request POST \
https://${TFE_HOSTNAME}/api/v2/admin/oidc-settings/actions/trim-key
改めて、curl https://${TFE_HOSTNAME}/.well-known/jwks | jq .
コマンドで確認してみると、古いキーは削除され、新しいキーの情報のみ存在する事が確認できます。
{
"keys": [
{
"kty": "RSA",
"n": "v81lK...j5sBw",
"e": "AQAB",
"kid": "7f3ce...b19d8",
"use": "sig",
"alg": "RS256"
}
]
}
参考になる部分があれば、ご活用下さい!
Discussion