Azure の Storage Account アクセスといえばConnection String ですが、Managed Service Identity (MSI) による AzureAD認証が可能です。(2019/3/25 に GAしたはず.... あれ?)
ここでは、Storage Account ではなく MSI を使ったAzure Functions からのアクセスについてみてみます。
目次
TL;DR
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の作成からロール設定をします。
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環境変数でチェックして分岐することにしましょう。
前回のコードから、CloudQueueClient の取得部分をMSIに変えるようにしてみます。変更箇所はCloudQueueClientの作成部分だけです。
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/
です。*1 - 参照: 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に対応したコード全体像です。
まとめ
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
*1:びっくりするぐらいわかりにくい。