Skip to content

Commit 59d985a

Browse files
committed
Make STSCredentialsProvider prefetch and stale times configurable
1 parent da9ebb9 commit 59d985a

File tree

3 files changed

+109
-7
lines changed

3 files changed

+109
-7
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"category": "Amazon STS",
3+
"type": "feature",
4+
"description": "Make the STSCredentialsProvider stale and prefetch times configurable so clients can control when session credentials are refreshed"
5+
}

services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsCredentialsProvider.java

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.time.Duration;
1919
import java.time.Instant;
20+
import java.util.Optional;
2021
import java.util.function.Function;
2122
import software.amazon.awssdk.annotations.NotThreadSafe;
2223
import software.amazon.awssdk.annotations.SdkInternalApi;
@@ -31,6 +32,7 @@
3132
import software.amazon.awssdk.utils.cache.NonBlocking;
3233
import software.amazon.awssdk.utils.cache.RefreshResult;
3334

35+
3436
/**
3537
* An implementation of {@link AwsCredentialsProvider} that is extended within this package to provide support for periodically-
3638
* updating session credentials. When credentials get close to expiration, this class will attempt to update them asynchronously
@@ -40,6 +42,10 @@
4042
@ThreadSafe
4143
@SdkInternalApi
4244
abstract class StsCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable {
45+
46+
private static final Duration DEFAULT_STALE_TIME = Duration.ofMinutes(1);
47+
private static final Duration DEFAULT_PREFETCH_TIME = Duration.ofMinutes(5);
48+
4349
/**
4450
* The STS client that should be used for periodically updating the session credentials in the background.
4551
*/
@@ -50,9 +56,15 @@ abstract class StsCredentialsProvider implements AwsCredentialsProvider, SdkAuto
5056
*/
5157
private final CachedSupplier<SessionCredentialsHolder> sessionCache;
5258

