Skip to content

Commit c4612e8

Browse files
committedMar 4, 2025
Updated credential providers to provide Async version for generating credentials.
1 parent b4a9df9 commit c4612e8

24 files changed

+1279
-134
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"core": {
3+
"changeLogMessages": [
4+
"Updated credential providers to provide Async version for generating credentials."
5+
],
6+
"type": "patch",
7+
"updateMinimum": false
8+
},
9+
"services": [
10+
{
11+
"serviceName": "SecurityToken",
12+
"type": "patch",
13+
"changeLogMessages": [
14+
"Added implementation for ICoreAmazonSTS_SAML/ICoreAmazonSTS.CredentialsFromSAMLAuthenticationAsync() and ICoreAmazonSTS.CredentialsFromAssumeRoleAuthenticationAsync()."
15+
]
16+
}
17+
]
18+
}

‎sdk/src/Core/Amazon.Runtime/Credentials/AWSCredentials.cs

-2
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,9 @@ public abstract class AWSCredentials : BaseIdentity
3535
/// </summary>
3636
protected virtual void Validate() { }
3737

38-
#if AWS_ASYNC_API
3938
public virtual System.Threading.Tasks.Task<ImmutableCredentials> GetCredentialsAsync()
4039
{
4140
return System.Threading.Tasks.Task.FromResult<ImmutableCredentials>(this.GetCredentials());
4241
}
43-
#endif
4442
}
4543
}

‎sdk/src/Core/Amazon.Runtime/Credentials/AssumeRoleAWSCredentials.cs

+25-9
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
using System.Net;
2323
using System.Diagnostics.CodeAnalysis;
2424
using ThirdParty.RuntimeBackports;
25+
using System.Threading.Tasks;
2526

2627
namespace Amazon.Runtime
2728
{
@@ -91,13 +92,32 @@ public AssumeRoleAWSCredentials(AWSCredentials sourceCredentials, string roleArn
9192
PreemptExpiryTime = TimeSpan.FromMinutes(15);
9293
}
9394

94-
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026",
95-
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")]
9695
protected override CredentialsRefreshState GenerateNewCredentials()
9796
{
9897
var region = FallbackRegionFactory.GetRegionEndpoint() ?? DefaultSTSClientRegion;
99-
ICoreAmazonSTS coreSTSClient = GlobalRuntimeDependencyRegistry.Instance.GetInstance<ICoreAmazonSTS>(ServiceClientHelpers.STS_ASSEMBLY_NAME, ServiceClientHelpers.STS_SERVICE_CLASS_NAME,
100-
new CreateInstanceContext(new SecurityTokenServiceClientContext {Action = SecurityTokenServiceClientContext.ActionContext.AssumeRoleAWSCredentials, Region = region, ProxySettings = Options?.ProxySettings } ));
98+
ICoreAmazonSTS coreSTSClient = GetSTSClient(region);
99+
100+
var credentials = coreSTSClient.CredentialsFromAssumeRoleAuthentication(RoleArn, RoleSessionName, Options);
101+
_logger.InfoFormat("New credentials created for assume role that expire at {0}", credentials.Expiration.ToString("yyyy-MM-ddTHH:mm:ss.fffffffK", CultureInfo.InvariantCulture));
102+
return new CredentialsRefreshState(credentials, credentials.Expiration);
103+
}
104+
105+
protected override async Task<CredentialsRefreshState> GenerateNewCredentialsAsync()
106+
{
107+
var region = FallbackRegionFactory.GetRegionEndpoint() ?? DefaultSTSClientRegion;
108+
ICoreAmazonSTS coreSTSClient = GetSTSClient(region);
109+
110+
var credentials = await coreSTSClient.CredentialsFromAssumeRoleAuthenticationAsync(RoleArn, RoleSessionName, Options).ConfigureAwait(false);
111+
_logger.InfoFormat("New credentials created for assume role that expire at {0}", credentials.Expiration.ToString("yyyy-MM-ddTHH:mm:ss.fffffffK", CultureInfo.InvariantCulture));
112+
return new CredentialsRefreshState(credentials, credentials.Expiration);
113+
}
114+
115+
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026",
116+
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")]
117+
private ICoreAmazonSTS GetSTSClient(RegionEndpoint region)
118+
{
119+
ICoreAmazonSTS coreSTSClient = GlobalRuntimeDependencyRegistry.Instance.GetInstance<ICoreAmazonSTS>(ServiceClientHelpers.STS_ASSEMBLY_NAME, ServiceClientHelpers.STS_SERVICE_CLASS_NAME,
120+
new CreateInstanceContext(new SecurityTokenServiceClientContext { Action = SecurityTokenServiceClientContext.ActionContext.AssumeRoleAWSCredentials, Region = region, ProxySettings = Options?.ProxySettings }));
101121

102122
if (coreSTSClient == null)
103123
{
@@ -130,11 +150,7 @@ protected override CredentialsRefreshState GenerateNewCredentials()
130150
}
131151
}
132152

133-
134-
135-
var credentials = coreSTSClient.CredentialsFromAssumeRoleAuthentication(RoleArn, RoleSessionName, Options);
136-
_logger.DebugFormat("New credentials created for assume role that expire at {0}", credentials.Expiration.ToString("yyyy-MM-ddTHH:mm:ss.fffffffK", CultureInfo.InvariantCulture));
137-
return new CredentialsRefreshState(credentials, credentials.Expiration);
153+
return coreSTSClient;
138154
}
139155
}
140156
}

