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

## やりたいこと

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

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

## Azure Functions 側のコード

ユーザー委任 SAS を発行するための公式ドキュメントが .NET, Azure CLI, PowerShell しか見つからなかったため、今回は [PowerShell のドキュメント](https://docs.microsoft.com/ja-jp/azure/storage/blobs/storage-blob-user-delegation-sas-create-powershell) に沿いました。なお、Azure Functions の Cold start の影響なのか、呼び出し間に時間が空くと応答に数十秒かかることもありました。いずれ他の言語でも試してみたいと思います。

```powershell
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 を使用してアクセス許可を割り当てる](https://docs.microsoft.com/ja-jp/rest/api/storageservices/create-user-delegation-sas#assign-permissions-with-rbac)
 に記載があるように、**generateUserDelegationKey** 権限および、発行した SAS に許可したい操作権限を持つ Azure ロールを割り当てる必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125247/6aa33baf-9bfc-1c26-cee5-71b1a8964898.png)

今回は組み込みロールの中から [ストレージ BLOB データ共同作成者](https://docs.microsoft.com/ja-jp/azure/role-based-access-control/built-in-roles#storage-blob-data-contributor) を選びました。お使いのストレージアカウントに対して割り当てます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125247/894563a9-5e2e-79df-6aec-d5f0c6ea32d3.png)

## アプリファイルの設定

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


**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 が返ってくることを確認しましょう。

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

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

## おわりに

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