59+
private final Duration staleTime;
60+
private final Duration prefetchTime;
61+
5362
protected StsCredentialsProvider(BaseBuilder<?, ?> builder, String asyncThreadName) {
5463
this.stsClient = Validate.notNull(builder.stsClient, "STS client must not be null.");
5564

65+
this.staleTime = Optional.ofNullable(builder.staleTime).orElse(DEFAULT_STALE_TIME);
66+
this.prefetchTime = Optional.ofNullable(builder.prefetchTime).orElse(DEFAULT_PREFETCH_TIME);
67+
5668
CachedSupplier.Builder<SessionCredentialsHolder> cacheBuilder = CachedSupplier.builder(this::updateSessionCredentials);
5769
if (builder.asyncCredentialUpdateEnabled) {
5870
cacheBuilder.prefetchStrategy(new NonBlocking(asyncThreadName));
@@ -67,9 +79,10 @@ protected StsCredentialsProvider(BaseBuilder<?, ?> builder, String asyncThreadNa
6779
private RefreshResult<SessionCredentialsHolder> updateSessionCredentials() {
6880
SessionCredentialsHolder credentials = new SessionCredentialsHolder(getUpdatedCredentials(stsClient));
6981
Instant actualTokenExpiration = credentials.getSessionCredentialsExpiration().toInstant();
82+
7083
return RefreshResult.builder(credentials)
71-
.staleTime(actualTokenExpiration.minus(Duration.ofMinutes(1)))
72-
.prefetchTime(actualTokenExpiration.minus(Duration.ofMinutes(5)))
84+
.staleTime(actualTokenExpiration.minus(staleTime))
85+
.prefetchTime(actualTokenExpiration.minus(prefetchTime))
7386
.build();
7487
}
7588

@@ -83,6 +96,21 @@ public void close() {
8396
sessionCache.close();
8497
}
8598

99+
/**
100+
* The amount of time, relative to STS token expiration, that the cached credentials are considered stale and should no longer be used.
101+
* All threads will block until the value is updated.
102+
*/
103+
public Duration staleTime() {
104+
return staleTime;
105+
}
106+
107+
/**
108+
* The amount of time, relative to STS token expiration, that the cached credentials are considered close to stale and should be updated.
109+
*/
110+
public Duration prefetchTime() {
111+
return prefetchTime;
112+
}
113+
86114
/**
87115
* Implemented by a child class to call STS and get a new set of credentials to be used by this provider.
88116
*/
@@ -97,6 +125,8 @@ protected abstract static class BaseBuilder<B extends BaseBuilder<B, T>, T> {
97125

98126
private Boolean asyncCredentialUpdateEnabled = false;
99127
private StsClient stsClient;
128+
private Duration staleTime;
129+
private Duration prefetchTime;
100130

101131
protected BaseBuilder(Function<B, T> providerConstructor) {
102132
this.providerConstructor = providerConstructor;
@@ -127,6 +157,31 @@ public B asyncCredentialUpdateEnabled(Boolean asyncCredentialUpdateEnabled) {
127157
return (B) this;
128158
}
129159

160+
/**
161+
* Configure the amount of time, relative to STS token expiration, that the cached credentials are considered stale and should no longer be used.
162+
* All threads will block until the value is updated.
163+
*
164+
* <p>By default, this is 1 minute.</p>
165+
*/
166+
@SuppressWarnings("unchecked")
167+
public B staleTime(Duration staleTime) {
168+
this.staleTime = staleTime;
169+
return (B) this;
170+
}
171+
172+
/**
173+
* Configure the amount of time, relative to STS token expiration, that the cached credentials are considered close to stale and should be updated.
174+
* See {@link #asyncCredentialUpdateEnabled}.
175+
*
176+
* <p>By default, this is 5 minutes.</p>
177+
*/
178+
@SuppressWarnings("unchecked")
179+
public B prefetchTime(Duration prefetchTime) {
180+
this.prefetchTime = prefetchTime;
181+
return (B) this;
182+
}
183+
184+
130185
/**
131186
* Build the credentials provider using the configuration applied to this builder.
132187
*/

services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsCredentialsProviderTestBase.java

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,43 @@ public abstract class StsCredentialsProviderTestBase<RequestT, ResponseT> {
4141

4242
@Test
4343
public void cachingDoesNotApplyToExpiredSession() {
44-
callClientWithCredentialsProvider(Instant.now().minus(Duration.ofSeconds(5)), 2);
44+
callClientWithCredentialsProvider(Instant.now().minus(Duration.ofSeconds(5)), 2, false);
45+
callClient(verify(stsClient, times(2)), Mockito.any());
46+
}
47+
48+
@Test
49+
public void cachingDoesNotApplyToExpiredSession_OverridePrefetchAndStaleTimes() {
50+
callClientWithCredentialsProvider(Instant.now().minus(Duration.ofSeconds(5)), 2, true);
4551
callClient(verify(stsClient, times(2)), Mockito.any());
4652
}
4753

4854
@Test
4955
public void cachingAppliesToNonExpiredSession() {
50-
callClientWithCredentialsProvider(Instant.now().plus(Duration.ofHours(5)), 2);
56+
callClientWithCredentialsProvider(Instant.now().plus(Duration.ofHours(5)), 2, false);
57+
callClient(verify(stsClient, times(1)), Mockito.any());
58+
}
59+
60+
@Test
61+
public void cachingAppliesToNonExpiredSession_OverridePrefetchAndStaleTimes() {
62+
callClientWithCredentialsProvider(Instant.now().plus(Duration.ofHours(5)), 2, true);
5163
callClient(verify(stsClient, times(1)), Mockito.any());
5264
}
5365

5466
@Test
5567
public void distantExpiringCredentialsUpdatedInBackground() throws InterruptedException {
56-
callClientWithCredentialsProvider(Instant.now().plusSeconds(90), 2);
68+
callClientWithCredentialsProvider(Instant.now().plusSeconds(90), 2, false);
69+
70+
Instant endCheckTime = Instant.now().plus(Duration.ofSeconds(5));
71+
while (Mockito.mockingDetails(stsClient).getInvocations().size() < 2 && endCheckTime.isAfter(Instant.now())) {
72+
Thread.sleep(100);
73+
}
74+
75+
callClient(verify(stsClient, times(2)), Mockito.any());
76+
}
77+
78+
@Test
79+
public void distantExpiringCredentialsUpdatedInBackground_OverridePrefetchAndStaleTimes() throws InterruptedException {
80+
callClientWithCredentialsProvider(Instant.now().plusSeconds(90), 2, true);
5781

5882
Instant endCheckTime = Instant.now().plus(Duration.ofSeconds(5));
5983
while (Mockito.mockingDetails(stsClient).getInvocations().size() < 2 && endCheckTime.isAfter(Instant.now())) {
@@ -72,14 +96,32 @@ public void distantExpiringCredentialsUpdatedInBackground() throws InterruptedEx
7296

7397
protected abstract ResponseT callClient(StsClient client, RequestT request);
7498

75-
public void callClientWithCredentialsProvider(Instant credentialsExpirationDate, int numTimesInvokeCredentialsProvider) {
99+
public void callClientWithCredentialsProvider(Instant credentialsExpirationDate, int numTimesInvokeCredentialsProvider, boolean overrideStaleAndPrefetchTimes) {
76100
Credentials credentials = Credentials.builder().accessKeyId("a").secretAccessKey("b").sessionToken("c").expiration(credentialsExpirationDate).build();
77101
RequestT request = getRequest();
78102
ResponseT response = getResponse(credentials);
79103

80104
when(callClient(stsClient, request)).thenReturn(response);
81105

82-
try (StsCredentialsProvider credentialsProvider = createCredentialsProviderBuilder(request).stsClient(stsClient).build()) {
106+
StsCredentialsProvider.BaseBuilder<?, ? extends StsCredentialsProvider> credentialsProviderBuilder = createCredentialsProviderBuilder(request);
107+
108+
if(overrideStaleAndPrefetchTimes) {
109+
//do the same values as we would do without overriding the stale and prefetch times
110+
credentialsProviderBuilder.staleTime(Duration.ofMinutes(2));
111+
credentialsProviderBuilder.prefetchTime(Duration.ofMinutes(4));
112+
}
113+
114+
try (StsCredentialsProvider credentialsProvider = credentialsProviderBuilder.stsClient(stsClient).build()) {
115+
if(overrideStaleAndPrefetchTimes) {
116+
//validate that we actually stored the override values in the build provider
117+
assertThat(credentialsProvider.staleTime()).as("stale time").isEqualTo(Duration.ofMinutes(2));
118+
assertThat(credentialsProvider.prefetchTime()).as("prefetch time").isEqualTo(Duration.ofMinutes(4));
119+
} else {
120+
//validate that the default values are used
121+
assertThat(credentialsProvider.staleTime()).as("stale time").isEqualTo(Duration.ofMinutes(1));
122+
assertThat(credentialsProvider.prefetchTime()).as("prefetch time").isEqualTo(Duration.ofMinutes(5));
123+
}
124+
83125
for (int i = 0; i < numTimesInvokeCredentialsProvider; ++i) {
84126
AwsSessionCredentials providedCredentials = (AwsSessionCredentials) credentialsProvider.resolveCredentials();
85127
assertThat(providedCredentials.accessKeyId()).isEqualTo("a");

0 commit comments

Comments
 (0)