GitHub Actions OIDC で Cloudflare Workers にシークレットレスデプロイ

Cloudflare API Token を GitHub Secrets に置いている運用を、OIDC 連携に置き換えるパターン。短命の access token を毎回発行する Workflow を、wrangler-action と組み合わせて最小構成で組み立てます。

CLOUDFLARE_API_TOKEN を GitHub の Repository Secrets に貼って、wrangler deploy を CI から叩く — Cloudflare Workers の標準的なデプロイ構成です。これ自体は機能しますが、実運用で気になる点が 3 つあります。

  • API Token の有効期限を切らないと、漏れたら無期限に使われ続ける
  • 切ると今度は定期的にローテーションする運用が発生する
  • どの Workflow がいつ Token を使ったかの監査が、Cloudflare 側のログでしか追えない

GitHub Actions の OIDC (OpenID Connect) 連携を使うと、CI 実行ごとに「そのジョブだけに発行される短命 access token」を Cloudflare 側で発行できるので、長寿命 Secret を持たずに済みます。本記事では、Workers のデプロイを OIDC 化する最小構成をまとめます。

何が嬉しいのか — シークレットレスの実利

OIDC 連携は地味に効くポイントが揃っています。

  • GitHub Secrets に Cloudflare の長寿命 Token を置かない。漏洩リスクが「ジョブ実行中の数分」に限定される
  • Token の発行先は Workflow の identity に紐づく。「main ブランチの push からのみ」「特定 environment に対してのみ」など、claim 単位で絞れる
  • Cloudflare 側の Audit Logs に GitHub の Workflow run ID が残る。誰のどの PR のデプロイかが事後追跡可能

「個人開発で Secret を 1 つ持つだけ」というケースでも、ローテーション忘れ問題が消えるだけで導入価値があります。

全体構成

GitHub Actions ─(OIDC token)─▶ Cloudflare API


                          短命 access token を発行


                            wrangler deploy

actions/configure-aws-credentials 的な「OIDC を直接 STS に投げる」パターンと同じ発想です。Cloudflare 側は Cloud Connectors / Account API Tokens の OIDC trust を信頼ポリシーとして登録します。

1. Cloudflare 側で OIDC trust を作る

Cloudflare Dashboard の「Manage Account → API Tokens → Create Token」から、OIDC Federation 用のテンプレートを使います (2026-04 時点で GA)。要点:

  • Issuer: https://token.actions.githubusercontent.com
  • Audience: cloudflare (任意の文字列で OK、Workflow 側と一致させる)
  • Subject claim で絞り込み: repo:<owner>/<repo>:ref:refs/heads/main のように、ブランチ単位 / environment 単位で絞る
  • Permissions: Account → Workers Scripts → Edit / Zone → DNS → Edit など、必要な最小権限のみ

設定が終わると、account_idrole_arn 相当の oidc_role_id が払い出されます。これを Workflow に渡します。

2. Workflow を書く

Workflow 全体です。permissions: id-token: write を付け忘れると OIDC token が発行されないので、ここを最初に確認。

# .github/workflows/deploy.yml
name: deploy

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  id-token: write   # OIDC token 発行に必須
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-node@v5
        with:
          node-version: "22"

      - run: npm ci

      # OIDC token を取得 → Cloudflare の短命 access token に交換
      - name: Exchange OIDC for Cloudflare token
        id: cf
        uses: cloudflare/oidc-auth-action@v1
        with:
          account_id: ${{ vars.CF_ACCOUNT_ID }}
          oidc_role_id: ${{ vars.CF_OIDC_ROLE_ID }}
          audience: cloudflare

      # 払い出された短命トークンを wrangler に渡す
      - name: Deploy
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ steps.cf.outputs.api_token }}
          accountId: ${{ vars.CF_ACCOUNT_ID }}
          command: deploy

