🤖

GitHub Actions から OIDC を使って Google Workspace のドメイン内にのみ共有しているデータにアクセスする

2022/06/03に公開

Google Workspace の組織内限定で共有しているスプレッドシートなどのデータに定期的にアクセスして集計スクリプトを回したいときってありますよね。 GAS でやるっていう手もありますが、好きな言語を使えることや、コード管理・デプロイ周りの簡潔さを考えると GitHub Actions に軍配が上がると思います。

GitHub OIDC (OpenID Connect) を使うとGitHub Actions から、サービスアカウントを使ってGoogleのリソースにセキュアにアクセスすることができるようになります。リポジトリに認証情報を持たせる必要もなくなりますし、キーのローテーションも不要になります。

今回この仕組みを使って Google Workspace のドメイン内限定で共有しているドライブ内のファイルやスプレッドシートなどのデータにアクセスする方法をまとめます。

認証には Google が公式で提供している Actions を使用します。

https://github.com/google-github-actions/auth

Google Workspace の組織内限定のリソースにアクセスするためには、組織内の特定ユーザーになりすましてAPIにアクセスすることのできるアクセストークンを発行する必要があります。

google-github-actions/auth のセットアップ

README の Setting up Workload Identity Federation のセクション の通りに進めていきます。

上記ドキュメントの手順にない作業として、Google Workspace の特定ユーザーのなりすましアクセストークンを発行するためには以下のように roles/iam.serviceAccountTokenCreator ロールを付与する必要があります。

gcloud iam service-accounts add-iam-policy-binding "my-service-account@${PROJECT_ID}.iam.gserviceaccount.com" \
  --project="${PROJECT_ID}" \
  --role="roles/iam.serviceAccountTokenCreator" \
  --member="principalSet://iam.googleapis.com/${WORKLOAD_IDENTITY_POOL_ID}/attribute.repository/${REPO}"

Google Workspace の Domain-Wide Delegation を設定

この作業は Google Workspace の管理者でないと実行することができません。管理者でない場合は権限のある方にお願いしましょう。

Google Admin にアクセスし、セキュリティ>アクセスとデータ管理>APIの制御の項目を開きます。画面下の方にある「ドメイン全体の委任」を開いて、「新しく追加」をクリックします。

先ほど作ったサービスアカウントのクライアントIDを入れます。クライアントIDは Google Cloud Console でもみれますが、以下のコマンドでも確認できます。

gcloud iam service-accounts describe "my-service-account@${PROJECT_ID}.iam.gserviceaccount.com" \
  --format "value(uniqueId)"

OAuth スコープには、アクセスしたいAPIに必要なスコープを追加します。
たとえばスプレッドシートへの読み取り権限であれば以下になります。詳細はこちら

https://www.googleapis.com/auth/spreadsheets.readonly

Google Workspace のユーザーになりすましたアクセストークンを発行する

Generating OAuth 2.0 access tokens の項目を参考に GitHub Actions の yaml を設定していきます。

  • workload_identity_provider はドキュメント通りに作られたプロバイダーのリソース名をセットします。
  • service_account はサービスアカウントのメールアドレスをセットします。
  • token_formataccess_token に設定することでアクセストークンが発行されるようになります。
  • access_token_scopes は API が必要なスコープをセットします。先ほど Google Admin で設定したものと同じスコープを入れるとよいでしょう。
  • access_token_subject には、Google Workspace にいる実際のユーザーのメールアドレスを入れます。発行されたアクセストークンは、このユーザーからのアクセスであると Google の API は認識します。スプレッドシートであれば、このユーザーのアクセス権限があるシートを見ることができます。
  • access_token_lifetime にはアクセストークンが有効な秒数をセットします。最大1時間( 3600s )です。
jobs:
  sync:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 16
          cache: npm
      - run: npm ci
      - id: auth
        uses: google-github-actions/auth@v0
        with:
          workload_identity_provider: projects/00000000000/locations/global/workloadIdentityPools/xxxxxxx/providers/xxxxxxx
          service_account: my-service-account@my-project-id.iam.gserviceaccount.com
          token_format: access_token
          access_token_scopes: https://www.googleapis.com/auth/spreadsheets.readonly
          access_token_subject: user@example.com
          access_token_lifetime: 1800s
      - run: node index.mjs
        env:
          SHEET_ID: xxxxxxxxxx
          SHEET_TITLE: xxxxxxxxxx
          GOOGLE_ACCESS_TOKEN: ${{ steps.auth.outputs.access_token }}

その後のステップで、実際に API にアクセスするスクリプトを実行します。Actions の yaml 内で ${{ steps.auth.outputs.access_token }} で生成されたアクセストークンを環境変数として渡しています。
今回は Node.js で google-spreadsheet というスプレッドシート用のラッパーライブラリを使ってみました。

index.mjs
import { GoogleSpreadsheet } from "google-spreadsheet";

const SHEET_ID = process.env.SHEET_ID;
const SHEET_TITLE = process.env.SHEET_TITLE;
const GOOGLE_ACCESS_TOKEN = process.env.GOOGLE_ACCESS_TOKEN;

const doc = new GoogleSpreadsheet(SHEET_ID);
doc.useRawAccessToken(GOOGLE_ACCESS_TOKEN);
await doc.loadInfo();

const sheet = doc.sheetsByTitle[SHEET_TITLE];
const rows = await sheet.getRows();
console.info("> Spreadsheet loaded");

// Spreadsheet:
// | name | value |
// | ---- | ----- |
// | foo  | bar   |
rows.forEach((row) => {
  if (row.rowIndex === 1) return;
  console.info(`${row.name}: ${row.value}`); // "foo: bar"
});

これを GitHub Actions の schedule イベントで動作させれば、定期的にスプレッドシートのデータにアクセスして集計処理などを行うことができるようになります。

on:
  schedule:
    - cron: '0 15,3 * * *' # 毎日 JST 12:00, 0:00 に実行

Discussion