ほりひログ

所属組織の製品 (Azure とか) に関連する内容が多めだけど、個人の見解であって、所属組織を代表する公式情報ではないです。

GitHub Actions w/ OIDC で ACR に docker push する

f:id:horihiro:20220321154822p:plain

TL; DR

azure/docker-login など docker login 系の Action ではなく az acr login コマンドでログインする。

name: Run Login to ACR w/ OpenID Connect

on:
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

jobs:
  ACRJob:
    runs-on: ubuntu-18.04

    steps:
      - uses: actions/checkout@main
      - uses: azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} 

      - name: docker login, build and push
        run: |
          az acr login -n ${{ secrets.ACR }}  # <--- **これ**
          docker build ./target -t ${{ secrets.ACR }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ github.run_number }}
          docker push ${{ secrets.ACR }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ github.run_number }}
        env:
          IMAGE_NAME: minimalserver

あ、Azure AD アプリケーションに AcrPull のロールを割り当てることを忘れずに。

以下、背景的な何か

GitHub Actions が OpenID Connect に対応してた

もう半年近く前、GitHub Actions が OpenID Connect に対応していた。
これで azure/login のアクションも資格情報として Azure AD アプリケーションの clientSecret を使わなくて済むようになった。

詳細はここに全て書いてある。

blog.haniyama.com

ただし Azure Container Regisitry & GitHub Actions のドキュメントを見ると、azure/docker-login で昔ながら?の clientSecret を使って ACR にログインする方法で書かれている。

ここと、

f:id:horihiro:20220319200022p:plain

ここ。

f:id:horihiro:20220319195916p:plain

できることなら clientSecret はあまり管理したくないので、何とか OIDC の仕組みを使って、GitHub Actions 内で clientSecret を使わない方法がないかにチャレンジ。

(おさらい) ACR にログインする

ACR は Docker の API (でいいの?)と互換性を持っているので、

docker login -u <ユーザー名> -p <パスワード> <ACR ログイン サーバー>

でログインできる。<ユーザー名> / パスワード は、

  1. Azure AD アプリケーションの場合は、
    • ユーザー名: アプリケーション ID (ClientID)
    • パスワード: clientSecret
  2. ACR の管理者ユーザーを有効にした場合は、
    • ユーザー名: ACR リソース名
    • パスワード: [アクセス キー] ブレード内の password or password2
      f:id:horihiro:20220319195524p:plain

のどちらかのペアを使うことになる。

今回は、OIDC を使って azure/login アクションで取得した認証情報を、ここのユーザー名/パスワードとして使う方法がないか調べたら、そこまでしなくていい方法がここに書いてあった。

github.com

曰く、

az acr login -n <ACR リソース名>

を使えばいい、と。

これを実行すると、

  • ユーザ名は null GUID 00000000-0000-0000-0000-000000000000
  • パスワードは、azure/login からの認証情報を元に(なんやかんやで)取得してきた ACR の refresh token

を使って docker login を実行してくれるらしい。
azure/docker-login アクションすら必要ないので、冒頭yaml になる。

サンプル リポジトリー。

github.com

ちなみに azure/docker-login がやっていること

azure/docker-loginwith で与えられた clientSecret などを使って何しているか見てみると、

github.com f:id:horihiro:20220320092328p:plain

なんと直接 config.json に認証情報を追記している。docker login すら実行していないとは。

act を使ってローカル プロジェクトを Azure Static Web Apps にデプロイしてみた


Note!
あくまで「やってみたらできた」的エントリーです。公式の方法ではありません。試すときは壊れてもいいリソースで。


act

github.com

f:id:horihiro:20220213091853p:plain なるものです。スゴイ。
同僚 (一度だけ実物見たことある) の YouTube 配信で「GitHub Actions のデバッグって大変ですよねー」と愚痴コメントしたら、その場で調べて教えてくれました。

Go 製のワン バイナリー ツールなので、(いつでも消せそうなので)気軽に入れられます。
各アクションの実行に Docker を使っているのでこれも必要です。
インストールは公式リポを参照ください。

act <event> <options>

みたいに実行すると、GitHub Actions の yaml ファイルを探して (既定では ./.github/workflows 以下)、<event> *1 にあった action を実行するみたいです。

Runner は Linux 限定、など制限はあるみたいですね。

Azure Static Web Apps

一方で Azure Static Web Apps (以下、SWA) 。 docs.microsoft.com

Azure Static Web Apps は、コード リポジトリから Azure にフル スタックの Web アプリを自動的にビルドしてデプロイするサービスです。

