RBACで割り当てられたロールと対象者のメアドを取得したいみたいな話があったので。
@kosmosebi メール送信先のアカウント名のリストを取りたかったんです。諦めてARMからロール情報取る方法を検討してみます。
—
帝国兵 (@superriver) April 22, 2019
詳細は分かりませんけど、コード書きのリハビリにざっと作ってみました。特に深く考えずダラダラ書いたので綺麗ではないです。
書いてて思ったけどFunctions要素はあんまりないです。その1はRBACで割り当てられている一覧をARMのREST APIを叩いてとってきてMS Graph APIでユーザー情報を取得します。
その1はユーザーのみ、重複あっても気にせず取得してるのでちょっと効率悪い感じ。
その2はGraph APIでディレクトリオブジェクトの情報をとってきてるのでグループの情報も取れます。ただコード的には泥臭い感じ。というよりユーザー・グループなど明確に処理を分けたらもう少し綺麗になるかも。dynamicのままJSON扱うのが嫌になってきたのですが何か間違ってるかもしれない。
割り当てられたロールの情報などはARMのMicrosoft.Authorization/roleAssignmentsを呼べば取れます。principalIdに割り当てられたオブジェクト(ユーザーやグループ、AppのオブジェクトID)が、roleDefinitionIdに割り当てられたロールのIDが入ってます。ロールの表示名などが欲しければARMのそのIDに対してGet投げればとれます。
割り当てられたオブジェクトの情報はユーザーやグループであればGraph APIの/directoryOjbects/getById でとれます。(コードでは省略してますけどtypesでユーザーオブジェクトだけ取れたりできます)
こっちのAPIのほうが複数IDまとめて情報取れるので呼び出し回数少なくていいですね(1000 idまでだったかな)。
Graph APIは /api/<version>/対象/API という感じの作りに対してARMのAPIは慣れるまでちょっと戸惑いますね。(ARMのほうは各リソースプロバイダやリソースそのものがAPIを持ってると思えば割とわかりやすい??)
注意点
今回ARMやGraph APIにアクセスする際の認証情報はAzure ADに登録したアプリケーションを使用するようにしています。テナントID(テナント名)、ClientId(AppId)、ClientSecret(AppSecret)は環境変数などに保存しておきましょう。デバッグ時はsecret.settings.jsonなど適当に作って読み込むようにしています。(gitignoreしておけば十分)
またアプリケーションが適切にARMやGraph APIを呼び出せるようにAPIのアクセス許可を追加しておきます。
例:

※設定後は忘れず管理者の同意を与えますボタンでアクセス許可を承認しておきます。
またARMのAPIはRBACが有効なのでサブスクリプションや対象のリソースに設定したアプリケーションが適切な権限を持っている必要があります。今回はサブスクリプションに閲覧者ロールで追加しておけば十分でしょう。各リソースなどのアクセス制御(IAM)でロールを割り当てておきます。Managed Identitiesを使って行う場合も同様です。(※ちなみにManaged Identitiesは対象リソース(API)にGraph APIが入ってなかったので今回は考慮していない)
サービスの権限ではなく利用者の権限で実現したい
サービスのアカウント(アプリケーション)でするのもいいけど利用者自身の権限で実施したいとかありますよね多分。
一番簡単な手法はFunctionsであればAuthentication/Authorization(EasyAuth)機能を使う方法があります。EasyAuth使えばFunctionsに認証をポチポチするだけで行えます。設定後、FunctionsのHttpTriggerなどにアクセスする際に認証が必要になり、Function内で利用者のAccess Tokenが取得できます。ただ既定のままだと自身の簡単なプロファイルなどは取れますけどARMやGraph APIにアクセスする権限は含まれてないのでエラーになります。
もしFuntion内からアクセスするAPIがARMだけなど1つだけであればARM Explorerで追加の設定を行うことで権限を追加できます。
additionalLoginParamsにresourceとして対象のAPI(FQDN)を追加すればそのAPIに対してアクセスできるAccess TokenがX-MS-TOKEN-AAD-ACCESS-TOKENリクエストヘッダーに入ります。あとはこのトークンをBearerとしてAuthorizationヘッダーに付与すればOK。
もし2つ以上のAPI(今回のようにARMとGraph API)にアクセスしたい場合は上記の方法は使えません。(使ってもいいけど指定できるのはどちらか1つだけ)
対処方法は面倒ですが、X-MS-TOKEN-AAD-REFRESH-TOKENヘッダーに含まれるRefresh Tokenを使ってそれぞれのAPI用のAccess Tokenを得る必要があります。(ドキュメントと違ってclient_id、client_secretパラメーターも必要)
例えばこんな感じのメソッドを用意して
private static async Task<string> GetBearerToken(string resource, string clientId, string clientSecret, string refreshToken) { var request = new HttpRequestMessage() { Method = HttpMethod.Post, RequestUri = new Uri("https://login.microsoftonline.com/common/oauth2/token") }; request.Content = new FormUrlEncodedContent(new Dictionary<string, string> { { "grant_type", "refresh_token" }, { "refresh_token", refreshToken }, { "resource", resource }, { "client_id", clientId }, { "client_secret", clientSecret } }); var res = await Client.SendAsync(request); var body = await res.Content.ReadAsStringAsync(); var bodyjson = Utf8Json.JsonSerializer.Deserialize<dynamic>(body); return "Bearer " + bodyjson["access_token"]; }
それぞれのAPIを指定して呼び出してBearerトークンを取得します。
var easyauthtoken = req.Headers["X-MS-TOKEN-AAD-REFRESH-TOKEN"][0]; var token = await GetBearerToken( "https://management.azure.com/", "<ClientID>", "<ClientSecret>", easyauthtoken );
ここで指定するClientId、ClientSecretはEasyAuthを設定した際に作成されるものになります。ARM Explorerを使って取得するなりAzure ADから参照(Secretは見れないので追加するなり)しましょう。

Managed Identities使う場合も同じようなことをしてくれているはずですが残念ながらGraph APIが…
EasyAuthをローカルデバッグしたい
EasyAuthはAzure上で利用できる機能です。デバッグするにはちょっと面倒くさいですね。お手軽にするにはFunctions Proxiesとngrokを使います。
- ローカル上でFunctionsを実行します。
- ngrokを起動してローカルFunctionsのポートにトンネルするようにします。
- FunctionsのProxiesを追加してngrokに飛ばすように設定します。
- FunctionsにEasyAuthを設定します。

後はFunctionsのProxiesのURLにアクセスすればEasyAuthによる認証が走ってその後ローカルでデバッグしているFunctionsにアクセスがきます。便利ですね。