‎sdk/src/Core/Amazon.Runtime/Credentials/AssumeRoleWithWebIdentityCredentials.cs

-4
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@
2525
using System.IO;
2626
using System.Net;
2727
using System.Text.RegularExpressions;
28-
#if AWS_ASYNC_API
2928
using System.Threading.Tasks;
30-
#endif
3129

3230
namespace Amazon.Runtime
3331
{
@@ -181,7 +179,6 @@ protected override CredentialsRefreshState GenerateNewCredentials()
181179
return new CredentialsRefreshState(credentials, credentials.Expiration);
182180
}
183181

184-
#if AWS_ASYNC_API
185182
protected override async Task<CredentialsRefreshState> GenerateNewCredentialsAsync()
186183
{
187184
string token = null;
@@ -218,7 +215,6 @@ protected override async Task<CredentialsRefreshState> GenerateNewCredentialsAsy
218215
_logger.DebugFormat("New credentials created using assume role with web identity that expire at {0}", credentials.Expiration.ToString("yyyy-MM-ddTHH:mm:ss.fffffffK", CultureInfo.InvariantCulture));
219216
return new CredentialsRefreshState(credentials, credentials.Expiration);
220217
}
221-
#endif
222218

223219
/// <summary>
224220
/// Gets a client to be used for AssumeRoleWithWebIdentity requests.

‎sdk/src/Core/Amazon.Runtime/Credentials/DefaultInstanceProfileAWSCredentials.cs

+107-8
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ private DefaultInstanceProfileAWSCredentials()
8181

8282
_logger = Logger.GetLogger(typeof(DefaultInstanceProfileAWSCredentials));
8383

84-
_credentialsRetrieverTimer = new Timer(RenewCredentials, null, TimeSpan.Zero, _neverTimespan);
84+
_credentialsRetrieverTimer = new Timer(RenewCredentials, null, TimeSpan.Zero, _neverTimespan); // This invokes synchronous calls in seperate thread.
8585
}
8686

8787
#region Overrides
@@ -174,15 +174,94 @@ public override ImmutableCredentials GetCredentials()
174174
return credentials;
175175
}
176176