とあるとおり、GitHub Actions や Azure Pipeline を使ってGitHub (or Azure DevOps) のリポジトリにある静的サイトのコードをビルドして、Azure 上の SWA リソースにデプロイまでしてくれます。

SWA デプロイ用の GitHub Actions

この SWA デプロイ用の GitHub Actions を act でローカル実行し、Azure 上の SWA にデプロイできるか試してみました。

ぶっちゃけ、SWA デプロイ用の GitHub Actions は自動生成されるものなので、GitHub Actions 自体をデバッグすることはあまりないでしょう。それはうすうす気づいています。
あくまで、act を試してみた、という実験です。

以下、手順。

0. 事前準備

まず、SWA 自体を Azure にデプロイしておきます。GitHub と連携する必要はないです。

f:id:horihiro:20220213110401p:plain

次にプロジェクトの用意 SWA にデプロイする予定のプロジェクトを GitHub 等に作成して、ローカルに git clone しておきます。

1. GitHub Actions の用意

./.github/workflows に下記 yaml ファイルに置きます。ほぼ自動生成されるものそのままです*2

name: Azure Static Web Apps CI/CD

on:
  push:

jobs:
  build_and_deploy_job:
    if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
    runs-on: ubuntu-latest
    name: Build and Deploy Job
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true
      - name: Build And Deploy
        id: builddeploy
        uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
          repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
          action: "upload"
          ###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
          # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
          app_location: "/" # App source code path
          api_location: "" # Api source code path - optional
          output_location: "dist" # Built app content directory - optional
          ###### End of Repository/Build Configurations ######

2. デプロイ トークンのコピー

Azure ポータルを開いてデプロイ トークンをコピーします。
yaml ファイルに書いた secrets.AZURE_STATIC_WEB_APPS_API_TOKEN として使います。

[概要] ブレードの 「デプロイ トークンの管理」からコピーできます。

f:id:horihiro:20220213110216p:plain

3. .actrc の準備

act の実行設定として下記の内容を .actrc としてプロジェクトのルートに保存します。

-s AZURE_STATIC_WEB_APPS_API_TOKEN=<2. でコピーしてきたデプロイ トークン>
-P ubuntu-latest=node:14.18.3-buster-slim
-P ubuntu-20.04=node:14.18.3-buster-slim
-P ubuntu-18.04=node:14.18.3-buster-slim

ここに書いてしておくと、act 実行時のオプションとして自動で読み込まれます。

最初の行は 2. でコピーした SWA のデプロイ トークンを貼り付けます。
-s FOO_BAR=... と書くと GitHub Actions 内で ${{ secrets.FOO_BAR }} で参照できます。

残りの行は runs-on: で指定するプラットフォームと使うコンテナー イメージの対応です。
ubuntu-*** と書いてあるのに Debian のイメージだったりしますが、細かいことはいいのかもしれません。気にしない。

ポイントは Node.js のバージョンをパッチ レベルまで指定していること。
Oryx (SWA のビルド エンジンである) は Node.js のサポート バージョンとしてパッチ レベルまで指定しています。
なので、マイナー/パッチを省略したバージョンを使うと、コンテナー レジストリ (Docker Hub) 内のイメージ更新で、いつか動かなくなるかもしれません。

f:id:horihiro:20220213121501p:plain

ちなみに act を始めて実行する時、既定のプラットフォームのサイズを Micro とすると、ホーム ディレクトリ配下に .actrc を作りますが、node:12-buster-slim*3 が使われます。
一方で Node.js V12 の 2022 年 2 月現在の最新バージョンは v12.22.10 なので、上のスクショのように「バージョンが合わん」とエラーになります。

4. act を実行する

プロジェクト ルートはこうなっているはず。

$ tree -a
.
├── .actrc
├── .github
│   └── workflows
│       └── azure-static-web-apps.yml
├── .gitignore
├── src
│   :
:
└── staticwebapp.config.json

ここで act push を実行 (push イベントを発行) すると GitHub Actions が実行され、SWA 上にデプロイされるはずです。

まとめ

SWA の GitHub Actions を題材にしたのは反省しているが、act は使えそう。

*1: push とか pull_request とか

*2:静的サイト構築に使うフレームワークによってビルド設定等は変わります

*3:node:16-buster-slim の場合も

Azure での SSL 証明書対応状況 (2022 年 2 月版)

API Management のマネージド証明書が Public Preview になったのを記念して(?)、他のサービスの証明書の対応状況 (2022 年 2 月現在) のまとめたメモ。間違ってたらごめんなさい。

