[.NET 6] AWS SSM Parameter StoreをGeneric Hostに登録して使う
概要
AWSでシステムを組む際に、パスワードなど秘匿したいデータを入れておく定番の1つ、Systems Manager Parameter Store です(以降 Parameter Storeと表記)。
ASP.NET Coreを筆頭にGeneric Hostで利用する際に、Parameter StoreをDI・Configurationと組み合わせて"きれいに"使う方法についてまとめます。
Azure Key Vaultを使う方法は公式ドキュメントがあります。Parameter Storeとの合わせ技は情報が乏しく苦労しています。
環境・利用パッケージ
- .NET 6
- NuGet Amazon.Extensions.Configuration.SystemsManager 4.0.0
- 依存: AWSSDK.SimpleSystemsManagement 3.7.12.9
Amazon.Extensions.Configuration.SystemsManager を使う
基本
すぐ上の再掲になりますが、Amazon.Extensions.Configuration.SystemsManager
という拡張パッケージがAWS公式から出ています。本記事はこれの説明に終始します。
以下のような名前の2つの値がParameter Storeには保存されていると仮定します。中身はStringまたはSecureStringです。
/foo/bar/db_password
/foo/bar/slack_webhook_url
基本的な利用法はREADMEの通りです。AddSystemsManager
をします。
Parameter Storeのpath指定については、1個上の階層を指定するようにします。その下のパラメータを全部取得してきてくれます [1]。
using Amazon.Extensions.Configuration.SystemsManager;
using Amazon.SimpleSystemsManagement.Model;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Configuration.AddSystemsManager("/foo/bar/"); // !!!
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.MapControllers();
app.Run();
取得できたかどうか見てみます。デバッガで見てもログに出しても何でも良いですが、以下はGET /から文字列で返してしまう雑な例です。得られた値を見ると、Parameter Storeのキーの最後のスラッシュ以降の部分が名前になっているのがわかります。
app.MapGet("/", () =>
string.Join("\n", builder.Configuration.AsEnumerable().Select(kv => $"{kv.Key} => {kv.Value}")));
/*
windir => C:\Windows
VSSKUEDITION => Community
(中略)
db_password => hogehoge
slack_webhook_url => piyopiyo
*/
AWS認証設定
上記はAWSのcredentialsに関することを何もしていないので、特にローカル環境での開発では動かしにくいケースがあるかもしれません。
credentialsの面倒を見る方法はおそらく3通りあります。
1. 環境変数
環境変数を何らかの方法で事前に設定する、AWS利用者にはおなじみの方法です。
- ストレートに鍵の内容を
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
に設定しておく
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-envvars.html - プロファイルを定義しておいた上で
AWS_PROFILE
を指定する https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
2. appsettings.jsonに書く
以下のような"AWS"
セクションを作っておくと、自動で読み取ってくれます。他のAWSSDK.NET 各種でも同様に使える流儀です。https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-netcore.html
{
"AWS": {
"Profile": "development",
"Region": "ap-northeast-1"
}
}
3. C#コードで指定
運用上おすすめしませんが、AWSCredentials
オブジェクトを流し込むこともできるのでなんでもでき最後の手段です。
builder.Configuration.AddSystemsManager(config =>
{
config.Path = "/foo/bar/";
config.AwsOptions = new AWSOptions
{
Credentials = new InstanceProfileAWSCredentials(),
Profile = "development",
Region = RegionEndpoint.APNortheast1
};
});
モデルにバインド (POCOにマッピング)
以上でParameter Storeの値を個別に得ることはできます。
もしモデルにバインドする場合は、以下のようにすると良さそうでした。
builder.Configuration.AddSystemsManager(config =>
{
config.Path = "/foo/bar/";
config.Prefix = "SSM";
});
// DIに登録する場合
builder.Services.Configure<SsmParameters>(builder.Configuration.GetSection("SSM"));
// その場でマッピングした値が欲しい場合
var model = builder.Configuration.GetSection("SSM").Get<SsmParameters>();
public record SsmParameters
{
[ConfigurationKeyName("db_password")]
public string DbPassword { get; set; } = null!;
[ConfigurationKeyName("slack_webhook_url")]
public string SlackWebhookUrl { get; set; } = null!;
}
Prefix
を指定することで、Parameter Storeから入ってきた設定値名に一括でプレフィックスが付きます。今回の場合はSSM:db_password
のようになります。:
または__
はセクションの区切りと見なされます(参考)。
Parameter Storeでの命名規則とC#での命名規則がずれる場合も、ConfigurationKeyNameAttribute
によりマッピング可能です [2]。
以上のようにConfigureで登録した値は、いつも通りのDIの感じで利用できます。
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
[ApiController]
[Route("[controller]")]
public class FooController : ControllerBase
{
public FooController(IOptions<SsmParameters> ssmParameters)
{
...
Configureしなかった場合も、個別に値を取り出すことができます。これもいつも通りです。
public class FooController : ControllerBase
{
public FooController(IConfiguration configuration)
{
string dbPassword = configuration["SSM:db_password"];
他の設定値プロバイダを優先させる
ASP.NET Coreでは、環境変数やappsettings.jsonのプロバイダをデフォルトで読みに行くようになっており、今回の AddSystemsManager
は最後に付け足した格好になっています。
(あまりないかもしれませんが、)もし環境変数等の優先度を後にする(環境変数などでParameter Storeの値を上書きする)場合は、恒例のAddXXX拡張メソッドではなく素の書き方をして先頭に挿入するのが楽です。
ただし素の書き方をする場合、AwsOptions
が省略不可です。デフォルトの挙動を期待したければ以下のようにします。
// 環境変数で上書きしたい
Environment.SetEnvironmentVariable("SSM__db_password", "dummydummy");
// 先頭にSSMのを挿入
builder.Host.ConfigureAppConfiguration((hostingContext, config) =>
{
config.Sources.Insert(0, new SystemsManagerConfigurationSource
{
Path = "/foo/bar/",
Prefix = "SSM",
AwsOptions = AwsOptionsProvider.GetAwsOptions(config)
});
});
ほかにも、config.Sources.Clear();
してから自分で必要なものを必要な順でAddしていく方法等もありです。
活用シーンの例: 環境で分岐
以下はそこそこありそうなシーンです。
- 本番環境ではParameter Storeを使う
- 開発環境では環境変数やローカルのSecretManagerを使う [3]
こんな感じになりそうです。ここまでの総まとめです。
プロバイダ両者で、設定値のキーは揃えるように注意します。Parameter Storeで /foo/bar/baz
というキーならば、それに相当する環境変数では SSM__baz
という名前にしておく、といった要領です。
if (builder.Environment.IsDevelopment())
{
// ASP.NET Coreの場合はデフォルトでやってくれるので実際は指定不要
// builder.Configuration.AddUserSecrets(...);
// builder.Configuration.AddEnvironmentVariables();
}
else
{
builder.Configuration.AddSystemsManager(config =>
{
config.Path = "/foo/bar/";
config.Prefix = "SSM";
});
}
builder.Services.Configure<SsmParameters>(builder.Configuration.GetSection("SSM"));
Secret Managerの参考: https://learn.microsoft.com/ja-jp/aspnet/core/security/app-secrets?view=aspnetcore-6.0&tabs=windows
-
この挙動のため、付与する権限として ssm:GetParametersByPath を要します。 ↩︎
-
System.Text.Jsonの
[JsonPropertyName]
は効きません ↩︎ -
AWSのSecrets Managerのことではありません ↩︎
Discussion