1. はじめに

GitHub Actions から Azure にデプロイする際、これまでは az ad sp create-for-rbac --sdk-auth で生成した長寿命の JSON シークレットを AZURE_CREDENTIALS として GitHub Secrets に保存する方法が広く使われてきました。しかし Azure CLI のリファレンス上でもこの --sdk-auth--json-auth のエイリアス)は 非推奨(Deprecated) と明記されており、代わりに OIDC (OpenID Connect) による Workload Identity Federation への移行が公式に推奨されています。

本記事では、GitHub Actions の id-token: write 権限と Microsoft Entra ID の federated credential を組み合わせ、長寿命シークレットを一切保存せずに Azure へログインする 手順を、実際のセットアップ手順に沿って一般化して解説します。GitHub Actions のワークフロー設計そのものについては Reusable Workflows 実践ガイド でも扱っているので、あわせて参照してください。

2. OIDC 認証の仕組み

2.1 なぜ長寿命シークレットをやめるのか

GitHub Secrets に保存した長寿命のクラウド認証情報は、漏洩・失効忘れ・ローテーション運用の負荷といったリスクを常に抱えます。Microsoft Entra ID の公式ドキュメントも「ワークロード ID フェデレーションは、資格情報の手動管理負荷とシークレット漏洩・証明書失効リスクの両方を排除する」と説明しており、OIDC 方式へ切り替えることで次のメリットが得られます[^2]。

  • クラウド側にシークレットを複製する必要がない
  • クラウド側の IAM(Entra ID / Azure RBAC)でアクセスを一元管理できる
  • トークンが自動的に短命失効するため、ローテーション運用が不要になる

2.2 OIDC トークン交換の流れ

permissions: id-token: write は「JWT を要求・使用する権限」のみを付与するものであり、他のリソースへの書き込み権限を直接与えるものではありません[^1]。実際の認証フローは次のようになります。

GitHub Actions ジョブが OIDC トークンを取得し Entra ID の federated credential と交換して Azure のアクセストークンを得るまでのシーケンス図

  1. Azure 側で事前に federated credential(issuer / subject / audience)を設定しておく
  2. ジョブ実行のたびに GitHub の OIDC プロバイダーが短命 JWT を発行する
  3. ワークフロー内の azure/login などのアクションが JWT を提示してトークン交換を要求する
  4. Entra ID がクレームを検証し、一致すれば短命のアクセストークンを返す
  5. 発行されたアクセストークンで Azure Resource Manager や コンテナレジストリの API を呼び出す

2.3 主要クレームと federated credential の 3 条件一致

OIDC トークンの主要クレームは次のとおりです[^3]。

クレーム内容
iss発行者。GitHub Actions では固定値 https://token.actions.githubusercontent.com
subサブジェクト。リポジトリ・環境・ブランチなどを表す文字列
audオーディエンス。Azure では既定で api://AzureADTokenExchange
その他repository / repository_id / environment / ref / job_workflow_ref / run_id など

Entra ID 側は、登録した federated credential の issuer / subject / audience が提示された JWT のクレームと 完全一致 するかどうかだけを検証します。この仕組みは Kubernetes・GCP・AWS・SPIFFE/SPIRE などでも採用されている「workload identity federation」の一種です[^4]。

3. 移行の背景 — 旧方式からの脱却

az ad sp create-for-rbac --sdk-auth--json-auth のエイリアス)は Azure CLI のリファレンス上で明示的に Deprecated と表示され、マネージド ID が利用できる場合はそちらを優先検討するよう案内されています[^5]。