Service Managed Certificate Bring Your Own Certificate, Key Vault
App Service
Key Vault Certfificate:
Key Vault Secret:
Kubernetes Services -
Key Vault:
Container Instances -
Application Gateway -
Key Vault:
Front Door Key Vault:
API Management
Key Vault:
Static Web Apps -
Cloud Services -

# 状況が変わったら更新する、かもしれないし、しないかもしれない。

オレ流 Azure RBAC のカスタム ロールの作り方 (3) ~ CLI ツール作りました

前回、前々回で紹介したカスタム ロール作成手順があまりに面倒だったので、少しでも手間を減らすために CLI ツール custom-role-generator を作りました。

github.com

初 Rust です。ググりながら作ったコードです。

ちなみに、前回・前々回はこちら。

uncaughtexception.hatenablog.com
uncaughtexception.hatenablog.com

こちらで紹介した下記の手順のうち、主に 1. と 2. あたりをサポートします。

  1. Azure CLI を使って必要な Azure REST API を確認する
  2. HTTP メソッドとエンドポイント URL からアクションを組み立てる
  3. とりあえずカスタム ロールを試す
  4. 地道にカスタム ロールを修正する

インストールは Releases から zip をダウンロードして解凍するだけです。さすが Rust 。ワンバイナリー*1

使い方

custom-role-generator は標準入力からインプットされた内容を解析します。

なので、az コマンドに --debug を付けて標準エラー出力にログを出力し、そのログを 2>&1 で標準出力にリダイレクトして、パイプ |custom-role-generator で渡せば動きます。

$ az [何かのコマンド] --debug 2>&1 | custom-role-generator

ファイルに出力したログも、< を使えば標準入力に渡せます。

$ az [何かのコマンド] --debug 2>az.log # 標準エラー出力を `az.log` に出力
$ custom-role-generator < az.log # `az.log` を標準入力から入力

まともに動けば、標準出力にカスタム ロールの定義ファイルが出力されます。

{
  "AssignableScopes": [
    "/subscriptions/..."
  ],
  "actions": [
    "Some.Provider/.../...",
      :
  ],
  "dataActions": [],
  "description": "Custom role definition for `az [何かのコマンド]` generated by custom-role-generator",
  "isCustom": true,
  "name": "<REPLACE_THIS>",
  "notActions": [],
  "notDataActions": []
}

既存のカスタム ロール定義ファイルを --append-to オプションで指定することで、ログから抽出した action を actions に追加することもできます。

$ az [何かのコマンド] --debug 2>&1 | custom-role-generator --append-to /path/to/existing/role-definition.json
{
  "AssignableScopes": [
    "/subscriptions/..."
  ],
  "actions": [
    "[もともとあった action1]",
    "[もともとあった action2]",
      :
    "[追加された action1]",
    "[追加された action2]",
      :
  ],
  "dataActions": [],
  "description": "Custom role definition for `az [何かのコマンド]` generated by custom-role-generator",
  "isCustom": true,
  "name": "<REPLACE_THIS>",
  "notActions": [],
  "notDataActions": []
}

あと custom-role-generator --debug のように --debug オプションを付けると、入力されたログをそのまま標準エラー出力に出力します。

実行例

やっぱり VNET 統合

まずは共同作成者なり、 VNET 統合を設定可能な権限を持ったユーザーでコマンドを実行し、ベースになるロール定義を作ります。

# 共同作成者ロールでの実行
$ az webapp vnet-integration add --id <WebApp のリソース ID> --vnet <VNET のリソース ID> --subnet <Subnet 名> --debug 2>&1 | custom-role-generator > role-vnet-integration1.json
$ cat role-vnet-integration1.json
{
  "AssignableScopes": [
    "/subscriptions/..."
  ],
  "actions": [
    "Microsoft.Network/locations/operations/read",
    "Microsoft.Network/virtualNetworks/read",
    "Microsoft.Network/virtualNetworks/subnets/read",
    "Microsoft.Network/virtualNetworks/subnets/write",
    "Microsoft.Resources/subscriptions/locations/read",
    "Microsoft.Web/serverfarms/read",
    "Microsoft.Web/sites/config/read",
    "Microsoft.Web/sites/config/write",
    "Microsoft.Web/sites/publishxml/action",
    "Microsoft.Web/sites/read",
    "Microsoft.Web/sites/write"
  ],
  "dataActions": [],
  "description": "",
  "isCustom": true,
  "name": "Custom role definition for `az webapp vnet-integration add` generated by custom-role-generator",
  "notActions": [],
  "notDataActions": []
}

