こんにちは、小林です。
2024年1月にFLINTERSが設立10周年を迎えるということで、記念の全社員ブログリレー10日目を担当します。
安心安全なCI/CD
多くのCI/CDツールにはセキュアな情報を保存しパイプライン内で参照するための、シークレットなどと呼ばれるような機能があると思います。
アクセスキーなど静的なクレデンシャルを保存することが多いと思いますが、可能であれば使わずに済ませたいところです。
そこでシークレットを使わずにGitHub ActionsでCloud Runアプリケーションのデプロイを行う、安心安全なCI/CDを作ってみたいと思います。
お題
- Cloud Runで動作するアプリケーションをデプロイしたい
- DBとして使用しているCloud SQLも同じCI/CDでマイグレーションしたい
- 上記をリポジトリのシークレットにセキュアな情報を設定することなく実現したい
備考
アプリケーションの実装は何で書かれていても今回の趣旨とは関係ないのですが、これから紹介するGitHub ActionsのYAMLの内容は私のチームで実際に使われているものを参考にしているため、GitHub Actionsが動作するリポジトリは
- Scala及びsbtで作成されたアプリケーション
- マイグレーションにはflywayのsbt pluginを使用
という構成になっていることとします。
その関係で趣旨とは外れたYAMLの記載も少々出てきてしまいますが、適宜補足します。
どうやるか
ざっくり以下の手段で実現します。
- OpenID Connect(OIDC)を使う
- Cloud SQL Auth Proxyを使ったIAM認証を行う
正直目新しい情報は何もなく恐縮なのですが、最低限自分の備忘録として役に立つのでヨシの精神でやっていきます。
とりあえず
チェックアウトとjdkの準備までをしておきましょう。
mainブランチにマージされたら動くやつです。
name: Deploy on: push: branches: - 'main' permissions: id-token: write contents: read jobs: staging-deploy: runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v3 - name: Setup JDK uses: actions/setup-java@v3 with: distribution: temurin java-version: 17 cache: sbt
ここで重要なのは以下になります。
permissions: id-token: write contents: read
この後登場するOpenID Connect(OIDC)を使用した認証に必要な設定となります。
サービスアカウントで認証する
resource "google_iam_workload_identity_pool" "github_actions" { workload_identity_pool_id = "github-actions" } resource "google_iam_workload_identity_pool_provider" "github_actions_oidc" { workload_identity_pool_id = google_iam_workload_identity_pool.github_actions.workload_identity_pool_id workload_identity_pool_provider_id = "github-actions-oidc" attribute_mapping = { "google.subject" = "assertion.sub" "attribute.actor" = "assertion.actor" "attribute.aud" = "assertion.aud" "attribute.repository" = "assertion.repository" } oidc { issuer_uri = "https://token.actions.githubusercontent.com" } } locals { service_account_name = "hoge@hogeproject.iam.gserviceaccount.com" repository = "hoge/hoge-backend" } resource "google_service_account_iam_member" "workload_identity_sa" { service_account_id = local.service_account_name role = "roles/iam.workloadIdentityUser" member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github_actions.name}/attribute.repository/${local.repository}" } output "github_actions_provider_name" { value = google_iam_workload_identity_pool_provider.github_actions_oidc.name }
突然ですがterraformです。
このようにWorkload Identity連携を利用することで、OIDC認証によってサービスアカウントの権限を行使できます。
上記のservice_account_name
が権限を行使したいサービスアカウント、repository
がCI/CDを実行するGitHubのリポジトリとなります。
これらが適用された前提で引き続きYAMLを見ていきます。
先程のYAMLの続きになります。
- id: auth-google name: Auth Google Cloud uses: google-github-actions/auth@v1 with: token_format: access_token workload_identity_provider: projects/111111111111/locations/global/workloadIdentityPools/github-actions/providers/github-actions-oidc service_account: hoge@hogeproject.iam.gserviceaccount.com
これだけでOIDC認証できました。
workload_identity_provider
は上記terraformのoutputに定義されているgithub_actions_provider_name
の値です。
hoge@hogeproject.iam.gserviceaccount.com
の権限に紐づくアクセストークンがGoogle Cloudの認証に使われる環境変数とstepのoutputに設定されます。
後でこのstepのoutputを使いたいので、idで参照できるようにid: auth-google
と記述しておきます。
Cloud Runをデプロイする
Cloud Runをデプロイするためのstepを見ています。 Artifact RegistoryにDocker Imageをプッシュし、それを参照したCloud Run Serviceをデプロイするという流れです。これらを実行するために必要な権限は認証方法に関わらないので割愛します。
まずArtifact Registoryを使用できるように認証します。
- name: Login docker uses: docker/login-action@v2 with: registry: asia-northeast1-docker.pkg.dev username: oauth2accesstoken password: ${{ steps.auth-google.outputs.access_token }}
先程のauth-google
stepで生成されたaccess_token
を使用してログインします。
続いてDocker Imageをプッシュするところまでです。
- name: Build run: sbt docker:stage - name: Docker build and push uses: docker/build-push-action@v4 with: push: true tags: asia-northeast1-docker.pkg.dev/hoge-project/web-app/backend:latest context: ./target/docker/stage
私のチームのプロジェクトでsbt-native-packager
のDockerPlugin
を利用している関係でstepや設定が増えていますが、要はDocker Imageをビルドしてプッシュしているだけなので、OIDCなどは特に関係ないです。
最後にCloud Runをデプロイします。
- name: Deploy cloud run run: | gcloud run deploy backend \ --region asia-northeast1 \ --image asia-northeast1-docker.pkg.dev/hoge-project/web-app/backend:latest
Cloud SDKは環境変数から認証情報を読み取ってくれるため、特に認証方法を指定しなければ先程のアクセストークンが使用されます。
Cloud SQLへ接続する
resource "google_sql_database_instance" "db" { database_version = "POSTGRES_14" region = "asia-northeast1" settings { ip_configuration { ipv4_enabled = true require_ssl = false private_network = "something-vpc" } database_flags { name = "cloudsql.iam_authentication" value = "on" } ...省略 } } locals { service_account_name = "hoge@hogeproject.iam.gserviceaccount.com" } resource "google_sql_user" "hoge_user" { name = trimsuffix(local.service_account_name, ".gserviceaccount.com") instance = google_sql_database_instance.db.name type = "CLOUD_IAM_SERVICE_ACCOUNT" } resource "google_project_iam_member" "cloud_sql_client" { project = "hoge-project" role = "roles/cloudsql.client" member = "serviceAccount:${local.service_account_name}" } output "cloud_sql_connection_name" { value = google_sql_database_instance.db.connection_name }
突然ですがterraformです。
Cloud SQLインスタンスと、そのインスタンスにサービスアカウントで接続するための設定を行なっています。
Cloud SQL for PostgreSQLを使っていますが、MySQLでも似たようなものだと思います。
ポイントは以下です。
ipv4_enabled = true
にすることでインターネット上からアクセスできるようにするsettings.ip_configuration.authorized_networks
を設定しない限り、外部からはCloud SQL Auth Proxyを使用しないと接続できません- これが
false
だと結局踏み台サーバーなどが必要になってしまいます
database_flags
にname = "cloudsql.iam_authentication"
,value = "on"
を設定してIAM認証を有効化する- DBユーザーとしてサービスアカウントを登録する
hoge@hogeproject.iam
というユーザーを作成します
- サービスアカウントに
roles/cloudsql.client
ロールの権限を付与
ちなみにrequire_ssl = false
にしていますが、Cloud SQL Auth Proxyはここの設定とは関係なく常に暗号化通信を行います。
マイグレーション対象のスキーマ, テーブルへの権限付与も必要です。
以下のようなSQLを実行して権限を付与しておきます。
SET SEARCH_PATH = app; GRANT USAGE ON SCHEMA app TO "hoge@hogeproject.iam"; GRANT ALL ON ALL TABLES IN SCHEMA app TO "hoge@hogeproject.iam";
これらが適用された前提でYAMLに戻ります。
まずCloud SQL Auth Proxy
をダウンロードし、実行します。
- name: Start Cloud SQL Proxy run: | curl "https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.6.1/cloud-sql-proxy.linux.amd64" -o cloud-sql-proxy chmod +x cloud-sql-proxy ./cloud-sql-proxy hoge-project:asia-northeast1:some-instance --auto-iam-authn &
hoge-project:asia-northeast1:some-instance
はterraformのoutputに定義されているcloud_sql_connection_name
で取得できるものです。
--auto-iam-authn
オプションを指定することでIAM認証で実行します。正確に確認できていませんが、だいたいCloud SDKと同じような仕様で認証情報を読み取っていると思います。(参考)
また、バックグラウンドでコマンドが実行されるようにしておきます。
最後にマイグレーションです。
- name: Migrate Cloud SQL env: DB_USER: hoge@hogeproject.iam run: sbt flywayMigrate
利用するマイグレーションツールによって接続の設定方法はいろいろあると思いますが、認証に関わるところで言うと、userはサービスアカウトに紐づくもの(上記terrformで定義したgoogle_sql_user.hoge_user
)を指定、passwordは使われないのでなんでもOKです。
全体
繋げただけですがこんな感じです。 実際の運用ではマイグレーション -> Cloud Runデプロイという順序なので、解説した順番と入れ替えています。
name: Deploy on: push: branches: - 'main' permissions: id-token: write contents: read jobs: staging-deploy: runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v3 - name: Setup JDK uses: actions/setup-java@v3 with: distribution: temurin java-version: 17 cache: sbt - id: auth-google name: Auth Google Cloud uses: google-github-actions/auth@v1 with: token_format: access_token workload_identity_provider: projects/111111111111/locations/global/workloadIdentityPools/github-actions/providers/github-actions-oidc service_account: hoge@hogeproject.iam.gserviceaccount.com - name: Start Cloud SQL Proxy run: | curl "https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.6.1/cloud-sql-proxy.linux.amd64" -o cloud-sql-proxy chmod +x cloud-sql-proxy ./cloud-sql-proxy hoge-project:asia-northeast1:some-instance --auto-iam-authn & - name: Migrate Cloud SQL env: DB_USER: hoge@hogeproject.iam run: sbt flywayMigrate - name: Login docker uses: docker/login-action@v2 with: registry: asia-northeast1-docker.pkg.dev username: oauth2accesstoken password: ${{ steps.auth-google.outputs.access_token }} - name: Deploy cloud run run: | gcloud run deploy backend \ --region asia-northeast1 \ --image asia-northeast1-docker.pkg.dev/hoge-project/web-app/backend:latest
まとめ
GitHub ActionsでOIDCを使うだけだとあまりにもありふれているので、Cloud SQLをくっつけてなんとかしようとしていたかもしれません。精進します。
とにかく、静的なクレデンシャルは漏洩時のリスクが怖いというのはもちろん、リスクを軽減するために定期的なローテーションやメンバーの異動なんかで差し替えたりする必要があるので、面倒ごとを減らすという意味でもできるだけ無くしていきたいところです。
参考
Google Cloud Platform での OpenID Connect の構成 - GitHub Docs
Workload Identity 連携 | IAM のドキュメント | Google Cloud
Cloud SQL Auth Proxy を使用して接続する | Cloud SQL for PostgreSQL | Google Cloud