177-
#if AWS_ASYNC_API
178177
/// <summary>
179178
/// Returns a copy of the most recent instance profile credentials.
180179
/// </summary>
181-
public override Task<ImmutableCredentials> GetCredentialsAsync()
180+
public override async Task<ImmutableCredentials> GetCredentialsAsync()
182181
{
183-
return Task.FromResult<ImmutableCredentials>(GetCredentials());
182+
CheckIsIMDSEnabled();
183+
ImmutableCredentials credentials = null;
184+
185+
// Try to acquire read lock. The thread would be blocked if another thread has write lock.
186+
if (_credentialsLock.TryEnterReadLock(_credentialsLockTimeout))
187+
{
188+
try
189+
{
190+
if (null != _lastRetrievedCredentials)
191+
{
192+
if (_lastRetrievedCredentials.IsExpiredWithin(TimeSpan.Zero) &&
193+
!_imdsRefreshFailed)
194+
{
195+
// this is the first failure - immediately try to renew
196+
_imdsRefreshFailed = true;
197+
_lastRetrievedCredentials = await FetchCredentialsAsync().ConfigureAwait(false);
198+
}
199+
200+
// if credentials are expired, we'll still return them, but log a message about
201+
// them being expired.
202+
if (_lastRetrievedCredentials.IsExpiredWithin(TimeSpan.Zero))
203+
{
204+
_logger.InfoFormat(_usingExpiredCredentialsFromIMDS);
205+
}
206+
else
207+
{
208+
_imdsRefreshFailed = false;
209+
}
210+
211+
return _lastRetrievedCredentials?.Credentials.Copy();
212+
}
213+
}
214+
finally
215+
{
216+
_credentialsLock.ExitReadLock();
217+
}
218+
}
219+
220+
// If there's no credentials cached, hit IMDS directly. Try to acquire write lock.
221+
if (_credentialsLock.TryEnterWriteLock(_credentialsLockTimeout))
222+
{
223+
try
224+
{
225+
// Check for last retrieved credentials again in case other thread might have already fetched it.
226+
if (null == _lastRetrievedCredentials)
227+
{
228+
_lastRetrievedCredentials = await FetchCredentialsAsync().ConfigureAwait(false);
229+
}
230+
231+
if (_lastRetrievedCredentials.IsExpiredWithin(TimeSpan.Zero) &&
232+
!_imdsRefreshFailed)
233+
{
234+
// this is the first failure - immediately try to renew
235+
_imdsRefreshFailed = true;
236+
_lastRetrievedCredentials = await FetchCredentialsAsync().ConfigureAwait(false);
237+
}
238+
239+
// if credentials are expired, we'll still return them, but log a message about
240+
// them being expired.
241+
if (_lastRetrievedCredentials.IsExpiredWithin(TimeSpan.Zero))
242+
{
243+
_logger.InfoFormat(_usingExpiredCredentialsFromIMDS);
244+
}
245+
else
246+
{
247+
_imdsRefreshFailed = false;
248+
}
249+
250+
credentials = _lastRetrievedCredentials.Credentials?.Copy();
251+
}
252+
finally
253+
{
254+
_credentialsLock.ExitWriteLock();
255+
}
256+
}
257+
258+
if (credentials == null)
259+
{
260+
throw new AmazonServiceException(FailedToGetCredentialsMessage);
261+
}
262+
263+
return credentials;
184264
}
185-
#endif
186265
#endregion
187266

188267
#region Private members
@@ -241,6 +320,28 @@ private static RefreshingAWSCredentials.CredentialsRefreshState FetchCredentials
241320
if (securityCredentials == null)
242321
throw new AmazonServiceException("Unable to get IAM security credentials from EC2 Instance Metadata Service.");
243322