これで、上述の 4 ステップのうち 1. と 2. が終了。

このロール定義を使ってカスタム ロールを作成し、テスト ユーザーに関連リソースのカスタム ロールに割り当てて、今度はそのテスト ユーザーで実行します。
これで成功したら嬉しいですが、前回のエントリーで書いた通り、VNET 統合の場合はこの段階ではまだ成功しません。
原因 (足りない action) を追加するため、あえて失敗するユーザーでの実行ログをもう一度 custom-role-generator に入れます

# テストユーザーでの実行
$ az webapp vnet-integration add --id <WebApp のリソース ID> --vnet <VNET のリソース ID> --subnet <Subnet 名> --debug 2>&1 \
| custom-role-generator --append-to role-vnet-integration1.json > role-vnet-integration2.json
$ cat role-vnet-integration2.json
{
  "AssignableScopes": [
    "/subscriptions/..."
  ],
  "actions": [
    "Microsoft.Network/locations/operations/read",
    "Microsoft.Network/virtualNetworks/read",
    "Microsoft.Network/virtualNetworks/subnets/join/action", # <- 追加される action
    "Microsoft.Network/virtualNetworks/subnets/read",
    "Microsoft.Network/virtualNetworks/subnets/write",
    "Microsoft.Resources/subscriptions/locations/read",
    "Microsoft.Web/serverfarms/read",
    "Microsoft.Web/sites/config/read",
    "Microsoft.Web/sites/config/write",
    "Microsoft.Web/sites/publishxml/action",
    "Microsoft.Web/sites/read",
    "Microsoft.Web/sites/write"
  ],
  "dataActions": [],
  "description": "",
  "isCustom": true,
  "name": "Custom role definition for `az webapp vnet-integration add` generated by custom-role-generator",
  "notActions": [],
  "notDataActions": []
}

これをもう一回テストユーザーに割り当てると問題なく VNET 統合が設定できます。

他のコマンドでも似たようなものだと思うので、ぜひ手強そうなカスタム ロールづくりに試してもらってフィードバックいただければ。

なお Rust 始めたばかりで全然わかりません。リファクタリングしないといけないし、テストもまともに書いていない、書けない。
PR も絶賛募集中。

これにてカスタム ロールの作り方はおしまい。


*1:Rust だけじゃないですけど

オレ流 Azure RBAC のカスタム ロールの作り方 (2) ~ VNET 統合の場合

今回はですね、こちらのオレ流 Azure RBAC のカスタム ロールの作り方 (1)の手順を使って、App Service (Web Apps や Function App)の VNET 統合を Azure CLI から設定できるカスタム ロールを作ってみますよ。
VNET 統合は App Service だけでなく、VNET/Subnet も操作するので、割と面倒複雑なカスタム ロールになります。

ではやってみます。

1. Azure CLI を使って必要な Azure REST API を確認する

まず共同作成者ロールのユーザーで下記 Azure CLI で実行してみます。

az webapp vnet-integration add --id <WebApp のリソース ID> --vnet <VNET のリソース ID> --subnet <Subnet 名> --debug

このコマンドに出力されるログから、grep 等で Azure REST API のアクセス ログを探すと、以下の 17 件が抽出できます。

DEBUG: urllib3.connectionpool: https://management.azure.com:443 "GET /subscriptions/.../providers/Microsoft.Network/virtualNetworks?api-version=... HTTP/1.1" 200 749
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "GET /subscriptions/.../resourceGroups/.../providers/Microsoft.Web/sites/...?api-version=... HTTP/1.1" 200 None
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "GET /subscriptions/.../resourceGroups/.../providers/Microsoft.Web/sites/...?api-version=... HTTP/1.1" 200 None
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "GET /subscriptions/.../resourceGroups/.../providers/Microsoft.Web/sites/.../config/web?api-version=... HTTP/1.1" 200 None
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "POST /subscriptions/.../resourceGroups/.../providers/Microsoft.Web/sites/.../publishxml?api-version=... HTTP/1.1" 200 1589
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "GET /subscriptions/.../resourceGroups/rg-linux-plan/providers/Microsoft.Web/serverfarms/...?api-version=... HTTP/1.1" 200 None
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "GET /subscriptions/.../resourceGroups/.../providers/Microsoft.Network/virtualNetworks/...?api-version=... HTTP/1.1" 200 None
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "GET /subscriptions/.../locations?api-version=... HTTP/1.1" 200 4592
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "GET /subscriptions/.../locations?api-version=... HTTP/1.1" 200 4592
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "GET /subscriptions/.../resourceGroups/.../providers/Microsoft.Network/virtualNetworks/.../subnets/...?api-version=... HTTP/1.1" 200 None
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "PUT /subscriptions/.../resourceGroups/.../providers/Microsoft.Network/virtualNetworks/.../subnets/...?api-version=... HTTP/1.1" 200 None
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "GET /subscriptions/.../providers/Microsoft.Network/locations/.../operations/...?api-version=... HTTP/1.1" 200 None
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "GET /subscriptions/.../resourceGroups/.../providers/Microsoft.Network/virtualNetworks/.../subnets/...?api-version=... HTTP/1.1" 200 None
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "PATCH /subscriptions/.../resourceGroups/.../providers/Microsoft.Web/sites/...?api-version=... HTTP/1.1" 200 None
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "GET /subscriptions/.../resourceGroups/.../providers/Microsoft.Web/sites/.../config/web?api-version=... HTTP/1.1" 200 None
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "GET /subscriptions/.../resourceGroups/.../providers/Microsoft.Web/sites/.../config/web?api-version=... HTTP/1.1" 200 None
DEBUG: urllib3.connectionpool: https://management.azure.com:443 "PATCH /subscriptions/.../resourceGroups/.../providers/Microsoft.Web/sites/.../config/web?api-version=... HTTP/1.1" 200 None

