Azure RBCA で最小権限を持ったカスタム ロールを探す

# Azure RBAC とカスタムロール
Azure ではリソースの操作権限を管理する仕組みとして [RBAC (Role-Based Access Control)](https://learn.microsoft.com/ja-jp/azure/role-based-access-control/overview) が提供されており、例えば「VM は操作できるが、データベースは操作できない」などの権限分離が可能になっています。
既定のいくつかのロールを使用する以外にも、[カスタムロール](https://learn.microsoft.com/ja-jp/azure/role-based-access-control/custom-roles)を使用する事で「VM の起動停止は出来るが、設定変更は出来ない」といったシステム管理者とオペレータのような役割に合わせた権限の付与が可能になります。

一方で、カスタムロールを作成し、特定の操作を行うための必要な個別の権限を調べるためには、個々の製品の個々の権限を確認し、本質的にはどの権限が必要なのかを調査する必要があります。

この記事では、最小限の権限の探し方を紹介します。

# カスタムロール作成時の心得
カスタムロールの設計方法は組織毎に方法論があると思いますので、ここでは特に触れません。

検証を行う際のヒントとして、以下の点が挙げられます。
- ロールは基本的にリソースグループ単位で設定する
- メモ帳などに権限を記録していきながら操作することを推奨
- 必要に応じて再ログインする (トークンがリフレッシュされるまで、古いロールが使用される)

・ロールは基本的にリソースグループ単位で設定する
カスタムロールは対象となるリソース (スコープ) が設定されており、個々のリソース単位では作成できなくはないですが、ポータルからの操作では出来ないようになっています。
VM では、VM 定義以外にもディスクや NIC など複数のリソースにまたがることがあるため、基本的にはリソースグループを最小として、必要に応じてサブスクリプションレベルで定義する方がベターです。

・メモ帳などに権限を記録していきながら操作することを推奨
RBAC に限りませんが、設定変更は試行錯誤の連続です。いつでも戻れるように「これは成功した」という記録しておくことがベターです。


# 最小限の権限の探し方
方法はいくつかありますが、代表例として以下の 2 つが上げられます。

1. 既存のロールから始める
2. 空の設定から作成する

最終的にどちらも同じ権限になりますので、優劣はありません。
スタートラインがある程度決まる、既存のロールから始める方が方針は決まっているので楽ではあります。

ここでは、「App Service のアプリケーション設定を変更する権限」について調べてみます。

既存のロールを適当に眺めていたところ、[Web サイト共同作成者 (Website Contributor)](https://learn.microsoft.com/ja-jp/azure/role-based-access-control/built-in-roles#website-contributor) というロールがあり、やりたかったアプリケーション設定も出来たので、ここを起点に考えてみます。
Web サイト共同作成者 (Website Contributor) の定義はこのようになっています。
```json
{
    "id": "/subscriptions/d7fd6cbe-****-****-****-************/providers/Microsoft.Authorization/roleDefinitions/becad79c-30f7-4ee2-ab22-42da325d0b25",
    "properties": {
        "roleName": "webapps-custom-role-1",
        "description": "",
        "assignableScopes": [
            "/subscriptions/d7fd6cbe-****-****-****-************/resourceGroups/rg-appsvc-20240101"
        ],
        "permissions": [
            {
                "actions": [
                    "Microsoft.Authorization/*/read",
                    "Microsoft.Insights/alertRules/*",
                    "Microsoft.Insights/components/*",
                    "Microsoft.ResourceHealth/availabilityStatuses/read",
                    "Microsoft.Resources/deployments/*",
                    "Microsoft.Resources/subscriptions/resourceGroups/read",
                    "Microsoft.Support/*",
                    "Microsoft.Web/certificates/*",
                    "Microsoft.Web/listSitesAssignedToHostName/read",
                    "Microsoft.Web/serverFarms/join/action",
                    "Microsoft.Web/serverFarms/read",
                    "Microsoft.Web/sites/*"
                ],
                "notActions": [],
                "dataActions": [],
                "notDataActions": []
            }
        ]
    }
}
```

ほぼ何もないところから始める場合、「何もない権限」は定義できないので、適当に探してきた "Microsoft.Web/sites/Read" 権限だけ付与しておきます。
```json
{
    "properties": {
        "roleName": "webapps-custom-role-2",
        "description": "",
        "assignableScopes": [
            "/subscriptions/d7fd6cbe-****-****-****-************/resourceGroups/rg-appsvc-20240101"
        ],
        "permissions": [
            {
                "actions": [
                    "Microsoft.Web/sites/Read"
                ],
                "notActions": [],
                "dataActions": [],
                "notDataActions": []
            }
        ]
    }
}
```

こうして作成した権限を、検証用の適当なユーザーに割り当てて、そのユーザーで期待した操作ができるかを確認していきます。



## 既存のロールから始める
既存のカスタムロールを使用する場合の方針は、余計な権限をそぎ落とすことになります。
具体的には以下を意味します。
- 要らなそうな項目を外す
- "/*" を書き下す

[Web サイト共同作成者 (Website Contributor)](https://learn.microsoft.com/ja-jp/azure/role-based-access-control/built-in-roles#website-contributor) というロールの権限は以下のようになっていました。
```json
        "permissions": [
            {
                "actions": [
                    "Microsoft.Authorization/*/read",
                    "Microsoft.Insights/alertRules/*",
                    "Microsoft.Insights/components/*",
                    "Microsoft.ResourceHealth/availabilityStatuses/read",
                    "Microsoft.Resources/deployments/*",
                    "Microsoft.Resources/subscriptions/resourceGroups/read",
                    "Microsoft.Support/*",
                    "Microsoft.Web/certificates/*",
                    "Microsoft.Web/listSitesAssignedToHostName/read",
                    "Microsoft.Web/serverFarms/join/action",
                    "Microsoft.Web/serverFarms/read",
                    "Microsoft.Web/sites/*"
                ],
                "notActions": [],
                "dataActions": [],
                "notDataActions": []
            }
        ]
```

Microsoft.Web リソースプロバイダー以外への操作、証明書リソースの操作 (Microsoft.Web/certificates) は不要でしょうと判断して、一気にそぎ落とすと以下のようになります。
"Microsoft.Web/sites/\*" のようにワイルドカードが指定されているので、ここを具体的なリソースに変更する必要がありますが、具体的な権限の一覧は [このドキュメントに列挙されており](https://learn.microsoft.com/ja-jp/azure/role-based-access-control/resource-provider-operations#microsoftweb) "Microsoft.Web/sites/\*"を書き下す必要があります。 

```json
        "permissions": [
            {
                "actions": [
                    "Microsoft.Web/listSitesAssignedToHostName/read",
                    "Microsoft.Web/serverFarms/join/action",
                    "Microsoft.Web/serverFarms/read",
                    "Microsoft.Web/sites/*"
                ],
                "notActions": [],
                "dataActions": [],
                "notDataActions": []
            }
        ]
```

![fig1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/5e92697a-8ac4-85d3-e7e6-9a001efe998b.png)

"Microsoft.Web/sites/\*" に当てはまる権限は上の表のようにたくさんありますが、関係なさそうな権限 (microsoft.web/sites/backups/, microsoft.web/sites/diagnostics/, Microsoft.Web/sites/slots/) を除去していくと以下のような定義になります。
いったんこのカスタムロールで期待した動作になるか確認しましょう。期待した動作にならない場合、何らかの権限を余計に落としている可能性があります (調べ方はこの記事の後の方に書かれています)。
```json
        "permissions": [
            {
                "actions": [
                    "Microsoft.Web/listSitesAssignedToHostName/read",
                    "Microsoft.Web/serverFarms/join/action",
                    "Microsoft.Web/serverFarms/read",
                    "Microsoft.Web/sites/Read",
                    "Microsoft.Web/sites/Write",
                    "Microsoft.Web/sites/config/Read",
                    "Microsoft.Web/sites/config/list/Action",
                    "Microsoft.Web/sites/config/Write",
                    "microsoft.web/sites/config/appsettings/read",
                    "microsoft.web/sites/config/web/appsettings/read",
                    "microsoft.web/sites/config/web/appsettings/write",
                    "microsoft.web/sites/config/web/appsettings/delete",
                    "microsoft.web/sites/config/web/connectionstrings/read",
                    "microsoft.web/sites/config/web/connectionstrings/write",
                    "microsoft.web/sites/config/web/connectionstrings/delete"
                ],
                "notActions": [],
                "dataActions": [],
                "notDataActions": []
            }
        ]
```

![fig2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/89107a28-ef42-d254-d6c8-2acf9f4ce1bc.png)

いったん上記で設定変更できたので、さらに削った結果が以下の通りです。
```json
        "permissions": [
            {
                "actions": [
                    "Microsoft.Web/sites/Read",
                    "Microsoft.Web/sites/config/Read",
                    "Microsoft.Web/sites/config/list/Action",
                    "microsoft.web/sites/config/web/appsettings/read",
                    "microsoft.web/sites/config/web/appsettings/write",
                    "microsoft.web/sites/config/web/appsettings/delete"
                ],
                "notActions": [],
                "dataActions": [],
                "notDataActions": []
            }
        ]
```

ただし、上記権限では問題があり、カスタムロールの動作確認のために再ログインしてみると "Microsoft.Web/sites/write" 権限が必要というメッセージが表示されています。
![fig3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/68111eb2-5513-f862-5ffb-8ff6e5f44082.png)


カスタムロールに "Microsoft.Web/sites/write" を追加して、再ログインして試した結果、正しく書き込めるようになりました。
```json
        "permissions": [
            {
                "actions": [
                    "Microsoft.Web/sites/Read",
                    "Microsoft.Web/sites/write",
                    "Microsoft.Web/sites/config/Read",
                    "Microsoft.Web/sites/config/list/Action",
                    "microsoft.web/sites/config/web/appsettings/read",
                    "microsoft.web/sites/config/web/appsettings/write",
                    "microsoft.web/sites/config/web/appsettings/delete"
                ],
                "notActions": [],
                "dataActions": [],
                "notDataActions": []
            }
        ]
```


上記が最小限の権限のように思えますが、個々の権限を削除して動作を確認していった結果、以下のようになりました。
```json
        "permissions": [
            {
                "actions": [
                    "Microsoft.Web/sites/Read",
                    "Microsoft.Web/sites/write",
                    "Microsoft.Web/sites/config/list/action"
                ],
                "notActions": [],
                "dataActions": [],
                "notDataActions": []
            }
        ]
```

![fig4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/3b67f03f-7566-3b8a-c9d7-9a9fa04579dd.png)

参考ですが、ポータルで操作が行えない場合、開発者ツールなどを使用して、ポータルアプリケーションの応答を確認してみると、以下のような応答が記録されていることがあります。
```json
{
    "responses": [
        {
            "httpStatusCode": 403,
            "headers": {
                "Pragma": "no-cache",
                "x-ms-failure-cause": "gateway",
                "x-ms-request-id": "c915c7a4-c4d4-4511-bd8c-4ff1cbd67f95",
                "x-ms-correlation-request-id": "7a8001b1-8b3a-4c37-a602-702a39705300",
                "x-ms-routing-request-id": "JAPANEAST:20240101T002847Z:c915c7a4-c4d4-4511-bd8c-4ff1cbd67f95",
                "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
                "X-Content-Type-Options": "nosniff",
                "Connection": "close",
                "Cache-Control": "no-cache",
                "Date": "Mon, 01 Jan 2024 00:28:46 GMT"
            },
            "content": {
                "error": {
                    "code": "AuthorizationFailed",
                    "message": "オブジェクト ID が 'f8a3fec1-1a90-4d75-8a7f-cdf0eef42747' のクライアント 'aad-test1@***.onmicrosoft.com' には、スコープ '/subscriptions/d7fd6cbe-****-****-****-************/resourceGroups/rg-appsvc-20240101/providers/Microsoft.Web/sites/***/config/azureStorageAccounts' でアクション 'Microsoft.Web/sites/config/list/action' を実行する認可がないか、スコープが無効です。アクセスが最近許可された場合は、資格情報を更新してください。"
                }
            },
            "contentLength": 593
        }
    ]
}
```
![fig5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/afc502b6-ad8d-2f8c-b73c-bf8766891753.png)



"microsoft.web/sites/config/web/appsettings/" は関係ありそうですが、ポータルの動作としては使用されません。
Azure ポータルの仕組みに関して動作は公開されていないため解説は困難ですが、アップデートは個別の設定値を投入するのではなく「既存の設定を持ってくる → 書き換える → PUT/PATCH でそっくり置き換える」という動きをしています。
![fig6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/9e94473f-d085-d8d5-6fe6-b7701c2eaae5.png)


なので、ここでは App Service の全体に対しての書き換え権限が必要になってきます。
```json
        "permissions": [
            {
                "actions": [
                    "Microsoft.Web/sites/Read",
                    "Microsoft.Web/sites/write",
                    "Microsoft.Web/sites/config/list/Action"
                ],
                "notActions": [],
                "dataActions": [],
                "notDataActions": []
            }
        ]
```

ちなみに REST API の場合、sites/write は使用されず、config/write が使用されるので以下のようになります。
- https://learn.microsoft.com/ja-jp/rest/api/appservice/web-apps/list-application-settings?view=rest-appservice-2022-03-01
- https://learn.microsoft.com/ja-jp/rest/api/appservice/web-apps/update-application-settings?view=rest-appservice-2022-03-01
```json
        "permissions": [
            {
                "actions": [
                    "Microsoft.Web/sites/Read",
                    "Microsoft.Web/sites/config/list/Action",
                    "Microsoft.Web/sites/config/write"
                ],
                "notActions": [],
                "dataActions": [],
                "notDataActions": []
            }
        ]
```



## 空の設定から作成する
初期状態
先のポータル表示と同じく「このセクションを表示または編集するには、読み取りまたは書き込みアクセス許可が必要です」と表示されており、開発者ツールで追ってみると 'Microsoft.Web/sites/config/list/action' 権限が必要というメッセージが出力されていることがわかります。
![fig4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/3b67f03f-7566-3b8a-c9d7-9a9fa04579dd.png)

```json
        "permissions": [
            {
                "actions": [
                    "Microsoft.Web/sites/Read"
                ],
                "notActions": [],
                "dataActions": [],
                "notDataActions": []
            }
        ]
```

エラーメッセージに従って 'Microsoft.Web/sites/config/list/action' を追加した権限が以下です。
```json
        "permissions": [
            {
                "actions": [
                    "Microsoft.Web/sites/Read",
                    "Microsoft.Web/sites/config/list/action"
                ],
                "notActions": [],
                "dataActions": [],
                "notDataActions": []
            }
        ]
```

上記のようにエラーメッセージに従って権限を追加していった結果、以下のようになりました。
```json
        "permissions": [
            {
                "actions": [
                    "Microsoft.Web/sites/Read",
                    "Microsoft.Web/sites/config/list/action",
                    "Microsoft.Web/sites/write"
                ],
                "notActions": [],
                "dataActions": [],
                "notDataActions": []
            }
        ]
```

つまりポータルで変更するための最小限の権限は上記の 3 つが必要、ということになります。

# FAQ
そのうち追記されるかも知れませんが、良く聞く話をまとめておきます。

### アプリケーション設定のように特定の機能だけ操作したいのですが、なぜ全体の権限 (とくに write に関して) が必要になるのでしょうか？

Azure ポータルの動作と ARM の構成の観点で考えてみましょう。

ポータルの動作を開発者ツールで追ってみると分かりますが、batch という API を通じて内部的に REST API を呼び出しています。
batch API は設定の取得と反映の両方で使用されており、ポータル上で設定を変更・保存した際に、変更した設定だけを Azure 側に送信するのではなく、見えている内容を全て Azure に送るという動作をします。
結果として変更したい特定の箇所以外の情報も保存するという動作を行う関係上、それらの write 権限も必要になります。

ARM の観点で見た場合、特定の操作と権限は 1:1 で対応しないためです。
[Azure Resource Explorer](https://resources.azure.com/) でリソースの定義や設定がどうなっているかを確認してみると分かりやすいですが、App Service の場合 Microsoft.web/sites/*/config に複数の定義情報が記録されており、この操作権限が RBAC で指定できる個々の権限に対応しています。
一方で、ポータルのあちこちで行える操作が Microsoft.web/sites/*/config に集約されて記録されるという動作になるため、「特定のチェックボックスだけを操作可能にする」という権限は仕組み上実現できません。

![fig7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349915/a0e1d343-9d83-f449-9e4e-a254051228b0.png)

これらは [ARM テンプレートの定義](https://learn.microsoft.com/en-us/azure/templates/microsoft.web/2022-09-01/sites?pivots=deployment-language-arm-template) からも確認ができます。

オペレーションミスを回避するために、特定のチェックボックスだけを操作可能にするということは有効かも知れませんが、インフラストラクチャの運用上はあまり一般的な機能とは言えないのもまた事実です。