-
Notifications
You must be signed in to change notification settings - Fork 863
/
RefreshingAWSCredentials.cs
308 lines (273 loc) · 11.5 KB
/
RefreshingAWSCredentials.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
using Amazon.Runtime.Internal.Util;
using Amazon.Util;
using System;
using System.Globalization;
using System.Threading;
namespace Amazon.Runtime
{
/// <summary>
/// Abstract class for automatically refreshing AWS credentials
/// </summary>
public abstract class RefreshingAWSCredentials : AWSCredentials, IDisposable
{
private Logger _logger = Logger.GetLogger(typeof(RefreshingAWSCredentials));
#region Refresh data
/// <summary>
/// Refresh state container consisting of credentials
/// and the date of the their expiration
/// </summary>
public class CredentialsRefreshState
{
public ImmutableCredentials Credentials
{
get;
set;
}
public DateTime Expiration { get; set; }
public CredentialsRefreshState()
{
}
public CredentialsRefreshState(ImmutableCredentials credentials, DateTime expiration)
{
Credentials = credentials;
Expiration = expiration;
}
internal bool IsExpiredWithin(TimeSpan preemptExpiryTime)
{
#pragma warning disable CS0612 // Type or member is obsolete
var now = AWSSDKUtils.CorrectedUtcNow;
#pragma warning restore CS0612 // Type or member is obsolete
var exp = Expiration.ToUniversalTime();
return (now > exp - preemptExpiryTime);
}
}
/// <summary>
/// Represents the current state of the Credentials.
/// </summary>
/// <remarks>This can be cleared without synchronization.</remarks>
protected CredentialsRefreshState currentState;
#region Private members
private TimeSpan _preemptExpiryTime = TimeSpan.FromMinutes(0);
private bool _disposed;
#if BCL35
/// <summary>
/// Semaphore to control thread access to GetCredentialsAsync method.
/// The semaphore will allow only one thread to generate new credentials and
/// update the current state.
/// </summary>
private readonly Semaphore _updateGeneratedCredentialsSemaphore = new Semaphore(1, 1);
#else
/// <summary>
/// Semaphore to control thread access to GetCredentialsAsync method.
/// The semaphore will allow only one thread to generate new credentials and
/// update the current state.
/// </summary>
private readonly SemaphoreSlim _updateGeneratedCredentialsSemaphore = new SemaphoreSlim(1, 1);
#endif
#endregion
#endregion
#region Properties
/// <summary>
/// The time before actual expiration to expire the credentials.
/// Property cannot be set to a negative TimeSpan.
/// </summary>
public TimeSpan PreemptExpiryTime
{
get { return _preemptExpiryTime; }
set
{
if (value < TimeSpan.Zero)
throw new ArgumentOutOfRangeException("value", "PreemptExpiryTime cannot be negative");
_preemptExpiryTime = value;
}
}
#endregion
#region Override methods
/// <summary>
/// Returns an instance of ImmutableCredentials for this instance
/// </summary>
/// <returns></returns>
public override ImmutableCredentials GetCredentials()
{
_updateGeneratedCredentialsSemaphore.Wait();
try
{
// We save the currentState as it might be modified or cleared.
var tempState = currentState;
// If credentials are expired or we don't have any state yet, update
if (ShouldUpdateState(tempState, PreemptExpiryTime))
{
tempState = GenerateNewCredentials();
UpdateToGeneratedCredentials(tempState, PreemptExpiryTime);
currentState = tempState;
}
return tempState.Credentials.Copy();
}
finally
{
_updateGeneratedCredentialsSemaphore.Release();
}
}
#if AWS_ASYNC_API
public override async System.Threading.Tasks.Task<ImmutableCredentials> GetCredentialsAsync()
{
await _updateGeneratedCredentialsSemaphore.WaitAsync().ConfigureAwait(false);
try
{
// We save the currentState as it might be modified or cleared.
var tempState = currentState;
// If credentials are expired, update
if (ShouldUpdateState(tempState, PreemptExpiryTime))
{
tempState = await GenerateNewCredentialsAsync().ConfigureAwait(false);
UpdateToGeneratedCredentials(tempState, PreemptExpiryTime);
currentState = tempState;
}
return tempState.Credentials.Copy();
}
finally
{
_updateGeneratedCredentialsSemaphore.Release();
}
}
#endif
#endregion
#region Private/protected credential update methods
private static void UpdateToGeneratedCredentials(CredentialsRefreshState state, TimeSpan preemptExpiryTime)
{
// Check if the new credentials are already expired
if (ShouldUpdateState(state, preemptExpiryTime))
{
string errorMessage;
if (state == null)
{
errorMessage = "Unable to generate temporary credentials";
}
else
{
errorMessage = string.Format(CultureInfo.InvariantCulture,
"The retrieved credentials have already expired: Now = {0}, Credentials expiration = {1}",
#pragma warning disable CS0612 // Type or member is obsolete
AWSSDKUtils.CorrectedUtcNow.ToLocalTime(), state.Expiration);
#pragma warning restore CS0612 // Type or member is obsolete
}
throw new AmazonClientException(errorMessage);
}
// Offset the Expiration by PreemptExpiryTime. This produces the expiration window
// where the credentials should be updated before they actually expire.
state.Expiration -= preemptExpiryTime;
if (ShouldUpdateState(state, preemptExpiryTime))
{
// This could happen if the default value of PreemptExpiryTime is
// overridden and set too high such that ShouldUpdate returns true.
var logger = Logger.GetLogger(typeof(RefreshingAWSCredentials));
logger.InfoFormat(
"The preempt expiry time is set too high: Current time = {0}, Credentials expiry time = {1}, Preempt expiry time = {2}.",
#pragma warning disable CS0612 // Type or member is obsolete
AWSSDKUtils.CorrectedUtcNow.ToLocalTime(),
#pragma warning restore CS0612 // Type or member is obsolete
state.Expiration, preemptExpiryTime);
}
}
/// <summary>
/// Test credentials existence and expiration time
/// should update if:
/// credentials have not been loaded yet
/// it's past the expiration time. At this point currentState.Expiration may
/// have the PreemptExpiryTime baked into to the expiration from a call to
/// UpdateToGeneratedCredentials but it may not if this is new application load.
/// </summary>
protected bool ShouldUpdate
{
get
{
return ShouldUpdateState(currentState, PreemptExpiryTime);
}
}
// Test credentials existence and expiration time
// should update if:
// credentials have not been loaded yet
// it's past the expiration time. At this point currentState.Expiration may
// have the PreemptExpiryTime baked into to the expiration from a call to
// UpdateToGeneratedCredentials but it may not if this is new application
// load.
private static bool ShouldUpdateState(CredentialsRefreshState state, TimeSpan preemptExpiryTime)
{
// it's past the expiration time. At this point currentState.Expiration may
// have the PreemptExpiryTime baked into to the expiration from a call to
// UpdateToGeneratedCredentials but it may not if this is new application
// load.
var isExpired = state?.IsExpiredWithin(TimeSpan.Zero);
if (isExpired == true)
{
#pragma warning disable CS0612 // Type or member is obsolete
var logger = Logger.GetLogger(typeof(RefreshingAWSCredentials));
logger.InfoFormat("Determined refreshing credentials should update. Expiration time: {0}, Current time: {1}",
state.Expiration.Add(preemptExpiryTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffffffK", CultureInfo.InvariantCulture),
AWSSDKUtils.CorrectedUtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffffffK", CultureInfo.InvariantCulture));
#pragma warning restore CS0612 // Type or member is obsolete
}
return isExpired ?? true;
}
/// <summary>
/// When overridden in a derived class, generates new credentials and new expiration date.
///
/// Called on first credentials request and when expiration date is in the past.
/// </summary>
/// <returns></returns>
protected virtual CredentialsRefreshState GenerateNewCredentials()
{
throw new NotImplementedException();
}
#if AWS_ASYNC_API
/// <summary>
/// When overridden in a derived class, generates new credentials and new expiration date.
///
/// Called on first credentials request and when expiration date is in the past.
/// </summary>
/// <returns></returns>
protected virtual System.Threading.Tasks.Task<CredentialsRefreshState> GenerateNewCredentialsAsync()
{
return System.Threading.Tasks.Task.Run(() => this.GenerateNewCredentials());
}
#endif
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
_updateGeneratedCredentialsSemaphore.Dispose();
}
_disposed = true;
}
#endregion
#region Public Methods
/// <summary>
/// Clears currently-stored credentials, forcing the next GetCredentials call to generate new credentials.
/// </summary>
public virtual void ClearCredentials()
{
currentState = null;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}