結構いろんなところにリクエストを投げてますね。

2. HTTP メソッドとエンドポイント URL からアクションを組み立てる

上の 17 件のアクセス ログ、それぞれについて、リソース プロバイダーとリソース タイプ、HTTP メソッドを元にして構築し重複分を除きます。

    "Microsoft.Network/locations/operations/read",
    "Microsoft.Network/virtualNetworks/read",
    "Microsoft.Network/virtualNetworks/subnets/read",
    "Microsoft.Network/virtualNetworks/subnets/write",
    "Microsoft.Resources/subscriptions/locations/read",
    "Microsoft.Web/serverfarms/read",
    "Microsoft.Web/sites/config/read",
    "Microsoft.Web/sites/config/write",
    "Microsoft.Web/sites/publishxml/action",
    "Microsoft.Web/sites/read",
    "Microsoft.Web/sites/write"

11 の action までまとまりました。

一点、GET /subscriptions/.../locations のアクセス ログはリソース プロバイダーを含まず、サブスクリプション直下のロケーション情報なので、対応する action である Microsoft.Resources/subscriptions/location/read に変換しています。

3. とりあえずカスタム ロールを試す

今回は Azure CLI でカスタム ロールを作ります。
上でまとめた 11 の action から下記の JSON を用意しました。

{
  "AssignableScopes": [
    "/subscriptions/..."
  ],
  "actions": [
    "Microsoft.Network/locations/operations/read",
    "Microsoft.Network/virtualNetworks/read",
    "Microsoft.Network/virtualNetworks/subnets/read",
    "Microsoft.Network/virtualNetworks/subnets/write",
    "Microsoft.Resources/subscriptions/locations/read",
    "Microsoft.Web/serverfarms/read",
    "Microsoft.Web/sites/config/read",
    "Microsoft.Web/sites/config/write",
    "Microsoft.Web/sites/publishxml/action",
    "Microsoft.Web/sites/read",
    "Microsoft.Web/sites/write"
  ],
  "dataActions": [],
  "description": "",
  "isCustom": true,
  "name": "<何か名前を付ける>",
  "notActions": [],
  "notDataActions": []
}

AssignableScopes に記載するサブスクリプション ID は適宜変更してください。

この JSON ファイルを使ってカスタム ロールの定義を作ります。

az role definition create --role-definition <JSON ファイルのパス>

上記の action では Microsoft.Web/sitesMicrosoft.Web/serverfarmsMicrosoft.Networks/virutalNetworks の 3 種類のリソースが出てきているので、VNET 統合に使う App Service 、App Service Plan、仮想ネットワークの 3 つのリソースに対して作ったカスタム ロールをテスト ユーザーに割り当てて、そのユーザーで最初の Azure CLI を実行してて結果を見ます。

4. 地道にカスタム ロールを修正する

ここで終わればラッキーですが、テスト ユーザーに作って実行してみると下記のエラーが出ます。
やはりまだ権限が足りていませんでした。

Message: The client '...' with object id '...' has permission to perform action 'Microsoft.Web/sites/write' on scope '/subscriptions/.../resourceGroups/.../providers/Microsoft.Web/sites/...'; however, it does not have permission to perform action 'join/action' on the linked scope(s) '/subscriptions/.../resourceGroups/../providers/Microsoft.Network/virtualNetworks/.../subnets/...' or the linked scope(s) are invalid.

