オールアバウトTech Blog

株式会社オールアバウトのエンジニアブログです。

CircleCi で OIDC(OpenID Connect) がサポートされたので Google Cloud で試してみた

こんにちは。オールアバウト SRE 所属 の@s_ishiiと申します。

先日 CircleCi で OIDC(OpenID Connect)がサポートされました。
Github Actions で OIDC がサポートされてからこの日が来るのを待ち望んでいたので早速オールアバウトの CI で利用してみました。

https://twitter.com/CircleCIJapan/status/1507524861396422657?cxt=HHwWgsDU2fn35uspAAAA

OIDC があると何が良いのか?

「そもそも OIDC って何が良いの?」という方のために OIDC 登場以前と以後の世界について簡単にご説明します。

従来 Google Cloud 等のクラウドAPI やリソース に CircleCi からアクセスするには、クラウド側でサービスアカウントキーを発行し、CircleCi の環境変数として登録する必要がありました。

この方法の問題点はサービスアカウントキーが本質的にセキュアでないという点です。
サービスアカウントキーは明示的に削除しない限り最長 10 年間認証可能なのでキーの漏洩リスクが常に存在します。

OIDC を利用することでサービスアカウントキーなしで認証を通すことができるためよりセキュアな運用が可能になります。

OIDC に関する説明は Github の以下ページでよくまとめられているので関心のある方は一読されることをおすすめします。 https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect

Understanding the OIDC token 以降は Github Actions 固有の話しです

Google Cloud で CircleCi OIDC を利用する手順

ここからは具体的な設定手順について解説していきます。

Google Cloud で CircleCi OIDC を利用するには以下 2 点が必要になります。

  • Workload Identity Pool の作成(Google Cloud)
  • アクセストークン取得用のコードの追加(CircleCi)

Workload Identity Pool の作成(Google Cloud)

基本的な手順は Github の以下ページで詳細に説明されています。 https://github.com/google-github-actions/auth#setting-up-workload-identity-federation

オールアバウトでは Google Cloud のインフラ群は Terraform で管理しているので以下のようなコードで設定しました。

locals {
    circleci_organization_id = "<CircleCi Organization ID>"
}

resource "google_iam_workload_identity_pool" "circleci" {
  provider                  = google-beta
  workload_identity_pool_id = "circleci-pool"
}

resource "google_iam_workload_identity_pool_provider" "circleci" {
  provider                           = google-beta
  workload_identity_pool_id          = google_iam_workload_identity_pool.circleci.workload_identity_pool_id
  workload_identity_pool_provider_id = "circleci-prvdr"

    attribute_mapping = {
        "google.subject"         = "assertion.sub"
        "attribute.actor"        = "assertion.actor"
        "attribute.repository"   = "assertion.repository"
    }

    oidc {
    allowed_audiences = [local.circleci_organization_id]
    issuer_uri        = "https://oidc.circleci.com/org/${local.circleci_organization_id}"
  }
}

module "circleci_sa" {
  source  = "terraform-google-modules/service-accounts/google"
  version = "~> 4.1.1"

  display_name = "CircleCi Service Account"
  project_id   = var.project_id
  prefix       = "sa-${var.env_code}"
  names        = ["circleci"]

  project_roles = [
    "${var.project_id}=>roles/artifactregistry.writer",
  ]
}

resource "google_service_account_iam_member" "circleci_sa" {
  service_account_id = module.circleci_sa.service_accounts[0].id
  role               = "roles/iam.workloadIdentityUser"
  member             = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.circleci.name}/*"
}

やっていることをまとめると以下 3 点になります。

  • Workload Identity Pool の作成
  • Workload Identity Provider の作成
  • ID 連携用のサービスアカウントの作成

基本的には 上記で紹介した Github のページで説明されていることを Terraform で書いただけですが、audience として CircleCi のOrganization IDを追加で設定しています。

何故 audience の設定を追加しているのかというと、Github Actions と CircleCi で発行する OIDC Token の中身が違うためです。
具体的には CircleCi の OIDC Token には aud 値として CircleCi の Organization ID が入るため Workload Identity Provider 側でこの値を許可するよう設定する必要があります。

なお、CircleCi の Organization ID は管理画面の Organization Settings からご確認頂けます。

アクセストークン取得用のコードの追加(CircleCi)

Google Cloud の設定が完了したら次は CircleCi 側のコードを変更します。

CircleCi のドキュメントを読むと今回のリリースで CircleCi の各 Job で CIRCLE_OIDC_TOKEN という環境変数が利用可能になっており、この変数に OIDC Token が格納されています。
https://circleci.com/docs/ja/2.0/openid-connect-tokens/

この OIDC Token を最終的にサービスアカウントのアクセストークンと交換するようコードを書いてやる必要があり、具体的には以下のようなコードになります。

- run:
    name: Google Authentication
    command: |
        ACCESS_TOKEN=`curl -X POST -s \
            -H "Content-Type: application/json; charset=utf-8" \
            -d "{
                "audience": \"//iam.googleapis.com/projects/<< parameters.project_number >>/locations/global/workloadIdentityPools/circleci-pool/providers/circleci-prvdr\",
                "grantType": \"urn:ietf:params:oauth:grant-type:token-exchange\",
                "requestedTokenType": \"urn:ietf:params:oauth:token-type:access_token\",
                "scope": \"https://www.googleapis.com/auth/cloud-platform\",
                "subjectTokenType": \"urn:ietf:params:oauth:token-type:jwt\",
                "subjectToken": \"${CIRCLE_OIDC_TOKEN}\"
            }" \
            "https://sts.googleapis.com/v1/token" | jq -r .access_token`
        SA_ACCESS_TOKEN=`curl -X POST -s \
            -H "Authorization: Bearer $ACCESS_TOKEN" \
            -H "Content-Type: application/json; charset=utf-8" \
            -d "{
                "scope": [
                \"https://www.googleapis.com/auth/cloud-platform\"
                ]
            }" \
            "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/<< parameters.sa_email >>:generateAccessToken" | jq -r .accessToken`
        echo $SA_ACCESS_TOKEN | docker login -u oauth2accesstoken --password-stdin https://asia-east1-docker.pkg.dev

<< parameters.sa_email >> には Google Cloud 作成したサービスアカウントのメールアドレスが入ります

詳細な説明は Google Cloud の以下公式ページをご確認頂くとして、簡単に説明すると 1 回目のリクエストで Workload Identity の認証を通し、2 回目で事前に設定したサービスアカウトを一時的に借用するためのリクエストを送信しています。

https://cloud.google.com/iam/docs/access-resources-oidc?hl=ja#exchange-token

動作確認

上記コードを動かすと以下のようになります。 (実際にはこの後続の step で Docker イメージを build して Artifact Registry に Push しています)

f:id:allabout-techblog:20220329210816p:plain

また、Google Cloud 側ではサービスアカウントが借用されたログを確認できます。 (事前に監査ログを有効化する必要があります)

f:id:allabout-techblog:20220329210851p:plain

まとめ

CircleCi の OIDC はサポートしたばかりのため Orb がまだ作られておらず、トークン取得周りのコードを自分で書く必要があります。
これはなかなか大変ですが、一度実装してしまえばサービスアカウントキーの管理から開放されるのでメリットは非常に大きいです。

今回は Google Cloud での実装例の紹介でしたが、もちろん AWS 等の他のクラウドでも利用できるので CircleCi を利用されている方は利用を検討することをおすすめします。

以上、「CircleCi  で OIDC(OpenID Connect) がサポートされたので Google Cloud で試してみた」でした。