GitHubActionsからOpenID ConnectでAzureに接続する

GitHubActionsからAzureに接続する場合、Azureでサービスプリンシパルを作成し、GitHubのSecretに登録して認証します。ひと昔前はクライアントシークレット（パスワードに相当するもの）を含む4つの情報が必要で、`AZURE_CREDENTIALS` の名前でGitHubのSecretに登録していました。

```json
{
    "clientId": "<GUID>",
    "clientSecret": "<GUID>",
    "subscriptionId": "<GUID>",
    "tenantId": "<GUID>",
}
```

OpenID Connect（OIDC）を使うと、クライアントシークレットなしに認証することができます。以下の3つだけそれぞれ登録すればOK。
```
AZURE_CLIENT_ID
AZURE_SUBSCRIPTION_ID
AZURE_TENANT_ID
```

公式ドキュメントにわかりやすく記載されていますが、よく使うので備忘録としてまとめます。
- [GitHub Actions を使用して Azure に接続する](https://learn.microsoft.com/ja-jp/azure/developer/github/connect-from-azure)
- [Azure での OpenID Connect の構成](https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-azure)
- [ワークロード ID フェデレーション](https://learn.microsoft.com/ja-jp/azure/active-directory/workload-identities/workload-identity-federation)

記事の中でご紹介するコードはGitHubに公開しています。

https://github.com/m-oka-system/azure-gha-oidc


# サービスプリンシパルの作成
まずはじめに、Azure AD で新しいアプリを作成し、そのアプリにサービスプリンシパルを作成します。
```bash
appName="githubactions"

# Azure sign in
az login

# Get the current Azure subscription ID
subscriptionId=$(az account show --query 'id' --output tsv)

# Create a new Azure Active Directory application
appId=$(az ad app create --display-name $appName --query appId --output tsv)

# Create a new service principal for the application
assigneeObjectId=$(az ad sp create --id $appId --query id --output tsv)
```
これは、Azure AD > アプリの登録から新規登録する操作に該当します。`appName` はお好みで。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/fe6996f0-7ad2-1e2b-de11-14896cf0ba06.png)

# ロールの割り当て
ここではサブスクリプションをスコープとして「共同作成者」のロールを割り当てています。
```bash
# Assign the 'Contributor' role to the service principal for the subscription
az role assignment create --role contributor \
  --subscription $subscriptionId \
  --assignee-object-id $assigneeObjectId \
  --assignee-principal-type ServicePrincipal \
  --scope /subscriptions/$subscriptionId
```

# フェデレーション資格情報の追加
フェデレーション資格情報を追加して、GitHubのリポジトリを信頼するように構成します。
```bash
# Assign the 'Contributor' role to the service principal for the subscription
az ad app federated-credential create --id $appId --parameters credential.json
```
パラメータはjsonファイルで渡しています。
`subject` がパッと見わかりにくいですが、例えば mian ブランチを対象とする場合は `repo:<user_name>/<repo_name>:ref:refs/heads/main` と書きます。

- name：任意の名前
- issuer：トークンの発行者（GithubActions）
- subject：フェデレーション ID 資格情報に関連付けられている対象リソース
    - 組織名、リポジトリ名、ブランチ名やタグの情報から構成される
    - OIDCトークンの検証に使用される
- description：説明（省略可）
- audiences：対象ユーザー（api://AzureADTokenExchange 固定）
```json:credential.json
{
  "name": "m-oka-system-azure-gha-oidc-main",
  "issuer": "https://token.actions.githubusercontent.com",
  "subject": "repo:m-oka-system/azure-gha-oidc:ref:refs/heads/main",
  "audiences": [
    "api://AzureADTokenExchange"
  ]
}
```

これは、Azure AD > アプリの登録 > 登録したアプリ > 証明書とシークレット > フェデレーション資格情報タブの「資格情報の追加」の操作に該当します。繰り返しになりますが、「クライアントシークレット」の発行は必要ありません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/da28c8c8-a9d9-e682-6cad-8d10f839ab4e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/80251865-2840-6a28-0b7d-671b4d88e61f.png)

# GitHubにシークレットを作成
GitHub CLIで Repository secrets を登録します。
```bash
gh auth login
gh secret set -f .secrets
```

```.secrets
AZURE_TENANT_ID=<GUID>
AZURE_SUBSCRIPTION_ID=<GUID>
AZURE_CLIENT_ID=<GUID>
```

これは、リポジトリのSettings > Secrets and variables > Actions > New repository secret の操作に該当します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/dc549a9a-f6a2-b580-8081-96767e0b456e.png)


# GitHubActionsのワークフローを作成
Azure にサインインして `az account show` でサブスクリプション名を取得するだけのワークフローを作ってみました。
```.github\workflows\main.yml
name: Azure Login with OpenID Connect

on:
  push:
    branches:
      - main
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

env:
  azcli_verion: 2.46.0

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Azure Login
        uses: azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Azure CLI script
        uses: azure/CLI@v1
        with:
          azcliversion: ${{ env.azcli_verion }}
          inlineScript: |
            az account show --query name

      - name: logout
        run: |
          az logout
```

ポイントは2カ所です。

- **アクセス許可設定の追加**
OIDCのトークンを要求するために `id-token: write` のパーミッションが必要です。
```yaml
permissions:
  id-token: write
  contents: read
```

- **Repository secretsの参照**
`azure/login@v1` アクションの `with` で先に登録したシークレットを渡して認証しています。
```yaml
      - name: Azure Login
        uses: azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
```

# ワークフローの実行
リポジトリにpushするとワークフローが実行されます。
サブスクリプションの名前 `Pay-As-You-Go` が表示されていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/1683aa24-9db8-ad9e-eac8-6c7b2dfd6eab.png)

今回は簡単なAzure CLIの実行だけでしたが、`Dockerfile` をビルドしてACRにpushしたり、App Serviceにアプリをデプロイする時などに役立つと思います。