概要
これまで(数ヶ月前まで)はGCP外のサービスからGCPリソースを操作するためには、
操作を行うサービスアカウントを作成->サービスアカウントキーを発行し、このキーをサービス側になんらかの形式で渡すことが必要でした。
しかし現在では、GithubactionsがワークフローにOIDC機構を導入したため、Workload Identity 連携を用いることでサービスアカウントキーの発行なくクラウドリソースへのアクセスが可能となっています。本稿ではその導入についてを記載します。
※N番煎じですが、Poolの準備にterraformを使っているものはなかったので記載しました。
※本稿ではOIDCの仕組みについては記載していません。
効用
- サービスアカウントキーの管理コスト(定期ローテーション等)がなくなる
- 漏洩リスクの低下
- 仮に漏洩したとしても、ワークフローを実行するたびに短い時間(N分~N時間の単位)有効なアクセスキーを発行することになるため、攻撃可能な時間が小さくなる
- WorkloadIdentityPoolの利用ログをみることが可能
やっていく
すでにGithubActionsでGCPリソース操作をしている状態からの改修を想定した記載となっています。
これから導入する場合はdiffのマイナス部分を無視してください。
before/afterのイメージ
おそらくsetup-gcloudを利用し、シークレットに格納しているサービスアカウントキーを渡すことで認証を行っていたかと思います。
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v0
with:
service_account_key: ${{ secrets.GCP_SA_KEY }}
export_default_credentials: true
これをauth actionに変更し、WorkloadIdentityPoolを通じてサービスアカウントへ接続することで、サービスアカウントキーを発行・管理することがなくなります。
- name: Authenticate google cloud
uses: google-github-actions/auth@v0
with:
workload_identity_provider: ${WORKLOAD_IDENTITYPOOL_ID}
service_account: ${SA_NAME}
WorkloadIdentityPoolの準備
gcloudコマンドでの設定方法はこちらに記載されていますので、ここではterraformで準備をします。
なおterraformに関してもmoduleが存在しますのでこれを利用するのが良いでしょう。ここでは説明のためにmoduleなしで記述しています。
- Workload Identity連携を使用するために必要なAPIを有効化
- リソース操作を実施するサービスアカウントを作成
- WorkloadIdentityPoolを作成
- WorkloadIdentityPoolプロバイダを作成
- 属性マッピングについてはこちらも参照
- Option: ここでは特定オーナーのみ受け入れるよう属性条件を設定
- WorkloadIdentityPoolに2のアカウントへのアクセス(権限の借用)を許可する設定
- Option: 特定リポジトリの場合にのみ許可するよう設定
# 1
resource "google_project_service" "this" {
for_each = [
"iam.googleapis.com", # Identity and Access Management (IAM) API
"iamcredentials.googleapis.com", # IAM Service Account Credentials API
"cloudresourcemanager.googleapis.com", # Cloud Resource Manager API
"sts.googleapis.com", # Security Token Service API
]
project = data.google_project.this.project_id
service = each.value
}
# 2
# サービスアカウントに付与する権限は任意に設定
resource "google_service_account" "github_actions" {
project = data.google_project.this.project_id
account_id = "github-actions"
display_name = "github actions"
description = "link to Workload Identity Pool used by github actions"
}
# 3
resource "google_iam_workload_identity_pool" "github" {
provider = google-beta
project = data.google_project.this.project_id
workload_identity_pool_id = "github"
display_name = "github"
description = "for github actions"
}
# 4
resource "google_iam_workload_identity_pool_provider" "github" {
provider = google-beta
project = data.google_project.this.project_id
workload_identity_pool_id = google_iam_workload_identity_pool.github.workload_identity_pool_id
workload_identity_pool_provider_id = "github-provider"
display_name = "github actions provider"
description = "OIDC identity pool provider for execute github actions"
# See. https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.repository" = "assertion.repository"
"attribute.owner" = "assertion.repository_owner"
"attribute.refs" = "assertion.ref"
}
# Option: 特定のリポジトリオーナーのみトークン交換を許可
attribute_condition = "attribute.owner=={OWNER_NAME}"
oidc {
issuer_uri = "https://token.actions.githubusercontent.com"
}
}
# 5
resource "google_service_account_iam_member" "github_actions" {
service_account_id = google_service_account.github_actions.name
role = "roles/iam.workloadIdentityUser"
member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github.name}/attribute.repository/${REPO_NAME}"
}
# あとで使うID
output "service_account_github_actions_email" {
description = "Actionsで使用するサービスアカウント"
value = google_service_account.github_actions.email
}
output "google_iam_workload_identity_pool_provider_github_name" {
description = "Workload Identity Pood Provider ID"
value = google_iam_workload_identity_pool_provider.github.name
}
ローカルからapplyしましょう。
PoolとProviderは、プロジェクト作成時に作っておくのがよいかもしれません。
actionsの設定
setup-gcloudからauthを使うように変更します、actionsの内容によってはこれ以外の権限設定が必要となるケースもあります。
また、GITHUB_TOKENにid-tokenのwrite権限を追加します。(デフォルトだと発行できない)
詳しくはこちらを参照ください。
+ permissions:
+ id-token: write
----
+ - name: Authenticate google cloud
+ uses: google-github-actions/auth@v0
+ with:
+ workload_identity_provider: ${output.google_iam_workload_identity_pool_provider_github_name}
+ service_account: ${output.service_account_github_actions_email}
# ※gcloudコマンドを使う場合はwithを消すだけでよい
- - name: Set up Cloud SDK
- uses: google-github-actions/setup-gcloud@v0
- with:
- service_account_key: ${{ secrets.GCP_SA_KEY }}
- export_default_credentials: true
- name: Setup terraform
uses: hashicorp/setup-terraform@v1.3.2
with:
terraform_version: v1.1.0
- name: Terraform init
run: terraform init
...
これで設定完了です!
キーの削除
シークレットに格納しているサービスアカウントキーと、GCPからCI用に発行したサービスアカウントキーを削除します。
Tips
プロバイダの属性条件に反すると
↓のように絶対に属性条件にマッチしないように設定してactionsを実行するとどうなるでしょうか。
resource "google_iam_workload_identity_pool_provider" "github" {
# attribute_condition = "attribute.owner=={OWNER_NAME}"
attribute_condition = "attribute.owner==\'whoisthat\'"
}
結果
Authenticate google cloud
ステップは成功し、terraform init
で実行に失敗しました。
エラーメッセージを見ても、属性条件に反しているため認証できなかったということがわかります。
自分はstate fileの管理をgcsにしていたためここで失敗したと思われ、Terraform Cloud
など別箇所でstate管理している場合はおそらくplan
/apply
でエラーになると思われます。
"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/github-actions@zzzzzz.iam.gserviceaccount.com:generateAccessToken": oauth2/google:
status code 400: {"error":"unauthorized_client","error_description":"The given credential is rejected by the attribute condition."}
サービスアカウントへのアクセス条件を満たしていないと
こちらも↓のように絶対に条件をみたさないような設定をして実行してみます。
resource "google_service_account_iam_member" "github_actions" {
service_account_id = google_service_account.github_actions.name
role = "roles/iam.workloadIdentityUser"
# member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github.name}/attribute.repository/${REPO_NAME}"
member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github.name}/attribute.repository/whatisthis"
}
結果
こちらもAuthenticate google cloud
ステップは成功し、後続のterraform init
で実行に失敗しました。
エラーメッセージを見る限り権限不足と出ており、権限の借用ができていないようにみえます。
Error: Failed to get existing workspaces: querying Cloud Storage failed: Get "https://storage.googleapis.com/storage/v1/b/mintak-tfstate/o?alt=json&delimiter=%2F&pageToken=&prefix=terraform%2F&prettyPrint=false&projection=full&versions=false": oauth2/google: status code 403: {
"error": {
"code": 403,
"message": "The caller does not have permission",
"status": "PERMISSION_DENIED"
}
}