323+
IAMSecurityCredentialMetadata metadata = GetMetadataFromSecurityCredentials(securityCredentials);
324+
325+
return new RefreshingAWSCredentials.CredentialsRefreshState(
326+
new ImmutableCredentials(metadata.AccessKeyId, metadata.SecretAccessKey, metadata.Token),
327+
metadata.Expiration);
328+
}
329+
330+
private static async Task<RefreshingAWSCredentials.CredentialsRefreshState> FetchCredentialsAsync()
331+
{
332+
var securityCredentials = await EC2InstanceMetadata.GetIAMSecurityCredentialsAsync().ConfigureAwait(false);
333+
if (securityCredentials == null)
334+
throw new AmazonServiceException("Unable to get IAM security credentials from EC2 Instance Metadata Service.");
335+
336+
IAMSecurityCredentialMetadata metadata = GetMetadataFromSecurityCredentials(securityCredentials);
337+
338+
return new RefreshingAWSCredentials.CredentialsRefreshState(
339+
new ImmutableCredentials(metadata.AccessKeyId, metadata.SecretAccessKey, metadata.Token),
340+
metadata.Expiration);
341+
}
342+
343+
private static IAMSecurityCredentialMetadata GetMetadataFromSecurityCredentials(System.Collections.Generic.IDictionary<string, IAMSecurityCredentialMetadata> securityCredentials)
344+
{
244345
string firstRole = null;
245346
foreach (var role in securityCredentials.Keys)
246347
{
@@ -255,9 +356,7 @@ private static RefreshingAWSCredentials.CredentialsRefreshState FetchCredentials
255356
if (metadata == null)
256357
throw new AmazonServiceException("Unable to get credentials for role \"" + firstRole + "\" from EC2 Instance Metadata Service.");
257358

258-
return new RefreshingAWSCredentials.CredentialsRefreshState(
259-
new ImmutableCredentials(metadata.AccessKeyId, metadata.SecretAccessKey, metadata.Token),
260-
metadata.Expiration);
359+
return metadata;
261360
}
262361

263362
private static void CheckIsIMDSEnabled()

‎sdk/src/Core/Amazon.Runtime/Credentials/GenericContainerCredentials.cs

+42
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using System.Globalization;
2121
using System.IO;
2222
using System.Linq;
23+
using System.Threading.Tasks;
2324

2425
namespace Amazon.Runtime
2526
{
@@ -110,6 +111,47 @@ protected override CredentialsRefreshState GenerateNewCredentials()
110111
return new CredentialsRefreshState(new ImmutableCredentials(credentials.AccessKeyId, credentials.SecretAccessKey, credentials.Token), credentials.Expiration);
111112
}
112113

114+
protected override async Task<CredentialsRefreshState> GenerateNewCredentialsAsync()
115+
{
116+
SecurityCredentials credentials;
117+
JitteredDelay retry = new JitteredDelay(TimeSpan.FromMilliseconds(200), TimeSpan.FromMilliseconds(50));
118+
119+
// Attempt to get the credentials 4 times ignoring null return/exceptions and on the 5th try, escalate the exception if there is one.
120+
for (int i = 1; ; i++)
121+
{
122+
try
123+
{
124+
var headers = CreateAuthorizationHeader();
125+
126+
// This provider intentionally does not use a proxy when retrieving credentials from the determined endpoint.
127+
credentials = await GetObjectFromResponseAsync<SecurityCredentials, SecurityCredentialsJsonSerializerContexts>(ResolvedEndpointUri, proxy: null, headers).ConfigureAwait(false);
128+
if (credentials != null)
129+
{
130+
break;
131+
}
132+
}
133+
catch (Exception exception)
134+
{
135+
if (i == MaxRetries)
136+
{
137+
// GetObjectFromResponse returns a "Unable to reach credentials server" generic message, but we'll check if there are inner exceptions available.
138+
// They may contain more details about the error (e.g. 500 Internal Server Error).
139+
while (exception.InnerException != null)
140+
{
141+
exception = exception.InnerException;
142+
}
143+
144+
throw new AmazonServiceException(string.Format(CultureInfo.InvariantCulture, "Unable to retrieve credentials. Message = \"{0}\"", exception.Message));
145+
}
146+
}
147+
;
148+
149+
AWSSDKUtils.Sleep(retry.Next());
150+
}
151+
152+
return new CredentialsRefreshState(new ImmutableCredentials(credentials.AccessKeyId, credentials.SecretAccessKey, credentials.Token), credentials.Expiration);
153+
}
154+
113155
/// <summary>
114156
/// Determines the endpoint where the credentials retrieval request should be sent.
115157
/// This provider will respect the following sources, in order of priority, to determine the request URI:

0 commit comments

Comments
 (0)
Failed to load comments.