Azure Functions にマネージド ID を割り当ててユーザー委任 SAS トークンを発行する


やりたいこと

Azure Storage Account に一時的なアクセス権限を与える SAS トークンを発行するには、ストレージアカウントのキーをベースにするのではなく、user delegation SAS (ユーザー委任 SAS) がセキュリティ的に良さそうです。しかし、クライアントが Raspberry Pi など Azure 上にない場合は、ユーザー委任 SAS を発行するのにサービスプリンシパルの設定がややこしそうに思いました。

そこで、Azure Functions に マネージド ID を割り当ててユーザー委任 SAS トークンを発行できるようにしてみました。これにより、Azure Functions を呼び出すことで発行された SAS を取得できます。今度は Azure Functions 側のアクセス管理が問題になりますが、そこはまた別途検討しようと思います。

Azure Functions 側のコード

ユーザー委任 SAS を発行するための公式ドキュメントが .NET, Azure CLI, PowerShell しか見つからなかったため、今回は PowerShell のドキュメント に沿いました。なお、Azure Functions の Cold start の影響なのか、呼び出し間に時間が空くと応答に数十秒かかることもありました。いずれ他の言語でも試してみたいと思います。

using namespace System.Net

# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)

# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request. Create SAS Uri."

$StorageAccountName = "<お使いのストレージアカウント名>"
$ContainerName = "<お使いのコンテナ名>"
$BlobName = $Request.Body.fileName

# https://docs.microsoft.com/ja-jp/azure/storage/blobs/storage-blob-user-delegation-sas-create-powershell
$ctx = New-AzStorageContext -StorageAccountName $StorageAccountName -UseConnectedAccount

# https://docs.microsoft.com/ja-jp/powershell/module/az.storage/new-azstorageblobsastoken?view=azps-5.7.0#example-2--generate-a-blob-sas-token-with-life-time
$StartTime = Get-Date
$EndTime = $StartTime.AddHours(2.0)

if ($BlobName)
{
    $Uri = New-AzStorageBlobSASToken -Context $ctx `
        -Container $ContainerName `
        -Blob $BlobName `
        -Permission racwd `
        -StartTime $startTime `
        -ExpiryTime $EndTime `
        -FullUri
}
else
{
# Blob 単位の SAS を発行したい場合
# https://docs.microsoft.com/ja-jp/azure/storage/blobs/storage-blob-user-delegation-sas-create-powershell#create-a-user-delegation-sas-for-a-blob
    $Uri = New-AzStorageContainerSASToken -Context $ctx `
        -Name $ContainerName `
        -Permission racwdl `
        -ExpiryTime $EndTime `
        -FullUri
}

# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = [HttpStatusCode]::OK
    Body = "{`"url`":`"" + $Uri + "`"}"
})

マネージド ID の割り当て

公式ドキュメント: ユーザーの委任 SAS の作成 - RBAC を使用してアクセス許可を割り当てる
に記載があるように、generateUserDelegationKey 権限および、発行した SAS に許可したい操作権限を持つ Azure ロールを割り当てる必要があります。

今回は組み込みロールの中から ストレージ BLOB データ共同作成者 を選びました。お使いのストレージアカウントに対して割り当てます。

アプリファイルの設定

前述のコードを利用するための設定が必要です。

requirementes.psd1 では Az モジュールを使えるようにします。

# This file enables modules to be automatically managed by the Functions service.
# See https://aka.ms/functionsmanageddependency for additional information.
#
@{
    # For latest supported version, go to 'https://www.powershellgallery.com/packages/Az'. 
    # To use the Az module in your function app, please uncomment the line below.
    'Az' = '5.*'
}

profile.ps1 では、Enable-AzureRmAlias を実行するようにします Azure.Storage モジュールはまだ AzureRm なのかな...?

profile.ps1
# Azure Functions profile.ps1
#
# This profile.ps1 will get executed every "cold start" of your Function App.
# "cold start" occurs when:
#
# * A Function App starts up for the very first time
# * A Function App starts up after being de-allocated due to inactivity
#
# You can define helper functions, run commands, or specify environment variables
# NOTE: any variables defined that are not environment variables will get reset after the first execution

# Authenticate with Azure PowerShell using MSI.
# Remove this if you are not planning on using MSI or Azure PowerShell.
if ($env:MSI_SECRET) {
    Disable-AzContextAutosave -Scope Process | Out-Null
    Connect-AzAccount -Identity
}

# Uncomment the next line to enable legacy AzureRm alias in Azure PowerShell.
Enable-AzureRmAlias

# You can also define functions or aliases that can be referenced in any of your PowerShell functions.

実行結果

Azure Functions の「関数の URL を取得」で取得した URL にアクセスして、SAS トークン付きの URL が返ってくることを確認しましょう。

{
  "url": "https://<ストレージアカウント名>.blob.core.windows.net/<コンテナ名>?<SAS トーコン>"
}

こちらのトークンをもとにマネージド ID に割り当たっている権限の操作が可能です。

おわりに

Cold Start の問題をまだ解消できていないですが、次は SORACOM Funk とも組み合わせることでデバイス <=> Azure Functions のアクセス問題を解決してみようかと思います。マネージド ID の使い方がわかってよかったです。