|
var profile = "YOUR_PROFILE_NAME"; |
|
var credentials = await GetSSOProfileCredentials(profile); |
|
|
|
// any operation you want to do with sso credentials. |
|
var s3client = new AmazonS3Client(credentials, RegionEndpoint.APNortheast1); |
|
var buckets = await s3client.ListBucketsAsync(); |
|
foreach (var bucket in buckets.Buckets) |
|
{ |
|
Console.WriteLine(bucket.BucketName); |
|
} |
|
|
|
/// <summary> |
|
/// Get AccessKey from AWS SSO cached file. |
|
/// </summary> |
|
static async Task<AWSCredentials> GetSSOProfileCredentials(string profileName) |
|
{ |
|
// ${HOME}/.aws/config |
|
var configFilePath = Path.Combine(SharedCredentialsFile.DefaultDirectory, "config"); |
|
// ${HOME}/.aws/sso/cache |
|
var ssoFolderPath = Path.Combine(SharedCredentialsFile.DefaultDirectory, "sso", "cache"); |
|
var (accountId, roleName, ssoCacheSeed) = GetSsoProfileValues(configFilePath, profileName); |
|
|
|
var cacheFileName = GetSha1(ssoCacheSeed) + ".json"; |
|
var fullCacheFilePath = Path.Combine(ssoFolderPath, cacheFileName); |
|
if (!File.Exists(fullCacheFilePath)) |
|
throw new FileNotFoundException($"aws sso cache file {fullCacheFilePath} not found, please confirm you have already logged in with 'aws sso login --profile {profileName}'"); |
|
var cacheObject = JsonSerializer.Deserialize<AwsSsoCacheObject>(File.ReadAllText(fullCacheFilePath)); |
|
|
|
if (cacheObject is null) |
|
throw new ArgumentNullException(nameof(cacheObject)); |
|
if (cacheObject.ExpiresAt < DateTime.UtcNow) |
|
throw new InvalidDataException("Obtained expiresAt is past date."); |
|
|
|
using var ssoClient = new AmazonSSOClient( |
|
new AnonymousAWSCredentials(), |
|
new AmazonSSOConfig { RegionEndpoint = RegionEndpoint.GetBySystemName(cacheObject.Region) }); |
|
var getRoleCredentialsResponse = await ssoClient.GetRoleCredentialsAsync(new GetRoleCredentialsRequest |
|
{ |
|
AccessToken = cacheObject.AccessToken, |
|
AccountId = accountId, |
|
RoleName = roleName |
|
}); |
|
|
|
var sessionCredential = new SessionAWSCredentials( |
|
getRoleCredentialsResponse.RoleCredentials.AccessKeyId, |
|
getRoleCredentialsResponse.RoleCredentials.SecretAccessKey, |
|
getRoleCredentialsResponse.RoleCredentials.SessionToken); |
|
|
|
// You can omit on local run. For Role credential fallback. Fargate/Lambda/EC2 Instance. |
|
FallbackCredentialsFactory.CredentialsGenerators.Insert(0, () => sessionCredential); |
|
|
|
return sessionCredential; |
|
|
|
static string GetSha1(string value) |
|
{ |
|
using var sha = SHA1.Create(); |
|
var hashSpan = sha.ComputeHash(Encoding.UTF8.GetBytes(value)).AsSpan(); |
|
return Convert.ToHexString(hashSpan).ToLowerInvariant(); |
|
} |
|
|
|
static (string AccountId, string RoleName, string SsoCacheSeed) GetSsoProfileValues(string configFilePath, string profileName) |
|
{ |
|
var profileFile = new ProfileIniFile(configFilePath, false); |
|
if (!profileFile.TryGetSection(profileName, false, false, out var profileProperties, out _)) |
|
throw new ArgumentNullException($"profile '{profileProperties}' not found in {configFilePath}."); |
|
|
|
var accountId = profileProperties["sso_account_id"]; |
|
var roleName = profileProperties["sso_role_name"]; |
|
|
|
// Get SSO cache file seed. |
|
// * legacy format use sso_start_url to generate sso cache file |
|
// * new format use sso_session name to generate sso cache file (see: https://github.com/aws/aws-sdk-go-v2/blob/24f7e3aaaf4a6e94d2fc05a7ef0fac4bb80b7e01/config/resolve_credentials.go#L211) |
|
if (!profileProperties.TryGetValue("sso_start_url", out var ssoCacheSeed)) |
|
{ |
|
// both legacy and new format is missing |
|
if (!profileProperties.TryGetValue("sso_session", out var ssoSesssion)) |
|
throw new ArgumentNullException($"Both sso_start_url and sso_session missing in profile {profileName}"); |
|
|
|
if (profileFile.TryGetSection(ssoSesssion, true, false, out var ssoSessionProperties, out _)) |
|
{ |
|
ssoCacheSeed = ssoSesssion; // new format |
|
} |
|
} |
|
|
|
return (accountId, roleName, ssoCacheSeed); |
|
} |
|
} |
|
|
|
internal class AwsSsoCacheObject |
|
{ |
|
[JsonPropertyName("startUrl")] |
|
public required string StartUrl { get; init; } |
|
[JsonPropertyName("region")] |
|
public required string Region { get; init; } |
|
[JsonPropertyName("accessToken")] |
|
public required string AccessToken { get; init; } |
|
[JsonPropertyName("expiresAt")] |
|
public required DateTime ExpiresAt { get; init; } |
|
} |