OIDCを介してTerraform Cloudをよりセキュアに使ってみる
みなさん、こんにちは!
そろそろ新卒と名乗るのもおこがましい気がするオバカムです。
オバカムがいるチームでは、インフラの管理にTerraform Cloudを使っています。
Terraform Cloudから管理対象のリソースへのアクセスには(当然ながら)認証情報が必要になります。
昨今、永続的な認証情報はセキュリティリスクとして受け取られるようになってきているため、例えばOIDCを利用して一時認証を使うなど、永続的な認証情報を避ける方法が重要視され始めています。
というわけで今回はTerraform CloudとOIDCにまつわる小ネタをいくつか紹介します! 今回は管理リソースはAWSとGoogle Cloudにあることを前提とします。1
OIDCを利用したTerraform Cloudでのリソース管理の概略図は以下を想定します。
以下のような流れで一時的な認証情報を取得します。
- Terraform CloudのWorkspaceでPlan/Applyをする際に各クラウドのIAMサービス2にアクセスキーを要求
- 各クラウドのIAMサービスはTerraform CloudのOpenID ProviderにWorkspaceの情報を問い合わせ
- OpenID ProviderはWorkspaceの情報をIDトークンとして各クラウドのIAMサービスに提供
- 各クラウドのIAMサービスは提供されたIDトークンをもとにアクセス権限を設定し、期限付きのアクセスキーを生成し提供
OIDCを利用してTerraform Cloudからリソースに一時認証でアクセス
OIDCを介してTerraform Cloudが動的認証情報を得るにはいくつかのステップを踏む必要があります。
- 各クラウドにTerraform CloudのOIDC Providerを認識してもらう
- 各クラウドでのアクセス権限設定
- Terraform CloudのWorkspaceの環境変数設定
順を追って説明していきます。
管理クラウド側に利用するOIDC Providerを設定
まずは管理対象のクラウドに利用するOIDC Providerを認識してもらいましょう。
OIDC Providerの認識部分でどちらも抑えておくべきポイントは以下です。
- Issuer(OIDC ProviderのURL)を指定
- Terraform Cloudでは
https://app.terraform.io
- Terraform Cloudでは
- Audience(OIDC Providerが認証情報を発行する対象)の指定
- AWSでは
aws.workload.identity
- Google Cloudでは
https://iam.googleapis.com/projects/{Project ID}/locations/global/workloadIdentityPools/{Workload Identity Pool Name}/providers/{Provider Name}
- AWSでは
各クラウドでの設定を設定画面のスクリーンショットを出しながら説明します。
今回はすべてコンソールから設定します。
AWS
- コンソールから
IAM
>ID プロバイダ
と進み、プロバイダを追加
ボタンをクリック - プロバイダの各種設定を行う
プロバイダのタイプ
はOpenID Connect
を選択プロバイダのURL
にhttps://app.terraform.io
を入力サムプリントを取得
ボタンをクリック対象者
にaws.workload.identity
を入力
プロバイダを追加
ボタンをクリック
GCP
- コンソールから
IAMと管理
>Workload Identity 連携
に進み、Workload Identity Poolの構成
ボタンをクリック - ID プールの設定
名前
に好きなID プール名(例ではovercome-test)を入力続行
ボタンをクリック
- ID プールにプロバイダの設定をする
プロバイダの選択
はOpenID Connect (OIDC)
を選択プロバイダ名
に好きなプロバイダ名(例ではovercome-test)を入力発行元 (URL)
にhttps://app.terraform/io
を入力オーディエンス
はデフォルトのオーディエンス
でOK- Terraform Cloud側でAudienceの値を変更する場合は
許可するオーディエンス
を選択して値を入力
- Terraform Cloud側でAudienceの値を変更する場合は
続行
ボタンをクリック
- Google Cloud側が参照できるOIDCの属性と認証条件を設定
- Google Cloud側で参照したい情報をマッピング
- マッピングは
Google ${n}
とOIDC ${n}
が対応Google ${n}
にはGoogle Cloud側での参照名を入力OIDC ${n}
にはOpenIDの属性情報のキーを入力
google.subject
にassertion.sub
をマッピング- このマッピングは必須
attribute.terraform_workspace_name
にassertion.terraform_workspace_name
をマッピング- Google Cloud側の参照名は
attribute.workspace_name
とかでもよい
- Google Cloud側の参照名は
- そのほかの属性についてはTerraform CloudのOpenIDの構成情報の
claims_supported
を参照
- マッピングは
- Google Cloudが認証を通すための条件を設定
- この条件がないとTerraform Cloudの全ワークスペースから認証が通ってしまうので設定必須
- ここでは以下のように設定
Organization
がovercome-organization
の時のみ、
かつProject
がovercome-test
の時のみ
google.subject.startsWith('organization:overcome-organization:project:overcome-test:')
- Google Cloud側で参照したい情報をマッピング
保存
ボタンをクリック
より詳しい設定方法は各クラウドのドキュメントを見ていただければと思います。
OpenID Connect プロバイダーの作成(AWS)
Workload Identity 連携を構成する(Google Cloud)
管理クラウド側のリソースへのアクセス権限設定
次に管理リソースへのアクセス権限を設定していきます。
AWS
AWSではOIDCを利用した動的認証の権限はIAM Roleで設定します。
IAM Roleはコンソールから設定が可能ですが、デフォルトの設定ではTerraform Cloudに存在する全ワークスペースがこのアカウントにアクセスできてしまうので、カスタムポリシーを自分で作ることにします。
信頼ポリシーには以下のJSONを設定します。
なお、以下のプレイスホルダーにはそれぞれ利用するAWSアカウント/Terraform Cloudワークスペースの情報を入れてください。
- AWS ACCOUNT ID
- Organization Name
- Project Name
- Projectに所属していない場合は省略可能です。
- Workspace Name
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRoleWithWebIdentity",
"Principal": {
"Federated": "arn:aws:iam::${AWS ACCOUNT ID}:oidc-provider/app.terraform.io"
},
"Condition": {
"StringEquals": {
"app.terraform.io:aud": [
"aws.workload.identity"
]
},
"StringLike": {
"app.terraform.io:sub": "organization:${Organaization Name}:project:${Project Name}:workspace:${Workspace Name}:*"
}
}
}
]
}
許可ポリシーは必要な権限を設定してあげればOKです。
OIDC特有の設定はここでは使わないので通常通りAWSマネージドポリシーの利用やカスタム許可ポリシーの設定をすれば問題ないです。
Google Cloud
Google Cloudでは動的認証の権限は実行するサービスアカウントに紐づいています。
まずはサービスアカウントとOIDCのWorkload Identityを連携させましょう。
- コンソールから
IAMと管理
>Workload Identity連携
と進み、先ほど設定したID プールの名前をクリック - 画面上部の
アクセス権を付与
をクリック - 必要な設定を入力し、
保存
ボタンをクリックサービスアカウント
には設定するサービスアカウントを選択- プリンシパルの選択にてOpenIDがサービスアカウントにアクセスできる条件を設定
- ここではプロバイダの属性マッピングの時に設定した
terraform_workspace_name
の値がoidc-test
の時だけサービスアカウントにアクセスできるようにしています
- ここではプロバイダの属性マッピングの時に設定した
無事サービスアカウントにアクセスできるようになったら、サービスアカウントに必要な権限を付与します。
Terraform Cloud側のOIDCの設定
最後にTerraform Cloudのリポジトリに一時認証情報を渡すための環境変数を設定します。
各クラウドごとに違う変数が用意されているので利用するクラウドを確認して設定します。
AWS
TFC_AWS_PROVIDER_AUTH
- AWSの認証にOIDCを利用するかどうかのBool値
- Bool (true/false)
TFC_AWS_RUN_ROLE_ARN
- 権限を借用するIAM RoleのARN
- String
Google Cloud
TFC_GCP_PROVIDER_AUTH
- GCPの認証にOIDCを利用するかどうかのBool値
TFC_GCP_RUN_SERVICE_ACCOUNT_EMAIL
- 権限を借用するService AccountのEメールアドレス
TFC_GCP_WORKLOAD_PROVIDER_NAME
- OIDCで利用するIDプールの(完全な)ID
- フォーマットは
projects/{{project}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/providers/{{workload_identity_pool_provider_id}}
- フォーマットは
- 以下3つの環境変数に分離可能
TFC_GCP_PROJECT_NUMBER
- Google Cloudのプロジェクト番号
TFC_GCP_WORKLOAD_POOL_ID
- 利用するIDプールの名前
TFC_GCP_WORKLOAD_PROVIDER_ID
- IDプールに紐づいているプロバイダの名前
- OIDCで利用するIDプールの(完全な)ID
PlanとApplyの参照権限を変える
Terraform CloudではOIDCの設定は環境変数経由になるため、一見すると複数の認証情報を一度に扱うのは難しいように見えます。
Terraform Cloudではそれを見越してか、PlanとApplyでそれぞれ別々の認証情報を渡すことができます。
- Plan
TFC_AWS_PLAN_ROLE_ARN
(AWS)TFC_GCP_PLAN_SERVICE_ACCOUNT_EMAIL
(Google Cloud)
- Apply
TFC_AWS_APPLY_ROLE_ARN
(AWS)TFC_GCP_APPLY_SERVICE_ACCOUNT_EMAIL
(Google Cloud)
なお、OIDCを利用した動的認証を使う場合には環境変数にTFC_*_RUN_*
を設定するか、TFC_*_PLAN_*
とTFC_*_APPLY_*
の両方を設定するかのどちらかが必須です。
OIDCで複数の認証情報を渡す
Terraform CloudではOIDCの設定は環境変数経由になるため、一見すると複数の認証情報を一度に扱うのは難しいように見えます。3
Terraform Cloudで同じクラウドの別々のアカウントの動的認証情報を渡すには、各クラウドのproviderモジュールとvariablesに少し細工をする必要があります。
まずは各クラウドのproviderモジュールです。
各クラウドのproviderモジュールはalias
を設定することで複数の設定プロファイルを使える……というのはTerraformを使っている皆様ならおなじみですよね。
ここで設定したalias
をキーとして細工します。
動的認証は(暗黙の)variable tfc_${provider}_dynamic_credentials
を通じてproviderへ渡されます。
このvariableをalias
をキーとしたmapとして扱えるように明示的に宣言します。
variable "tfc_${provider}_dynamic_credentials" {
type = object({
default = object({
...
})
aliases = map(object({
...
}))
})
}
各クラウドのproviderからは以下のように参照します。
provider "${cloud}" {
... = [var.tfc_${cloud}_dynamic_credentials.default....]
}
provider "aws" {
alias = "ALIAS"
... = [var.tfc_${cloud}_dynamic_credentials.aliases["ALIAS"]....]
}
各クラウドの実装例を以下に示しておきます。
- AWS
- ここではアプリとDNS(Route53)が別クラウドアカウント管理という想定での設定例です。
TFC_AWS_PROVIDER_AUTH=true TFC_AWS_RUN_ROLE_ARN=${App Account Role ARN} TFC_AWS_PROVIDER_AUTH_DNS=true TFC_AWS_RUN_ROLE_ARN_DNS=${DNS Account Role ARN}
variable "tfc_aws_dynamic_credentials" { description = "Object containing AWS dynamic credentials configuration" type = object({ default = object({ shared_config_file = string }) aliases = map(object({ shared_config_file = string })) }) }
# For Application provider "aws" { shared_config_files = [var.tfc_aws_dynamic_credentials.default.shared_config_file] } # For Route53 provider "aws" { alias = "DNS" shared_config_files = [var.tfc_aws_dynamic_credentials.aliases["DNS"].shared_config_file] }
- GCP
- ここではアプリとBigQueryが別プロジェクト管理という想定での設定例です。
TFC_GCP_PROVIDER_AUTH=true TFC_GCP_RUN_SERVICE_ACOUNT_EMAIL=${App Project Service Account Email} TFC_GCP_PROJECT_NUMBER=${App Project Number} TFC_GCP_WORKLOAD_POOL_ID=${App Project Workload Pool ID} TFC_GCP_WORKLOAD_PROVIDER_ID=${App Project Workload Provider ID} TFC_GCP_PROVIDER_AUTH_BQ=true TFC_GCP_RUN_SERVICE_ACCOUNT_EMAIL_BQ=${BQ Project Service Account Email} TFC_GCP_PROJECT_NUMBER=${BQ Project Number} TFC_GCP_WORKLOAD_POOL_ID=${BQ Project Workload Pool ID} TFC_GCP_WORKLOAD_PROVIDER_ID=${BQ Project Workload Provider ID}
variable "tfc_gcp_dynamic_credentials" { description = "Object containing GCP dynamic credentials configuration" type = object({ default = object({ credentials = string }) aliases = map(object({ credentials = string })) }) }
# For Application provider "google" { credentials = var.tfc_gcp_dynamic_credentials.default.credentials } # For BigQuery provider "google" { alias = "BQ" credentials = var.tfc_gcp_dynamic_credentials.aliases["BQ"].credentials }
まとめ
OIDCを介してTerraform Cloudに動的認証情報を渡す方法を上げてみました。
設定する部分は多いですが、やっていることは以下3つです。
- クラウド側でOIDC Providerを認識させる
- クラウド側でアクセス権限を設定する
- Terraform Cloud側で権限を借用するアカウント・ロールを設定する
それでは、よいTerraform Cloudライフを!