こんにちは、サイオステクノロジーの佐藤 陽です。
今回は、Azure Key Vaultに保存されている値を、ASP.NET Coreのプロジェクトの構成値に設定する方法についてご紹介します。
正式にはAzure Key Vault構成プロバイダとも呼ばれているようです。
はじめに
APIキーや、DBへの接続情報などをAzure Key Vaultに保管することなどは今や当たり前かと思います。
必要なタイミングで、毎回Key Vaultを参照することもあるかとは思いますが、
今回はアプリ起動時に一度だけ読み込む方法をご紹介します。
基本的には公式ドキュメントに沿って実装していけばいいのですが、
細かいところの実装まで載っていなかったりしたので、そのあたりまでカバーしていきたいと思います。
環境
- .NET 6.0
- ASP.NET Core 6.0
- Azure Web Apps
- Azure Key Vault
実装
WebApps構築
まず始めにASP.NET CoreのプロジェクトをデプロイするWebAppsを準備します。
Freeプランで構わないのでサクッと作ってしまいましょう。
Managed Identityの割り当て
KeyVaultに対してアクセスするため、Managed Identityを割り当てます。
Web Appsの”ID” ブレードから、「システム割り当て済み」のタブで「状態」を「オン」に切り替えます。
すると、固有のIDが割り当てられるのでこれをメモしておきましょう。
これでWeb Appsの設定は完了です。
ちなみに、ユーザー割り当てのManaged Identityでも構築は可能ですが、今回は説明を割愛します。
Key Vault構築
まずAzure Key Vaultのリソースを構築していきます。
こちらもリソース作成自体は特に問題ないと思うので、説明は省略します。
シークレット格納
「シークレット」ブレードからシークレットを登録していきます。
以下の3つを登録しました。
apikey | this is api key |
dbconnection | this is db connection string |
parent–apikey | this is nested api key |
“parent–apikey”の”–“(ハイフン2つ)ですが、階層を表しています。
これをjson形式で表すと
{
"apikey" : "this is apikey",
"dbconnection" : "this is db connection string",
"parent"{
"apikey" : this is nested api key
}
}
という形になります。
アクセスポリシー設定
<※2023/6追記>
本記事投稿時はアクセスポリシーにて設定しましたが、現状RBACの設定が推奨されているようです。
AzurePortal上のKeyVaultの「設定」→「アクセス構成」にて確認可能です。
RBACで設定を行う場合は、
- キー コンテナー シークレット ユーザー
- キー コンテナー閲覧者
の2つのRoleをWebAppsに割り当ててください。
~追記終わり~
Web AppsからKey Vaultにアクセスし、シークレットの内容を取得できるように設定します。
Key Vaultの「アクセスポリシー」から、先ほど作成したWeb Appsを登録していきます。
アクセス許可のタブは、シークレットの「取得」「一覧」が含まれていればOKです。
プリンシパルのタブにおいて、先ほど作成したManaged Identityの値で検索し、Web Appsを指定します。
ここまでできたら、Key Vaultの設定も完了です。
プロジェクトの構築
では実際に実装の方を進めていきます。
今回は、サンプルプロジェクトをベースに実装の方進めていくので
dotnet new webapi
で、サンプルプロジェクトを作成します。
次にProgram.csのファイルに対して修正を加えます。
アプリのビルド前に、まず構成プロバイダーのコレクションにKey Vaultを追加します。
その後、サービスにConfig情報を登録します。
builder.Services.AddSwaggerGen();
//from
if (builder.Environment.IsProduction())
{
//構成プロバイダのコレクションにKeyVaultを追加
builder.Configuration.AddAzureKeyVault(
new Uri("https://{Key-Vault-Name}.vault.azure.net/"),
new DefaultAzureCredential());
}
//サービスに対してConfigを登録
builder.Services.Configure<MySecrets>(builder.Configuration);
//To
var app = builder.Build();
MySecretsとしては以下のように定義します。
namespace KV_ConfigProvider.Models
{
public class MySecrets
{
public string ApiKey { get; set; } = string.Empty;
public string DbConnection { get; set; } = string.Empty;
public Parent Parent { get; set; } = new();
}
public class Parent
{
public string ApiKey { get; set; } = string.Empty;
}
}
あとは追加で1つControllerを追加します。
実装としては
- Controllerのコンストラクタで、DIされたSecrets情報を取得
- Controllerの中で取得したSecrets情報を返す
といった内容です。
using KV_ConfigProvider.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
namespace KV_ConfigProvider.Controllers
{
[ApiController]
[Route("[controller]")]
public class KeyVaultSecretController : ControllerBase
{
private readonly MySecrets _secrets;
public KeyVaultSecretController(IOptions<MySecrets> secrets)
{
_secrets = secrets.Value;
}
[HttpGet(Name = "GetSecrets")]
public string Get()
{
string json = JsonConvert.SerializeObject(_secrets, Formatting.Indented);
return json;
}
}
}
これで実装は完了です。
先ほど作成したWebAppにデプロイし、Postmanで実行してみます。
格納されてるSecrets情報を取得できていますね。
ちなみに、ネストされた“Parent”の中身だけを取得ることも可能です。
そうした場合、Program.csファイルのサービスの登録をセクションを指定行います。
builder.Services.Configure<MySecrets>(builder.Configuration.GetSection("Parent"));
これで取得する対象がParentの中身となります。
この状態で再度APIを実行すると、以下のようなレスポンスに変わります。
parentの子である”ApiKey”に値が入っていることが確認できます。
これでKeyVaultの内容をConfigurationに反映する流れを実装できました。
(※この後の検証のため、セクション指定は外しておいてください。)
開発環境での構成値
これまでの流れで、実際にKeyVaultから値を取得するようにはなりましたが
開発時などはローカルで取得したいケースもあるかと思うので、こちらに関しても確認していきます。
Secret Manager
公式にも説明のあるSecretManagerを使った方法を説明します。
まずは、プロジェクトファイル内に、UserSecretId情報を追記します。
<PropertyGroup>
<UserSecretsId>{GUID}</UserSecretsId>
</PropertyGroup>
この状態で、シークレットを登録するdotnetコマンドを叩きます。
dotnet user-secrets set "{SECRET NAME}" "{SECRET VALUE}"
すると、以下のパスにシークレット情報が登録されています。
%APPDATA%\Microsoft\UserSecrets\{GUID}\secrets.json
今回は、以下のような内容としました。
先ほどと同じ値のプレフィックスに“_dev”を追加します。
{
"apikey": "this is apikey_dev",
"dbconnection": "this is db connection string_dev",
"parent:apikey": "this is nested api key_dev"
}
この状態で、再度アプリをローカルで実行します。
そうすると、_devのプレフィックスが入った値が取得できました。
これで、本番環境と開発環境で切り替えられるようになりますね。
appsettings.json
Secret Manager以外の方法として、appsettings.jsonに値を記載しても読み込めます。
結局、ConfigurationBuilderに値を追加していれば何でもいい認識です。
ちなみにSecret Managerとappsetting.json両方で定義した場合は、Secret Managerの値が優先されます。
この辺りの優先度は、こちらにも書いてあります。
Secret Managerは完全にプロジェクト外で管理してくれるので、誤ってコミットする心配は少なそうです。
ただ、UserIdの設定や編集などが手間なので、appsettings.jsonの方がお手軽感はありますね。
まとめ
今回はKeyVaultに格納されている値をプロジェクトのConfigurationへ反映させる実装を行いました。
KeyVaultを使って安全にシークレット情報を扱っていきましょう!