App Service への書き込み権限はあるけど、Subnet への接続の権限がない、といった様子。

メッセージを元に、Microsoft.Network/virtualNetworks/subnets/join/action の action をカスタム ロールに追加し、再度ロールの割り当てからトライすると無事成功することが確認できました。

{
  "AssignableScopes": [
    "/subscriptions/..."
  ],
  "actions": [
    "Microsoft.Network/locations/operations/read",
    "Microsoft.Network/virtualNetworks/read",
    "Microsoft.Network/virtualNetworks/subnets/read",
    "Microsoft.Network/virtualNetworks/subnets/write",
    "Microsoft.Resources/subscriptions/locations/read",
    "Microsoft.Web/serverfarms/read",
    "Microsoft.Web/sites/config/read",
    "Microsoft.Web/sites/config/write",
    "Microsoft.Web/sites/publishxml/action",
    "Microsoft.Web/sites/read",
    "Microsoft.Web/sites/write",
    "Microsoft.Network/virtualNetworks/subnets/join/action"
  ],
  "dataActions": [],
  "description": "",
  "isCustom": true,
  "name": "<何か名前を付ける>",
  "notActions": [],
  "notDataActions": []
}

これで Azure CLI から VNET 統合を設定できるカスタム ロールの完成です。 成功しない (追加したはずの action でエラーが出る) 場合は、ロールの反映に時間がかかっている場合がほとんどなので、時間を置く and/or ログアウト&ログインを試せば、いずれ成功します。

おまけ (削除 & ポータル操作対応)

ついでに VNET 統合の無効化と Azure ポータルからの操作権限を追加したカスタム ロールこちらです。

{
  "AssignableScopes": [
    "/subscriptions/..."
  ],
  "actions": [
    "Microsoft.Network/locations/operations/read",
    "Microsoft.Network/virtualNetworks/read",
    "Microsoft.Network/virtualNetworks/subnets/read",
    "Microsoft.Network/virtualNetworks/subnets/write",
    "Microsoft.Resources/subscriptions/locations/read",
    "Microsoft.Web/serverfarms/read",
    "Microsoft.Web/sites/config/read",
    "Microsoft.Web/sites/config/write",
    "Microsoft.Web/sites/publishxml/action",
    "Microsoft.Web/sites/read",
    "Microsoft.Web/sites/write",
    "Microsoft.Network/virtualNetworks/subnets/join/action",
    "Microsoft.Web/sites/networkConfig/delete",
    "Microsoft.Web/sites/config/list/Action",
    "Microsoft.Web/sites/virtualnetworkconnections/read",
    "Microsoft.Web/sites/networkConfig/read"
  ],
  "dataActions": [],
  "description": "",
  "isCustom": true,
  "name": "<何か名前を付ける>",
  "notActions": [],
  "notDataActions": []
}

削除に必要な action Microsoft.Web/sites/networkConfig/deleteaz functionapp vnet-integration remove コマンドのリクエスト ログから、Azure ポータル上から VNET 統合を構成するために必要な action Microsoft.Web/sites/config/list/Action, Microsoft.Web/sites/virtualnetworkconnections/read, Microsoft.Web/sites/networkConfig/read の 3 つは Web ブラウザーのネットワーク トレースを地道に読み解いています。

以上、App Service の VNET 統合を Azure CLI で設定可能なカスタム ロール作成の実例でした。

「これじゃうまくいかない」といったことがあればコメントででもいただければ。

オレ流 Azure RBAC のカスタム ロールの作り方 (1)

今回は、Azure 上のリソースのアクセス管理ができる Azure RBAC のカスタム ロールの作り方です。

カスタム ロールを作って、(共同作成者みたいな巨大な権限ではなく)一部の操作のみができる権限をユーザーに割り当てるシーンはよくあると思います。

作り方はドキュメントに書いてあるのですが、、、

docs.microsoft.com f:id:horihiro:20220122121314p:plain

この「使用可能なアクションを把握」というのが割と大変なので、なんとなくいつもやってるはず、という手順をメモとして残しておきます。
もっと楽なやり方があれば教えてもらえるとありがたいです🙇‍♂️

1. Azure CLI を使って必要な Azure REST API を確認する

まず最初に設定に必要な Azure REST API へのリクエスト パターンを把握するため、対象の操作を実行できる権限を持つユーザーで Azure CLI--debug オプション付きで実行します。

az <sub-command> <options ...> --debug

