[AWSSDK.NET] AssumeRoleで得る権限を自動更新する
概要
AWSのAssumeRoleにより一時的なCredentialsを得られますが、既定で1時間、最大でも12時間で期限が切れてしまいます。
AssumeRoleを使用して12時間を超えて動作するプログラムを実装する際は、ExpirationをこまめにチェックしながらなんとかしてCredentialsを再度取り直す、あまり美しくないお手製実装を書きがちでした。
RefreshingAWSCredentials
を使うことで、すっきり実装することが可能です。
RefreshingAWSCredentials
を継承して自分で書くこともできますが、AssumeRoleについては AssumeRoleAWSCredentials
というズバリのクラスがあります。
AssumeRoleAWSCredentials
の存在を知らずに記事を書き終えたところで、知ってしまいました。供養のため両方のやり方を残しておきます。
参考
なお、Python (boto3) でも同様に備わっています。
環境
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AWSSDK.S3" Version="3.7.410.6" />
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.401.13" />
<PackageReference Include="AWSSDK.Extensions.NETCore.Setup" Version="3.7.301" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
</ItemGroup>
</Project>
AWSSDK.S3
についてはサンプルコードの都合であり、必須ではありません。
RefreshingAWSCredentials を使う場合
RefreshingAWSCredentials を継承するクラス
RefreshingAWSCredentials
クラスは名前空間Amazon.Runtime
、NuGetパッケージはAWSSDK.Core
にあります。本記事の例ではAssumeRole(STS)と共に使用しますが、ほかの用途にも使えます。
RefreshingAWSCredentials
はabstractなので、適宜継承して使います。最低限、GenerateNewCredentialsAsync
メソッドをoverrideするようにします[1]。 初回または期限切れした後の更新時に呼ばれます。Credentialsを取得して、Expiration(期限)を添えて返すように書きます。
using Amazon.Runtime;
using Amazon.SecurityToken;
using Amazon.SecurityToken.Model;
public class AssumeRoleRefreshingAWSCredentials(IAmazonSecurityTokenService stsClient, AssumeRoleRequest assumeRoleRequest) : RefreshingAWSCredentials
{
protected override async Task<CredentialsRefreshState> GenerateNewCredentialsAsync()
{
var assumeRoleResponse = await stsClient.AssumeRoleAsync(assumeRoleRequest).ConfigureAwait(false);
var immutableCredentials = new ImmutableCredentials(
assumeRoleResponse.Credentials.AccessKeyId,
assumeRoleResponse.Credentials.SecretAccessKey,
assumeRoleResponse.Credentials.SessionToken);
return new CredentialsRefreshState(immutableCredentials, assumeRoleResponse.Credentials.Expiration);
}
// デバッグ用: 無くて構わない
public DateTime? Expiration => currentState?.Expiration;
}
このクラスを使い、実際にAssumeRoleによるCredentialsを得るときは、GetCredentialsAsync
メソッドを使用します。[2]
using var stsClient = new AmazonSecurityTokenServiceClient();
var assumeRoleRequest = new AssumeRoleRequest();
using var assumeRole = new AssumeRoleRefreshingAWSCredentials(stsClient, assumeRoleRequest);
var cred = await assumeRole.GetCredentialsAsync();
利用例
using Amazon;
using Amazon.Runtime.CredentialManagement;
using Amazon.SecurityToken;
using Amazon.SecurityToken.Model;
var stsClient = new AmazonSecurityTokenServiceClient();
var assumeRoleRequest = new AssumeRoleRequest
{
DurationSeconds = 900, // 最小
RoleSessionName = "Session1",
RoleArn = "arn:aws:iam::123456789012:role/foo-role"
};
using var refreshingCredentials = new AssumeRoleRefreshingAWSCredentials(stsClient, assumeRoleRequest);
// 1秒ごとに権限の状態を表示する
while (true)
{
var currentCredentials = await refreshingCredentials.GetCredentialsAsync().ConfigureAwait(false);
Console.WriteLine("{0} | Expiration={1}, SecretKey={2}", DateTime.Now, refreshingCredentials.Expiration, currentCredentials.SecretKey);
await Task.Delay(1000).ConfigureAwait(false);
}
1秒ごとに、現在の権限のExpirationとSecretKeyを表示する(ちょっと危ない)コードです。DurationSecondsの最小値は900秒なので15分は我慢して待ちます。15分後、以下のようにExpirationが延びてSecretKeyが切り替わるのを観測できるはずです。
(前略)
2024/12/17 23:39:43 | Expiration=2024/12/17 23:39:48, SecretKey=dummyftXCaxQ9VsJYunEdVyY+AbGJH25LPKDUMMY
2024/12/17 23:39:44 | Expiration=2024/12/17 23:39:48, SecretKey=dummyftXCaxQ9VsJYunEdVyY+AbGJH25LPKDUMMY
2024/12/17 23:39:45 | Expiration=2024/12/17 23:39:48, SecretKey=dummyftXCaxQ9VsJYunEdVyY+AbGJH25LPKDUMMY
2024/12/17 23:39:46 | Expiration=2024/12/17 23:39:48, SecretKey=dummyftXCaxQ9VsJYunEdVyY+AbGJH25LPKDUMMY
2024/12/17 23:39:47 | Expiration=2024/12/17 23:39:48, SecretKey=dummyftXCaxQ9VsJYunEdVyY+AbGJH25LPKDUMMY
!ここで変わる! 2024/12/17 23:39:49 | Expiration=2024/12/17 23:54:19, SecretKey=y6ZktjBLkY+DUMMYDUMMYDUMMY+bfAnKR88crPYg
2024/12/17 23:39:50 | Expiration=2024/12/17 23:54:19, SecretKey=y6ZktjBLkY+DUMMYDUMMYDUMMY+bfAnKR88crPYg
2024/12/17 23:39:51 | Expiration=2024/12/17 23:54:19, SecretKey=y6ZktjBLkY+DUMMYDUMMYDUMMY+bfAnKR88crPYg
2024/12/17 23:39:52 | Expiration=2024/12/17 23:54:19, SecretKey=y6ZktjBLkY+DUMMYDUMMYDUMMY+bfAnKR88crPYg
2024/12/17 23:39:53 | Expiration=2024/12/17 23:54:19, SecretKey=y6ZktjBLkY+DUMMYDUMMYDUMMY+bfAnKR88crPYg
(続く...)
AssumeRoleAWSCredentialsを使う場合
こちらは継承不要でいきなり使えます。と言うか、AssumeRoleAWSCredentials
はRefreshingAWSCredentials
を継承しており、つまり前述の実装をやってくれている次第です。そう考えると以下に出てくる引数の意味も全て理解できると思います。
RefreshingAWSCredentials
を継承しているということで、使い方は前述の自前継承版とほとんど同様です。
// STSのために使う権限。InstanceProfileは一例。
var credentials = new InstanceProfileAWSCredentials();
using var assumeRoleCredentials = new AssumeRoleAWSCredentials(
credentials,
roleArn: "arn:aws:iam::123456789012:role/foo-role",
roleSessionName: "Session1",
options: new AssumeRoleAWSCredentialsOptions
{
DurationSeconds = 900,
});
// 既定で「15分」がセットされており、DurationSecondsとあわせて適宜設定すること
assumeRoleCredentials.PreemptExpiryTime = TimeSpan.FromSeconds(30);
RefreshingAWSCredentials
の機能として、PreemptExpiryTimeプロパティを指定することで、真のExpirationより少し早く更新するよう仕向けることができます。AssumeRoleAWSCredentials
の場合、PreemptExpiryTime
には既定で「15分」がセットされており、本記事のようにDurationSeconds=900にしていると残り時間は差し引きゼロとなり、つまり毎秒更新を要求してしまいます。実際はDurationをもっと長く取るでしょうから問題にはなりにくいですが、このあたりは注意します。
Generic HostでのAssumeRole活用
DIコンテナとAWSSDK.NETを共に利用するときは、AWSSDK.Extensions.NETCore.Setup の活用が非常におすすめです。
appsettings.json に以下のように書けば、AssumeRoleによるCredentialsを使ってくれるようになります。
{
"AWS": {
"Region": "ap-northeast-1",
"SessionName": "MySession",
"SessionRoleArn": "arn:aws:iam::123456789012:role/foo-role"
}
}
using System.Reflection;
using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;
using Amazon.S3;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Configuration.AddJsonFile("appsettings.json", optional: false);
builder.Services.AddAWSService<IAmazonS3>();
var app = builder.Build();
var s3Client = app.Services.GetRequiredService<IAmazonS3>();
// s3Clientが持つCredentialsの中身を無理やり見てみる
var credentials = typeof(AmazonS3Client)
.GetProperty("Credentials", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(s3Client);
Console.WriteLine(credentials is AssumeRoleAWSCredentials); // True
コード一番下の通り、クライアントはAssumeRoleAWSCredentials
を持ったインスタンスとなります。本記事の内容を踏まえれば期限切れの心配なく使えると理解でき、便利です。
STSのために使う権限の融通が利きにくい欠点はあるかもしれません。例えば「STSにはProfile名で解決した権限を使い、その他はAssumeRoleで賄いたい」というケースだと、appsettings.jsonのシンプルな記述ではおそらく難しそうで、デフォルトの"AWS"
セクション以外にもう1つセクションを作るとか、C#コードにてAWSOptions
パラメタをうまく取りまわす等の対処になろうかと思います。
Discussion