ASP.NET Core ウェブアプリから Azure Key Vault を使う

# 目的・背景
- ASP.NET Core ウェブアプリから Azure Key Vault にアクセスしてシークレットを取得する
    - デバッグ実行でも Azure App Service にデプロイ後でも動作するようにしたい
    - ユーザーシークレットを使ってリポジトリ上に痕跡を残さない
    - Azure リソース間のアクセス制御はマネージド ID を使って簡単に設定
- Microsoft Learn の資料ではいまいち分からずいろいろ試したので自分メモとして書き残す

# 環境
- Visual Studio 2022 17.8.6
- .NET 8
- Azure は 2024/02/04 時点での仕様・表示を参考に執筆

# 手順

## 1. Azure リソースの作成
### Key Vault
1. リソース作成
1. ロールの割り当て
1. シークレットを追加

|名前|値|
|---|---|
|MySecret|My Secret|

### App Service
1. リソースの作成
1. アプリケーション設定（環境変数）の追加
1. マネージド ID の設定
    1. ID で「状態」を「オン」にする
    1. オブジェクト（プリンシパル）ID が設定される
1. Azure ロールの割り当て
    1. 「ロールの割り当ての追加」
    1. 「スコープ」を「Key Vault」に
    1. 「サブスクリプション」を選択
    1. 「役割」を「キーコンテナー シークレット ユーザー」に
## 2. ASP.NET Core アプリの作成
### プロジェクトの作成
1. ASP.NET Core MVC

### ユーザーシークレットの追加
1. プロジェクトを右クリック
1. ユーザー シークレットの管理
1. JSON ファイルを編集
```json
{
  "Azure": {
    "KeyVault": "{作成した Key Vault の名前}"
  }
}
```

### NuGet パッケージの追加
1. Azure.Identity
1. Azure.Security.KeyVault.Secrets

### Program.cs の実装

- 環境変数から Azure Key Vault の名前を取得する
- 名前から URI を作成し、Azure Key Vault へのアクセスを構成する
```diff_c_sharp
// Add services to the container.
builder.Services.AddControllersWithViews();

+ string? keyVaultName = builder.Configuration["Azure:KeyVault"];
+ 
+ builder.Configuration.AddAzureKeyVault(
+         new Uri($"https://{keyVaultName}.vault.azure.net/"),
+         new DefaultAzureCredential());
+
var app = builder.Build();
```

これで IConfiguration からキーコンテナーへアクセスできます。

### Controller 追加
1. Controllers フォルダーを右クリック
1. 追加→コントローラー
1. KeyVaultController とでもしておく

### ViewModel 追加
1. View に渡す文字列をクラスに

```csharp
public sealed class KeyVaultViewModel(
    string keyVaultName,
    string name,
    string secret)
{
    public string KeyVaultName { get; } = keyVaultName;
    public string Name { get; } = name;
    public string Secret { get; } = secret;
}
```

### ViewModel を使って View を表示する
1. 以下のように `KeyVaultController` を実装してみる
```csharp
public class KeyVaultController(
    IConfiguration configuration,
    ILogger<KeyVaultController> logger)
    : Controller
{
    private const string _sectionName = "Azure:KeyVault";
    private const string _secretName = "MySecret";

    private readonly IConfiguration _configuration = configuration;

    private readonly ILogger<KeyVaultController> _logger = logger;

    public IActionResult Index()
    {
        string keyVaultName = _configuration.GetSection(_sectionName).Value ?? string.Empty;
        _logger.LogInformation("AzureKeyVault: {KeyVaultName}", keyVaultName);

        string secret = _configuration.GetSection(_secretName).Value ?? string.Empty;
        _logger.LogInformation("Secret: {Secret}", secret);

        KeyVaultViewModel vm = new(keyVaultName, _secretName, secret);

        return View(vm);
    }
}
```

### View 追加
1. KeyVaultController.Index() を右クリック
1. ビューの追加
1. Razor ビュー -  空
1. こんな感じに
```html
<div class="text-center">
    <h1 class="display-4">KeyVault</h1>
    <p>This is a demo page for Azure Key Vault.</p>
    <table class="table">
        <tbody>
            <tr>
                <td>Key Vault Name</td>
                <td>@Model.KeyVaultName</td>
            </tr>
            <tr>
                <td>Secret Name</td>
                <td>@Model.Name</td>
            </tr>
            <tr>
                <td>Secret Value</td>
                <td>@Model.Secret</td>
            </tr>
        </tbody>
    </table>
</div>
```

# 動作確認

デバッグ実行と App Service のデプロイ両方で動作を確認。

# 参考文献

#### ASP.NET Core での実装

https://learn.microsoft.com/ja-jp/azure/key-vault/general/tutorial-net-create-vault-azure-web-app

https://learn.microsoft.com/ja-jp/aspnet/core/security/key-vault-configuration?view=aspnetcore-8.0

#### マネージド ID と Key Vault の理解

https://milestone-of-se.nesuke.com/sv-advanced/azure/az-managedid-keyvault-service-principal/

#### ロール割り当て周りの操作

https://zenn.dev/microsoft/articles/94f22a95dca9ef