すると、標準エラー出力に大量のログが表示される中、以下のような Azure REST API へのリクエスト ログが確認できます。

f:id:horihiro:20220122122059p:plain

フォーマットは以下のようになっているはずです (2022 年 1 月現在)。

DEBUG: urllib3.connectionpool: https://management.azure.com:443 "<HTTP メソッド> <Azure REST API のエンドポイント URL > HTTP/1.1" <レスポンス コード> <レスポンス ボディの長さ>

このうち使うのは、HTTP メソッドと Azure REST API のエンドポイント URL です。
これを全てピックアップします。

なお、この REST API の実行は、リソースの状態によってスキップするリクエストもあるので、作り立てホヤホヤのリソースで試すことがお勧めです。

2. HTTP メソッドとエンドポイント URL からアクションを組み立てる

Azure REST API のエンドポイント URL は、一般的には以下のフォーマットです。

https://management.azure.com:443/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME>/providers/<RESOURCE_PROVIDER>/<RESOURCE_TYPE_1>/<RESOURCE_NAME_1>/.../<RESOURCE_TYPE_n>/<RESOURCE_NAME_n>?api-version=<API_VERSION>

このうち使うのは、<RESOURCE_PROVIDER> と <RESOURCE_TYPE_[1..n]> で、以下のように連結します。

<RESOURCE_PROVIDER>/<RESOURCE_TYPE_1>/.../<RESOURCE_TYPE_n>

最後に以下の表から HTTP メソッドに対応した動詞を見つけてそれを足します。

HTTP メソッド 動詞
GET read
PUT write
PATCH write
DELETE detete
POST action

例えば、といったリクエスト ログが見つかったとします。

DEBUG: urllib3.connectionpool: https://management.azure.com:443 "PATCH /subscriptions/.../resourceGroups/.../providers/Microsoft.Web/sites/.../config/...?api-version=... HTTP/1.1" 200 ...

すると、これを実行するためにカスタム ロールに必要な action は

Microsoft.Web/sites/config/write

になります。

これをピックアップしたリクエスト ログすべてに対して実施して、action のリストを作ります。

全 action はこのドキュメントにあるので、作った action が載っているか確認しましょう。

docs.microsoft.com

3. とりあえずカスタム ロールを試す

action のリストができたら一度カスタム ロールを作ります。

作り方は Azure CLI でも Azure ポータルでもお好みで。

docs.microsoft.com

作成したロールをテスト ユーザーに割り当てて、そのユーザーとして最初に実行した Azure CLI を実行してみます。
ロール割り当ての反映には、時間がかかったりトークンを更新しないといけない場合が多く、追加したはずの action でエラーになる場合もありますが、根気よく試しましょう。

4. 地道にカスタム ロールを修正する

これで成功したらは完成ですが、そうはいかない場合も多々あります。

ここまででリスト アップした action は、最初にリクエスト ログを取得した Azure CLI のコマンドが、直接リクエストを実行しているログを元にしています。
一方で、Azure CLI からのリクエストを起点に、Azure Resource Manager が実行ユーザーの権限で裏で別の API を実行するケースがあります。

この場合、Azure CLI のログにはエンドポイント URL は記録されません。
このケースでの action 不足の時は、エラー ログとして以下のようなメッセージが表示されます。

Message: The client '...' with object id '...' has permission to perform action '<カスタム ロールに記載した action>' on scope '<ロールのスコープ>'; however, it does not have permission to perform action '<不足している action>' on the linked scope(s) '<不足 action の対象スコープ>' or the linked scope(s) are invalid.

however 以降が足りない action とスコープの説明になっているので、これを元に再度カスタム ロール用の action を追加してロールを割り当てて実行します。

これを繰り返して、エラーが消えれば、カスタム ロールの完成です。
結局、ここの部分が割と時間がかかりるのですが、諦めず頑張りましょう。

FAQ

Q1. write の action である機能の無効化もできますか?

一般的に write の action は既存のリソースの情報を書き換えるために必要な権限です。
その為、例えばある機能の有効化/無効化の切り替えを、既存のリソース情報の書き換え (プロパティ foobarEnabledtruefalse を入れる、など) で制御しているのであれば、write の action でも無効化は可能だと思います。

一方で、有効化に子リソースを追加、無効化に子リソースの削除、といった操作を伴う機能の場合、writeaction だけで対象リソースの削除はできないので、delete の action が必要になります。

ちなみに write の action で read の action をカバーすることもできないのでご注意を。

