AzureのStorage AccountアクセスといえばConnection Stringですが、Managed Service Identity (MSI) によるAzureAD認証が可能です。(2019/3/25にGAしたはず.... あれ?)
ここでは、Storage AccountではなくMSIを使ったAzure Functionsからのアクセスについてみてみます。
概要
Azure FunctionsのBindingでのMSI認証はできない。
StorageCredentialsを作ってから、CloudBlobClient/CloudQueueClientを作ることになる。
ローカルのMSI認証は現在バグあり。
なぜMSIなのか
MSIを使うことで認証情報をいちいちStorage Accountからひっぱてきて参照可能な形で渡す必要がなくなります。 KeyVaultを使うにしても、AppSettingsを使うにしてもConnectionStringsを埋めるのは嫌ですし、IAM = RBAC = AzureADの認証下でアプリケーションのアクセスが制御されるのは望ましいでしょう。
とくにTerraformを使って構成している場合は、MSIの有効化、IAMでのStorageへの割り当てまで構成時点で完了するのでアプリケーションから見ると透過的に扱えてより効果的でしょう。
AppServiceでMSIを使う
AppService (AzureFunctions) でMSIを使うには、SystemIdentityを有効にします。
Azure Portal で設定する
Azure Portalだと「アクセスを持ちたい側でMSIを設定」「アクセスされる側のIAMでロール設定」を行います。
まずは、Function AppでMSIを設定します。


これでAzureADのAppRegistrationにアプリケーションが登録されたので、アクセスされるStorage AccountのIAMでアクセス権限を設定してあげます。

IAMはSubscription > Resource Group > Resourceで継承されるので、そのResourceに限定したいならResourceのIAMでロール設定すればokです。もしResourceGroup全体で利用したいなら、 Resource GroupのIAMでルール設定すればokです。
Terraform で構成する
こんなことをやっていたら時間がなくなるので、TerraformでFunctionAppの作成からロール設定をします。
https://gist.github.com/guitarrapc/e54e417361d8a41d9e87eedb7bb52530
TerraformでMSIを設定するポイントについて説明します。MSIを使う側であるazurerm_function_appでMSIを有効化するのはidentityです。これはVMやACIも変わらないのでまず設定するといいでしょう。
identity = { type = "SystemAssigned" }
続いて、azurerm_role_assignmentを使ってIAMを設定します。ここではわかりやすいように、Resource Groupに対してContributerロールを設定しています。AzureRMの返り値的に、principal_idがlookup必須なのはあんまりイケテマセンが公式です。
resource "azurerm_role_assignment" "main" { scope = "${data.azurerm_resource_group.current.id}" role_definition_name = "Contributor" principal_id = "${lookup(azurerm_function_app.main.identity[0], "principal_id")}" }
さぁこれでMSIの準備はできました。
AzureFunctions でMSIを使ってStorage Account にアクセスする
前回の記事で、StorageAccountのConnection Stringを使って認証をとりましたが、これをMSIに切り替えます。
とはいえ、MSIはAzure上のリソースで利用可能な他、ローカルでもaz loginやVisual StudioのOption > AccountからAzureに接続していれば利用できると思いきや、Storage Accountに関しては現状動かないです。
認証ヘッダがうまく動いていないので、直るまでローカルはConnection Stringにバイパスします。バイパスはAzure環境かどうかをWebSITE_INSTANCE_ID環境変数でチェックして分岐することにしましょう。
https://gist.github.com/guitarrapc/2c556d64e93c1d08f58242313f8c3563
前回のコードから、CloudQueueClientの取得部分をMSIへ変えるようにしてみます。変更箇所はCloudQueueClientの作成部分だけです。
https://gist.github.com/guitarrapc/661d4161655c44b9183060994c89d5f3
MSIから取得するときのポイントは、3つあります。
AzureServiceTokenProviderを使って認証TokenのProvider経由で認証を作る- MSIは認証プロバイダー経由で認証を作る
azureServiceTokenProvider.GetAccessTokenAsync("アクセストークンの請求先")でURIを指定する- KeyVaultなら
https://vault.azure.net/、Resource Groupの操作ならhttps://management.azure.com/、Storageの操作ならhttps://storage.azure.com/ - 参照: Azure Services that support managed identities for Azure resources | Microsoft Docs
- KeyVaultなら
new CloudQueueClient(new StorageUri(new Uri($"https://{storageAccountName}.queue.core.windows.net")), storageCredentials);のように、URIとCredentialを指定してクライアントを作成する- ConnectionStringを使っている時は接続先のStorageAccountが明示されていたので、
account.CreateCloudQueueClient();と書けましたが、MSIではStorageAccountが不明なので接続先が作れない - このため、StorageCredentialからStorageAccountを作るのではなく、URIでアクセス先のStorageAccountNameと一緒に指定してあげる
- ConnectionStringを使っている時は接続先のStorageAccountが明示されていたので、
MSIがローカルで使えるようにバグ修正されるまで、Azure環境とローカルで分岐してあげます。
// connect to Azure Storage var queueClient = context.IsAzureEnvironment() ? await CreateQueueClientAsync("YOUR_STORAGE_ACCOUNT_NAME") : CreateQueueClient(Environment.GetEnvironmentVariable("queue_storage_connection_string"));
ということでMSIに対応したコード全体像です。
https://gist.github.com/guitarrapc/392b5a42a85b3c51faecfa97ae3bdfe1
まとめ
MSIを使えるなら使いましょう。ConnectionStringとは使うのやめましょ。
MSI、Azureの仕組みは分かりやすいです。一方で、アプリからの利用が分かりにくいというか整理されたドキュメントがないので認証先のドキュメント把握、仕組みの整理に手間取ったです。
余談
2019/3/25にStorage Account (Blob, Queue) のAzureAD認証がGAしたはずの記事が出ているのですが、現在アクセスできません。
これがキャッシュです。
ブログ一覧にもないのと、アクセス先URIでもPreviewになっているので、GAキャンセルでまだPreviewっぽい?
Azure Services that support managed identities for Azure resources | Microsoft Docs
Ref:
仮想マシン上で Azure リソースのマネージド ID を使用してアクセス トークンを取得する方法 | Microsoft Docs
Azure AD Authentication with Azure Storage + Managed Service Identity - Joonas W's blog
Local MSI Login using AAD account · Issue #557 · Azure/azure-libraries-for-net · GitHub