.NET 8 コンソールアプリから Blob コンテナーへのアクセスを VM のマネージド ID 権限で試してみた

Azure Storage Account へのアクセスはアクセスキーを使うと簡単なのですが、アクセスキーが漏洩すると大変です。そこで今回は、Azure 仮想マシンのマネージド ID 権限で Blob コンテナー一覧を表示する .NET 8 コンソールアプリで試してみました。

## 検証用の Azure Storage Account と コンテナーを 2 つ作成

```bash:bash
prefix=mnrblob
region=japaneast

az group create \
  --name ${prefix}-rg \
  --location $region

az storage account create \
  --name ${prefix} \
  --resource-group ${prefix}-rg \
  --sku Standard_LRS

az storage container create \
  --account-name ${prefix} \
  --name test-container-01 \
  --auth-mode login

az storage container create \
  --account-name ${prefix} \
  --name test-container-02 \
  --auth-mode login

az storage container list \
  --account-name ${prefix} \
  --auth-mode login \
  --output table

Name               Lease Status    Last Modified
-----------------  --------------  -------------------------
test-container-01                  2024-11-29T23:02:28+00:00
test-container-02                  2024-11-29T23:02:47+00:00
```

## ローカルで Blob コンテナー一覧を表示する .NET 8 コンソールアプリを作成

```bash:bash
dotnet new console -o $prefix --use-program-main

cd $prefix

dotnet add package Azure.Storage.Blobs

dotnet add package Azure.Identity

export AZURE_STORAGE_ACCOUNT_NAME=$prefix

code Program.cs
```

```cs:Program.cs
using System;
using System.Threading.Tasks;
using Azure.Identity;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;

class Program
{
    static async Task Main(string[] args)
    {
        try
        {
            string accountName = Environment.GetEnvironmentVariable("AZURE_STORAGE_ACCOUNT_NAME") ?? "";

            var blobServiceClient = new BlobServiceClient(
                new Uri($"https://{accountName}.blob.core.windows.net"),
                new DefaultAzureCredential());

            await foreach (BlobContainerItem container in blobServiceClient.GetBlobContainersAsync())
            {
                Console.WriteLine($"Container name: {container.Name}");
            }

        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception: {ex.Message}");
        }
    }
}
```

## ローカルで動作確認

```bash:bash
dotnet run

Container name: test-container-01
Container name: test-container-02
```

## Azure CLI をログアウトして動作確認

```bash:bash
az logout

dotnet run
```

認証済み情報がなくなったので、以下のメッセージが表示されました。

```txt
Exception: DefaultAzureCredential failed to retrieve a token from the included credentials. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/defaultazurecredential/troubleshoot
- EnvironmentCredential authentication unavailable. Environment variables are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/environmentcredential/troubleshoot
- WorkloadIdentityCredential authentication unavailable. The workload options are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/workloadidentitycredential/troubleshoot
- ManagedIdentityCredential authentication unavailable. No response received from the managed identity endpoint.
- Visual Studio Token provider can't be accessed at /Users/mnr/.IdentityService/AzureServiceAuth/tokenprovider.json
- Please run 'az login' to set up account
- PowerShell is not installed.
- Azure Developer CLI could not be found.
```

## 検証用の Azure 仮想マシンを作成

```bash:bash
az login

az vm create \
  --resource-group ${prefix}-rg \
  --name ${prefix}-vm \
  --os-disk-name ${prefix}-vmOSDisk \
  --image Canonical:ubuntu-24_04-lts:server:latest \
  --size Standard_B1s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --assign-identity \
  --nsg-rule NONE \
  --public-ip-address-dns-name ${prefix}

az network nsg rule create \
  --resource-group ${prefix}-rg \
  --name Allow-SSH \
  --nsg-name ${prefix}-vmNSG \
  --priority 100 \
  --source-address-prefixes $(curl -s inet-ip.info) \
  --destination-port-ranges 22 \
  --access Allow \
  --protocol Tcp
```

## 別ターミナルで Azure 仮想マシンに SSH 接続

```bash:bash
prefix=mnrblob
region=japaneast

ssh azureuser@${prefix}.$region.cloudapp.azure.com

sudo apt-get update

sudo apt-get install -y dotnet-sdk-8.0

dotnet --version

prefix=mnrblob

dotnet new console -o $prefix --use-program-main

cd $prefix

dotnet add package Azure.Storage.Blobs

dotnet add package Azure.Identity

export AZURE_STORAGE_ACCOUNT_NAME=$prefix

cat <<EOF > Program.cs
using System;
using System.Threading.Tasks;
using Azure.Identity;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;

class Program
{
    static async Task Main(string[] args)
    {
        try
        {
            string accountName = Environment.GetEnvironmentVariable("AZURE_STORAGE_ACCOUNT_NAME") ?? "";

            var blobServiceClient = new BlobServiceClient(
                new Uri($"https://{accountName}.blob.core.windows.net"),
                new DefaultAzureCredential());

            await foreach (BlobContainerItem container in blobServiceClient.GetBlobContainersAsync())
            {
                Console.WriteLine($"Container name: {container.Name}");
            }

        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception: {ex.Message}");
        }
    }
}
EOF

dotnet run
```

まだ、マネージド ID 権限を付与していないので、以下のエラーメッセージが表示されました。

```txt
Exception: This request is not authorized to perform this operation using this permission.
RequestId:899451a0-b01e-0016-56b6-423fc8000000
Time:2024-11-29T23:26:32.7651261Z
Status: 403 (This request is not authorized to perform this operation using this permission.)
ErrorCode: AuthorizationPermissionMismatch

Content:
<?xml version="1.0" encoding="utf-8"?><Error><Code>AuthorizationPermissionMismatch</Code><Message>This request is not authorized to perform this operation using this permission.
RequestId:899451a0-b01e-0016-56b6-423fc8000000
Time:2024-11-29T23:26:32.7651261Z</Message></Error>

Headers:
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: 899451a0-b01e-0016-56b6-423fc8000000
x-ms-client-request-id: 6695cfae-b890-4cbe-8d92-9fd46a08e176
x-ms-version: 2025-01-05
x-ms-error-code: AuthorizationPermissionMismatch
Date: Fri, 29 Nov 2024 23:26:31 GMT
Content-Length: 279
Content-Type: application/xml
```

## 元のターミナルで仮想マシンの マネージド ID に Storage Account Contributor 権限を付与

```bash:bash
az role assignment create \
  --role "Storage Account Contributor" \
  --assignee $(az vm show \
    --resource-group ${prefix}-rg \
    --name ${prefix}-vm \
    --query identity.principalId \
    --output tsv) \
  --scope $(az storage account show \
    --name ${prefix} \
    --resource-group ${prefix}-rg \
    --query id \
    --output tsv)
```

## もう一度、仮想マシンで動作確認

```bas:bash
dotnet run

Container name: test-container-01
Container name: test-container-02
```

## 後片付け

```bash:bash
az group delete \
  --name ${prefix}-rg \
  --yes
```

## 参考

https://learn.microsoft.com/ja-jp/azure/storage/blobs/storage-blob-containers-list

https://learn.microsoft.com/ja-jp/cli/azure/vm?view=azure-cli-latest#az-vm-create

https://learn.microsoft.com/ja-jp/dotnet/core/install/linux-ubuntu-install?tabs=dotnet8&pivots=os-linux-ubuntu-2404