Q2. Azure ポータルでの操作と Azure CLI での操作を、一つのカスタム ロールでカバー出来ますか?

二つの操作は共通部分は多いですが、それぞれ独自に必要な action もあります。 Azure ポータルでの操作は、UI 上に表示するための情報取得のリクエストが発生する場合があり、逆に Azure CLI の場合はオプションでその辺りを決め打ちで指定するので、比較的 Azure ポータルでの操作の方が Azure CLI での操作よりも広い権限を求めることが多いようです。

なので一つのカスタム ロールにまとめたい場合は、それらの action 全てを含くんだ (= OR を取った) カスタム ロールを作る必要があります。

次は App Service の VNET 統合を例にして、以上のステップを試してみたいと思いますが、長くなったので別エントリーへ(書きました)。

Azure Static Web Apps の Enterprise-grade edge がパブリック プレビューになりました

Azure Static Web Apps (以下 SWA) の Enterprise-grade edge はハッキリ言うと、マネージドの (ユーザーのサブスクリプション外にある) Azure Front Door (以下 AFD)連携です。

azure.microsoft.com

SWA は一つのリソースでいくつかのリージョンにコンテンツをデプロイしていて、リソース内部に持つ Traffic Manager (以下 TM) を使って、コンテンツ配信に最適なリージョンに振り分けてます。
で、今回パブリック プレビューとなった Enterprise-grade edge では、この SWA の前段にある TM をマネージドの AFD に置き換えているようです。

絵で描くとこれ↓が、

f:id:horihiro:20220119165303p:plain

こう↓なります。

f:id:horihiro:20220119165552p:plain

AFD は CDN なので、SWA が配信するコンテンツを世界各地のエッジ サーバーでキャッシュできますし、AFD 自身も TM を中に持っているので、クライアントの場所によって (ネットワーク的に) 最も近いエッジ サーバーからコンテンツを配信できるので、コンテンツの配信速度の向上が見込めます

やってみる

ドキュメントによると、いくつか事前に設定が必要です。

  • カスタム ドメイン (TTL が 48 時間未満) をSWA に設定
  • SWA のプランを Standard に
  • 対象の Azure サブスクリプションにて、Microsoft.CDN リソース プロバイダーを再登録

docs.microsoft.com

あとは、Azure ポータルで SWA の [Enterprise-grade edge] というブレードを開いて有効化するだけです。

f:id:horihiro:20220119160840p:plain

ちなみに、Free プランの場合は [Enterprise-grade edge] ブレードがグレー アウトして選択できません。 Standard プランでもカスタム ドメインを設定していない場合は、以下のエラーが出るので、キチンとカスタム ドメインは設定しましょう。

f:id:horihiro:20220119161956p:plain

Azure CLI で有効化する場合は、事前に enterprise-edge という拡張が必要になるので、事前に下記コマンドでインストールしておきましょう。

az extension add -n enterprise-edge

確認してみる

まず Enterprise-grade edge 有効化前の名前解決結果です。 f:id:horihiro:20220119162601p:plain SWA に設定したカスタム ドメイン (swa-std.■■■.net) に対して nslookup を実行すると、

  1. SWA の既定のドメイン
  2. SWA 用の TM
  3. どこかの Web Apps ( .azurewebsites.net ドメインなので)
    (以下は Web Apps の名前解決の流れ)

という順序で CNAME を追っていき、最終的にどこか (恐らく最寄りの) の Web Apps の IP アドレスに辿りつきます。

次に、Enterprise-grade edge を有効化して、同じカスタム ドメインに対して nslookup を実行してみます。

f:id:horihiro:20220119170656p:plain

  1. SWA の既定のドメイン
  2. マネージドの AFD
  3. AFD 用の TM
  4. どこかのエッジ サーバー

という名前解決の流れになり、AFD が提供するエッジ サーバーの IP アドレスに辿りつくわけです。

実際にここで得られた IP アドレスを、Azure の IP アドレスリストと照合してみると、AzureFrontDoor.Frontend というタグが付いたグループに含まれることが確認できるので、興味のある方はこちらから。

www.microsoft.com

# が、どのリージョンかまでは追えなかった。

価格

最後のお値段ですが、ひと月&一アプリ当たり $17.52 課金されるようです。

f:id:horihiro:20220120083155p:plain

azure.microsoft.com

おまけ

Enterprise-grade edge の有効/無効はポータルから何度もできますが、無効化した直後に SWA からカスタム ドメインを削除すると、一時的にロックに近い状態になります。
こうなると、1時間ほど再有効化ができなくなるのでご注意ください。
# これでリソース壊しかけました