From 6e3932ea2ffa4a7b60f23f4a24ab69bcf1c23374 Mon Sep 17 00:00:00 2001 From: "M. Scott Ford" Date: Wed, 29 Jan 2025 12:52:15 -0500 Subject: [PATCH] Adds async methods for assuming roles This is an attempt to fix #3626. --- .../Credentials/AssumeRoleAWSCredentials.cs | 51 +++++++++++++++- .../SharedInterfaces/ICoreAmazonSTS.cs | 23 ++++++++ ...zonSecurityTokenServiceClient.Extension.cs | 58 +++++++++++++++++++ .../SecurityToken/GlobalSuppressions.cs | 1 + 4 files changed, 132 insertions(+), 1 deletion(-) diff --git a/sdk/src/Core/Amazon.Runtime/Credentials/AssumeRoleAWSCredentials.cs b/sdk/src/Core/Amazon.Runtime/Credentials/AssumeRoleAWSCredentials.cs index 69823fafbea8..538bec5d2759 100644 --- a/sdk/src/Core/Amazon.Runtime/Credentials/AssumeRoleAWSCredentials.cs +++ b/sdk/src/Core/Amazon.Runtime/Credentials/AssumeRoleAWSCredentials.cs @@ -20,6 +20,9 @@ using System; using System.Globalization; using System.Net; +#if AWS_ASYNC_API +using System.Threading.Tasks; +#endif namespace Amazon.Runtime { @@ -130,11 +133,57 @@ protected override CredentialsRefreshState GenerateNewCredentials() } } + var credentials = coreSTSClient.CredentialsFromAssumeRoleAuthentication(RoleArn, RoleSessionName, Options); + _logger.InfoFormat("New credentials created for assume role that expire at {0}", credentials.Expiration.ToString("yyyy-MM-ddTHH:mm:ss.fffffffK", CultureInfo.InvariantCulture)); + return new CredentialsRefreshState(credentials, credentials.Expiration); + } + +#if AWS_ASYNC_API +#if NET8_0_OR_GREATER + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", + Justification = "Reflection code is only used as a fallback in case the SDK was not trimmed. Trimmed scenarios should register dependencies with Amazon.RuntimeDependencyRegistry.GlobalRuntimeDependencyRegistry")] +#endif + protected override async Task GenerateNewCredentialsAsync() + { + var region = FallbackRegionFactory.GetRegionEndpoint() ?? DefaultSTSClientRegion; + ICoreAmazonSTS coreSTSClient = GlobalRuntimeDependencyRegistry.Instance.GetInstance(ServiceClientHelpers.STS_ASSEMBLY_NAME, ServiceClientHelpers.STS_SERVICE_CLASS_NAME, + new CreateInstanceContext(new SecurityTokenServiceClientContext {Action = SecurityTokenServiceClientContext.ActionContext.AssumeRoleAWSCredentials, Region = region, ProxySettings = Options?.ProxySettings } )); + if (coreSTSClient == null) + { + try + { + var stsConfig = ServiceClientHelpers.CreateServiceConfig(ServiceClientHelpers.STS_ASSEMBLY_NAME, ServiceClientHelpers.STS_SERVICE_CONFIG_NAME); + stsConfig.RegionEndpoint = region; - var credentials = coreSTSClient.CredentialsFromAssumeRoleAuthentication(RoleArn, RoleSessionName, Options); + if (Options?.ProxySettings != null) + { + stsConfig.SetWebProxy(Options.ProxySettings); + } + + coreSTSClient = ServiceClientHelpers.CreateServiceFromAssembly( + ServiceClientHelpers.STS_ASSEMBLY_NAME, ServiceClientHelpers.STS_SERVICE_CLASS_NAME, SourceCredentials, stsConfig); + } + catch (Exception e) + { + if (InternalSDKUtils.IsRunningNativeAot()) + { + throw new MissingRuntimeDependencyException(ServiceClientHelpers.STS_ASSEMBLY_NAME, ServiceClientHelpers.STS_SERVICE_CLASS_NAME, nameof(GlobalRuntimeDependencyRegistry.RegisterSecurityTokenServiceClient)); + } + + var msg = string.Format(CultureInfo.CurrentCulture, + "Assembly {0} could not be found or loaded. This assembly must be available at runtime to use Amazon.Runtime.AssumeRoleAWSCredentials.", + ServiceClientHelpers.STS_ASSEMBLY_NAME); + var exception = new InvalidOperationException(msg, e); + Logger.GetLogger(typeof(AssumeRoleAWSCredentials)).Error(exception, exception.Message); + throw exception; + } + } + + var credentials = await coreSTSClient.CredentialsFromAssumeRoleAuthenticationAsync(RoleArn, RoleSessionName, Options).ConfigureAwait(false); _logger.InfoFormat("New credentials created for assume role that expire at {0}", credentials.Expiration.ToString("yyyy-MM-ddTHH:mm:ss.fffffffK", CultureInfo.InvariantCulture)); return new CredentialsRefreshState(credentials, credentials.Expiration); } +#endif } } diff --git a/sdk/src/Core/Amazon.Runtime/SharedInterfaces/ICoreAmazonSTS.cs b/sdk/src/Core/Amazon.Runtime/SharedInterfaces/ICoreAmazonSTS.cs index 9a46b152c0bf..7c1c61db0342 100644 --- a/sdk/src/Core/Amazon.Runtime/SharedInterfaces/ICoreAmazonSTS.cs +++ b/sdk/src/Core/Amazon.Runtime/SharedInterfaces/ICoreAmazonSTS.cs @@ -34,6 +34,29 @@ public interface ICoreAmazonSTS /// AssumeRoleImmutableCredentials CredentialsFromAssumeRoleAuthentication(string roleArn, string roleSessionName, AssumeRoleAWSCredentialsOptions options); +#if AWS_ASYNC_API + /// + /// + /// This method is used internally to access the Amazon Security Token + /// service within other service assemblies. + /// Please use AmazonSecurityTokenServiceClient to access the Amazon Security Token + /// service instead. + /// + /// Use Amazon Security Token Service to assume a role. + /// + /// Proxy settings that are required for the HTTPS and STS calls made during the authentication/credential + /// generation process are supported and should have been configured on the STS ClientConfig instance + /// associated with the STS client instance exposing this interface. + /// + /// + /// The Amazon Resource Name (ARN) of the role to assume. + /// An identifier for the assumed role session. + /// Options to be used in the call to AssumeRole. + /// + Task CredentialsFromAssumeRoleAuthenticationAsync(string roleArn, string roleSessionName, AssumeRoleAWSCredentialsOptions options); +#endif + + #if !BCL // In the NETSTANDARD flavors of the SDK ICoreAmazonSTS is declared without CredentialsFromSAMLAuthentication, } // we cannot add a new method to the interface for backward compatibility concerns. diff --git a/sdk/src/Services/SecurityToken/Custom/AmazonSecurityTokenServiceClient.Extension.cs b/sdk/src/Services/SecurityToken/Custom/AmazonSecurityTokenServiceClient.Extension.cs index 30eaddaf2ba7..67c681d54f90 100644 --- a/sdk/src/Services/SecurityToken/Custom/AmazonSecurityTokenServiceClient.Extension.cs +++ b/sdk/src/Services/SecurityToken/Custom/AmazonSecurityTokenServiceClient.Extension.cs @@ -14,6 +14,7 @@ */ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Linq; using Amazon.Runtime; @@ -166,6 +167,7 @@ async Task ICoreAmazonSTS_WebIdentity.Credential /// /// /// + [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types")] AssumeRoleImmutableCredentials ICoreAmazonSTS.CredentialsFromAssumeRoleAuthentication(string roleArn, string roleSessionName, AssumeRoleAWSCredentialsOptions options) { @@ -212,5 +214,61 @@ AssumeRoleImmutableCredentials ICoreAmazonSTS.CredentialsFromAssumeRoleAuthentic throw exception; } } + +#if AWS_ASYNC_API + /// + /// + /// + /// + /// + /// + /// + async Task ICoreAmazonSTS.CredentialsFromAssumeRoleAuthenticationAsync(string roleArn, + string roleSessionName, AssumeRoleAWSCredentialsOptions options) + { + try + { + var request = new AssumeRoleRequest + { + RoleArn = roleArn, + RoleSessionName = roleSessionName + }; + if (options != null) + { + request.ExternalId = options.ExternalId; + request.SerialNumber = options.MfaSerialNumber; + request.TokenCode = options.MfaTokenCode; + request.Policy = options.Policy; + request.SourceIdentity = options.SourceIdentity; + + if (options.DurationSeconds.HasValue) + { + request.DurationSeconds = options.DurationSeconds.Value; + } + + if (options.Tags != null && options.Tags.Count > 0) + { + request.Tags = options.Tags.Select(kv => new Tag() { Key = kv.Key, Value = kv.Value }).ToList(); + } + + if (options.TransitiveTagKeys != null && options.TransitiveTagKeys.Count > 0) + { + request.TransitiveTagKeys = options.TransitiveTagKeys; + } + } + + var response = await AssumeRoleAsync(request).ConfigureAwait(false); + return new AssumeRoleImmutableCredentials(response.Credentials.AccessKeyId, response.Credentials.SecretAccessKey, + response.Credentials.SessionToken, response.Credentials.Expiration); + } + catch (Exception e) + { + var msg = "Error calling AssumeRole for role " + roleArn; + var exception = new AmazonClientException(msg, e); + Logger.GetLogger(typeof(AmazonSecurityTokenServiceClient)).Error(exception, exception.Message); + throw exception; + } + } +#endif } } diff --git a/sdk/src/Services/SecurityToken/GlobalSuppressions.cs b/sdk/src/Services/SecurityToken/GlobalSuppressions.cs index a27db543cc80..938d1e2f3ce0 100644 --- a/sdk/src/Services/SecurityToken/GlobalSuppressions.cs +++ b/sdk/src/Services/SecurityToken/GlobalSuppressions.cs @@ -5,6 +5,7 @@ // a specific target and scoped to a namespace, type, member, etc. [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Scope = "member", Target = "~M:Amazon.SecurityToken.AmazonSecurityTokenServiceClient.Amazon#Runtime#SharedInterfaces#ICoreAmazonSTS#CredentialsFromAssumeRoleAuthentication(System.String,System.String,Amazon.Runtime.AssumeRoleAWSCredentialsOptions)~Amazon.Runtime.AssumeRoleImmutableCredentials")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Scope = "member", Target = "~M:Amazon.SecurityToken.AmazonSecurityTokenServiceClient.Amazon#Runtime#SharedInterfaces#ICoreAmazonSTS#CredentialsFromAssumeRoleAuthenticationAsync(System.String,System.String,Amazon.Runtime.AssumeRoleAWSCredentialsOptions)~System.Threading.Tasks.Task{Amazon.Runtime.AssumeRoleImmutableCredentials}")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Scope = "member", Target = "~M:Amazon.SecurityToken.AmazonSecurityTokenServiceClient.Amazon#Runtime#SharedInterfaces#ICoreAmazonSTS#CredentialsFromSAMLAuthentication(System.String,System.String,System.String,System.TimeSpan,System.Net.ICredentials)~Amazon.Runtime.SAMLImmutableCredentials")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Scope = "member", Target = "~M:Amazon.SecurityToken.AmazonSecurityTokenServiceClient.Amazon#Runtime#SharedInterfaces#ICoreAmazonSTS_WebIdentity#CredentialsFromAssumeRoleWithWebIdentityAuthentication(System.String,System.String,System.String,Amazon.Runtime.AssumeRoleWithWebIdentityCredentialsOptions)~Amazon.Runtime.AssumeRoleImmutableCredentials")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Scope = "member", Target = "~M:Amazon.SecurityToken.AmazonSecurityTokenServiceClient.Amazon#Runtime#SharedInterfaces#ICoreAmazonSTS_WebIdentity#CredentialsFromAssumeRoleWithWebIdentityAuthenticationAsync(System.String,System.String,System.String,Amazon.Runtime.AssumeRoleWithWebIdentityCredentialsOptions)~System.Threading.Tasks.Task{Amazon.Runtime.AssumeRoleImmutableCredentials}")]