13
4

More than 1 year has passed since last update.

【GithubActions】サービスアカウントキーなしでactionsからGCPリソースを扱う via Terraform

Last updated at Posted at 2021-12-26

概要

これまで(数ヶ月前まで)は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なしで記述しています。

  1. Workload Identity連携を使用するために必要なAPIを有効化
  2. リソース操作を実施するサービスアカウントを作成
  3. WorkloadIdentityPoolを作成
  4. WorkloadIdentityPoolプロバイダを作成
    1. 属性マッピングについてはこちらも参照
    2. Option: ここでは特定オーナーのみ受け入れるよう属性条件を設定
  5. WorkloadIdentityPoolに2のアカウントへのアクセス(権限の借用)を許可する設定
    1. Option: 特定リポジトリの場合にのみ許可するよう設定
main.tf
# 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権限を追加します。(デフォルトだと発行できない)
詳しくはこちらを参照ください。

ci.yaml
+ 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を実行するとどうなるでしょうか。

main.tf
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."}

サービスアカウントへのアクセス条件を満たしていないと

こちらも↓のように絶対に条件をみたさないような設定をして実行してみます。

main.tf
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"
   }
 }

reference

13
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
4