vars.*Repository Variables (Secrets ではなく非機密な設定値)。account_idoidc_role_id はリーク しても単独では悪用されないので、Variables に置いて構いません。

3. ブランチ・環境で claim を絞る

OIDC の旨味は Subject claim を絞れるところです。Cloudflare 側の trust policy で次のような縛りを入れておきます。

縛り例Subject claim
main ブランチからの push のみrepo:r43lab/site:ref:refs/heads/main
production environment のジョブのみrepo:r43lab/site:environment:production
Pull Request からは拒否pull_request を含む subject を許可しない

environment: production を Workflow に書き、Cloudflare 側で environment:production claim を要求すると、PR からは production にデプロイできない という制約が、Cloudflare 側でも担保されます。GitHub の environment protection rule と二重に効くので、誤デプロイがほぼ起きません。

4. Preview 用にもう 1 つ trust を作る

「main は production、PR は preview」とよくあるので、preview 用にもう 1 つ trust を発行しておきます。

  • Subject: repo:<owner>/<repo>:pull_request
  • Permissions: Workers Scripts Edit + Preview Deployments のみ
  • Account: 同じ

PR 側の Workflow:

on:
  pull_request:

permissions:
  id-token: write
  contents: read
  pull-requests: write

jobs:
  preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v5
        with: { node-version: "22" }
      - run: npm ci
      - id: cf
        uses: cloudflare/oidc-auth-action@v1
        with:
          account_id: ${{ vars.CF_ACCOUNT_ID }}
          oidc_role_id: ${{ vars.CF_OIDC_ROLE_ID_PREVIEW }}
          audience: cloudflare
      - uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ steps.cf.outputs.api_token }}
          accountId: ${{ vars.CF_ACCOUNT_ID }}
          command: versions upload

versions upload で preview deployment が作られ、wrangler-action が PR コメントに preview URL を貼ってくれます。本番権限を持たない trust から発行しているので、PR の Workflow が暴走しても production が書き換わらないのがポイントです。

落とし穴 / ハマりポイント

  • permissions: id-token: write を付け忘れる。OIDC token が発行されず、Could not fetch OIDC token で落ちる。最初に踏むやつ
  • Subject claim を絞らずに trust を作ると、別リポジトリの Workflow からも token 発行可能になる。最低 repo:<owner>/<repo>:* レベルの絞りは必須
  • audience のミスマッチ。Cloudflare trust 側と Workflow 側で audience を同じ文字列にしないと交換に失敗する
  • environment: をジョブに書かないと environment claim が乗らない。environment 縛りで絞っているのに「environment claim が無い」で落ちることがある
  • vars.CF_ACCOUNT_ID を Secrets に入れるか Variables に入れるか。漏れても OIDC trust が無ければ何もできないので Variables で OK だが、組織ポリシーで決まっているならそれに従う

ざっくり比較: 旧来の長寿命 Token との差

API Token in SecretsOIDC 連携
漏洩時の影響時間失効までずっとジョブ実行中の数分
ローテーション手動運用必要不要
ブランチ単位の絞り不可claim で可能
Audit logCloudflare 側のみGitHub run ID 紐付き
初期設定の手間Secret 1 個コピペtrust policy 1 個作成

初期設定の手間は数分多いだけで、運用フェーズに入ったあとの楽さが段違いです。

まとめ

GitHub Actions の OIDC を Cloudflare の短命 token に交換する構成は、CLOUDFLARE_API_TOKEN を貼っておく旧来構成の上位互換です。

  • Secret ローテーションが消える
  • ブランチ・環境で claim を絞れる
  • Audit log に Workflow run ID が残る

個人開発でも、main / preview の trust を 2 つ作っておくだけで「PR の Workflow が production を壊す事故」が構造的に起きなくなります。Workers / Pages / R2 / D1 すべて同じ仕組みで連携できるので、Cloudflare に乗っているプロダクトは早めに乗り換える価値があります。

参考