観点旧方式(AZURE_CREDENTIALS / --sdk-authOIDC(Workload Identity Federation)
GitHub Secrets への保存内容クライアントシークレットを含む長寿命 JSONクライアント ID・テナント ID・サブスクリプション ID のみ(シークレットなし)
有効期限手動更新するまで有効(失念リスクあり)JWT・アクセストークンとも短命で自動失効
漏洩時の影響有効期限内は不正利用され続けるトークンが短命なため影響範囲が限定的
Azure CLI 上の扱いDeprecated公式推奨

4. セットアップ手順

4.1 必要な GitHub Secrets

リポジトリの Settings → Secrets and variables → Actions に以下を設定します。長寿命シークレットは含まれません。

Secret 名用途
AZURE_CLIENT_IDOIDC 連携先の Entra ID アプリケーション(サービスプリンシパル)ID
AZURE_TENANT_IDEntra テナント ID
AZURE_SUBSCRIPTION_IDデプロイ先サブスクリプション ID
AZURE_WEBAPP_NAMEデプロイ対象の Web App 名(サービス固有の値)
AZURE_DEPLOY_SLOT_NAMEデプロイ対象の slot 名(例: staging
AZURE_RESOURCE_GROUPデプロイ先リソースグループ
AZURE_ACR_NAMEイメージの push 先となる Azure Container Registry 名

廃止対象: 旧 AZURE_CREDENTIALSaz ad sp create-for-rbac --sdk-auth で生成した JSON)を使っている場合は、移行後に Settings → Secrets and variables → Actions から削除してください。

4.2 Entra ID アプリケーションとサービスプリンシパルの作成

bash
APP_NAME="<your-repo>-github-actions"
SUBSCRIPTION_ID="<your-subscription-id>"
RESOURCE_GROUP="<your-resource-group>"
ACR_NAME="<your-acr-name>"

# シークレットなしでアプリ + サービスプリンシパルを作成
az ad app create --display-name "$APP_NAME"
APP_ID=$(az ad app list --display-name "$APP_NAME" --query "[0].appId" -o tsv)
az ad sp create --id "$APP_ID"
SP_OBJECT_ID=$(az ad sp show --id "$APP_ID" --query id -o tsv)

取得した値は GitHub Secrets に次のように対応させます。

  • APP_IDAZURE_CLIENT_ID
  • az account show --query tenantId -o tsvAZURE_TENANT_ID
  • サブスクリプション ID → AZURE_SUBSCRIPTION_ID

4.3 ロールの付与(最小権限の考え方)

az ad sp create-for-rbac と異なり、az ad app create / az ad sp create は既定でいかなるロールも割り当てません。必要なロールを スコープを絞って 個別に付与します。

bash
# デプロイ先リソースグループ単位で Contributor を付与する例
az role assignment create --assignee "$APP_ID" \
  --role "Contributor" \
  --scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP"

# コンテナレジストリへの push 権限のみを付与する例
az role assignment create --assignee "$APP_ID" \
  --role "AcrPush" \
  --scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.ContainerRegistry/registries/$ACR_NAME"

最小権限のポイント: サブスクリプション全体ではなく、対象リソースグループ・対象リソースまでスコープを絞ります。Contributor より狭い権限で足りる場合は Website Contributor のようなサービス固有ロールへの置き換えを検討してください。

4.4 GitHub Environments の設定

federated credential の subjectenvironment:staging / environment:production を指定する構成を採る場合、Entra ID 側に登録するより前に GitHub 側の Environment を作成しておく必要があります。Settings → Environmentsstagingproduction を作成します。

Environment には次の保護ルールを追加設定することを強く推奨します。

  • 対象ブランチ・タグの制限
  • 必須レビュアー(承認者)
  • wait timer(デプロイまでの待機時間)

4.5 federated credential の登録

GitHub Environment 単位で subject を発行する構成が代表的です(例: stagingproduction)。4.4 で作成した Environment 名と subject の値を一致させます。

fic-staging.json:

json
{
  "name": "<your-repo>-staging",
  "issuer": "https://token.actions.githubusercontent.com",
  "subject": "repo:<your-org>/<your-repo>:environment:staging",
  "audiences": ["api://AzureADTokenExchange"]
}

fic-production.json:

json
{
  "name": "<your-repo>-production",
  "issuer": "https://token.actions.githubusercontent.com",
  "subject": "repo:<your-org>/<your-repo>:environment:production",
  "audiences": ["api://AzureADTokenExchange"]
}

登録コマンド:

bash
az ad app federated-credential create --id "$APP_ID" --parameters fic-staging.json
az ad app federated-credential create --id "$APP_ID" --parameters fic-production.json

注意: federated credential の管理(作成・更新・削除)には Application Administrator / Cloud Application Administrator / Global Administrator / Hybrid Identity Administrator のいずれかのロールが必要です。1 アプリあたり最大 20 個まで登録でき、issuer / subject はそれぞれ最大 600 文字です。

4.6 subject 形式のバリエーション

federated credential の subject は用途に応じて 4 つの形式から選びます[^3]。

シナリオsubject 書式適用ケース
Environment 指定repo:<owner>/<repo>:environment:<env-name>job に environment: を紐付けた場合。ブランチ・タグに関わらず同一 subject になり、多数のブランチ・タグからデプロイする構成で有用
ブランチ指定repo:<owner>/<repo>:ref:refs/heads/<branch>environment を使わない job で特定ブランチに限定したい場合
タグ指定repo:<owner>/<repo>:ref:refs/tags/<tag>リリースタグの push を起点とするデプロイに限定したい場合
Pull Requestrepo:<owner>/<repo>:pull_requestpull_request イベントでのみ、job が environment を参照していない場合に成立

セキュリティ上の注意点:

  • GitHub 公式は「未信頼のリポジトリがアクセストークンを要求できないよう、最低 1 つの条件(subject など)を必ず定義しなければならない」と明記しています[^3]。
  • pull_request をトリガーとするワークフローに、Azure デプロイのような強い権限を紐付けることは推奨されません。fork 由来の PR でも同一リポジトリ名で subject が成立し得るためです。
  • Entra ID 側は「subject の値が一致していない場合、作成時にはエラーにならず、トークン交換のタイミングで初めてサイレントに失敗する」と警告しています[^2]。登録直後は必ず実際のワークフロー実行で疎通確認してください。
  • ワイルドカードは非対応です。

build のようにビルド・push を行う job が environment を指定していない場合は、その job にも environment を付与するか、ブランチ subject(repo:<owner>/<repo>:ref:refs/heads/<branch>)を別途登録する必要があります。

4.7 ワークフロー側の permissions 設定

permissions: id-token: writeOIDC を必要とする job のみ に付与し、lint / test のような CI job には付与しません。

GitHub Actions の CI ジョブ・CD ジョブが id-token: write の付与範囲とともにビルド・push・staging デプロイ・production スワップへ進むワークフロー図

yaml
jobs:
  ci:
    # lint / test のみ。OIDC は不要なため id-token は付与しない
    permissions:
      contents: read
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run lint
      - run: npm test

  build-and-deploy:
    needs: ci
    if: github.ref == 'refs/heads/main'
    environment: staging
    permissions:
      contents: read
      id-token: write # OIDC トークン取得に必須
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Azure login (OIDC)
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Build and push image
        run: |
          az acr build --registry "${{ secrets.AZURE_ACR_NAME }}" \
            --image "<your-image>:${{ github.sha }}" .

      - name: Deploy to staging slot
        run: |
          az webapp deployment slot swap \
            --resource-group "${{ secrets.AZURE_RESOURCE_GROUP }}" \
            --name "${{ secrets.AZURE_WEBAPP_NAME }}" \
            --slot "${{ secrets.AZURE_DEPLOY_SLOT_NAME }}" \
            --target-slot production

5. トラブルシューティング

症状確認ポイント
AADSTS70021: No matching federated identity record found for presented assertion.[^9]federated credential の subject と GitHub 側(repo:<owner>/<repo>:environment:... 等)が完全一致するか確認する。federated credential 登録直後はテナント内へのレプリケーションが完了するまで数分かかることがあり、その間は同じエラーが一時的に発生し得るため、少し時間を置いてから再試行することも有効[^9]
AuthorizationFailed(コンテナレジストリ push / Web App デプロイ時)OIDC トークン交換自体は成功しているが、Azure RBAC 側で対象リソースへの権限が不足している。ロール割当のスコープ・ロール種別を確認する
id-token: write が効いていない該当 job の permissions:id-token: write が明示されているか確認する
federated credential 登録時にエラーが出ないのに認証が通らないEntra ID は subject 不一致を登録時にエラーとして返さない。ワークフロー実行時のログで初めて失敗が顕在化するため、登録後は必ず実行して疎通確認する

6. 2025〜2026 年の最新動向

6.1 Immutable subject claims(2026 年 7 月 15 日〜)

従来の GitHub 既定の sub は organization 名・repository 名のみを使用していたため、リポジトリを削除して同名で再作成すると、別の所有者が同じ subject を取得できてしまう 問題がありました。2026 年 7 月 15 日以降に作成・リネーム・移管されたリポジトリでは、owner ID と repository ID を含む immutable な sub 形式(例: repo:<owner>@<owner-id>/<repo>@<repo-id>:ref:refs/heads/main)が既定になります[^3]。既存リポジトリは明示的なオプトインまで従来形式を維持しますが、AWS・GCP・Azure・HashiCorp Vault いずれの信頼ポリシーもこの新形式への更新が将来的に必要になり得ます。

次の表は、旧形式と immutable 形式の違いをまとめたものです。

項目旧形式(〜2026/7/14 作成分は継続)immutable 形式(2026/7/15〜新規作成分)
sub の構成要素organization 名 + repository 名のみowner ID + repository ID を含む
sub の例repo:<owner>/<repo>:ref:refs/heads/mainrepo:<owner>@<owner-id>/<repo>@<repo-id>:ref:refs/heads/main
リポジトリ削除 → 同名で再作成した場合のリスク別の所有者が同じ subject を再取得できてしまうowner ID / repository ID が変わるため再取得は成立しない
適用対象既存リポジトリ(明示的なオプトインまで維持)2026 年 7 月 15 日以降に作成・リネーム・移管されたリポジトリ
信頼ポリシー側の対応要否変更不要Azure / AWS / GCP / HashiCorp Vault 側の subject 設定更新が将来必要になり得る

6.2 その他のアップデート

機能概要
カスタムプロパティの OIDC クレーム化repo_property_* プレフィックスで、リポジトリのカスタムプロパティを OIDC クレームに含め、属性ベースのアクセス制御(ABAC)ポリシーを組めるようになっている
sub claim のカスタマイズREST API の include_claim_keyssub のフォーマット自体を再定義できる
Dependabot の OIDC 対応AWS CodeArtifact / Azure DevOps Artifacts / JFrog Artifactory 向けのプライベートレジストリ認証で OIDC が利用可能になっている
2026 年のセキュリティロードマップWorkflow Execution Protections(2026 年 6 月 18 日 GA)、Actions Data Stream、GitHub-hosted ランナー向け egress ファイアウォール(技術プレビュー)などが進行中[^6]

7. 他クラウドとの比較

Azure 以外の主要クラウドも同様の OIDC 連携をサポートしています[^7] [^8]。

項目AzureAWSGCP
信頼設定の実体Entra ID アプリ/マネージド ID の federated credentialIAM OIDC Identity Provider + IAM ロールの trust policyWorkload Identity Pool + Provider
既定 audienceapi://AzureADTokenExchangests.amazonaws.comプロバイダー設定に依存
subject 条件の指定方法subject フィールドに直接文字列を指定IAM trust policy の Condition.StringEqualsattribute mapping で assertion.sub を評価する条件式
カスタムクレーム対応標準の aud / sub のみ非対応対応(job_workflow_ref 等をネイティブ参照可能)
immutable subject claim 対応対応(2026/7/15〜新形式)対応対応

8. まとめ

GitHub Actions から Azure への OIDC 連携は、長寿命シークレットを保存せずにデプロイを自動化できる、現時点で最も推奨される認証方式です。既に AZURE_CREDENTIALS を使っている場合は、次の順序で移行を進めるとスムーズです。

  1. Entra ID アプリケーション・サービスプリンシパルを新規作成する
  2. 最小権限でロールを付与する
  3. federated credential を Environment 単位で登録する
  4. ワークフローの OIDC 対応 job にのみ id-token: write を付与する
  5. 疎通確認後、旧 AZURE_CREDENTIALS を GitHub Secrets から削除する

2026 年 7 月 15 日以降は immutable subject claims のデフォルト化も控えているため、リポジトリの作成・移管を予定している場合は subject 形式の変更点も併せて確認しておくことをおすすめします。ワークフロー全体の再利用性を高めたい場合は、Reusable Workflows 実践ガイド と組み合わせて OIDC ログイン部分を共通化するのも有効です。

9. 参考リンク

9.1 脚注

[^1]: Configuring OpenID Connect in Azure — GitHub Docs [^2]: Workload identity federation — Microsoft Entra ID [^3]: About security hardening with OpenID Connect — GitHub Docs [^4]: Security hardening your deployments — OpenID Connect concepts [^5]: az ad sp — Azure CLI reference [^6]: GitHub Actions security roadmap discussion [^7]: Configuring OpenID Connect in Amazon Web Services — GitHub Docs [^8]: Configuring OpenID Connect in Google Cloud Platform — GitHub Docs [^9]: Workload identity federation for app considerations — Microsoft Entra Workload ID(「Time for federated credential changes to propagate」節に AADSTS70021: No matching federated identity record found for presented assertion. の文言が明記されている)

9.2 参考リンク一覧


この記事の執筆にあたり、AI の支援を受けています。