Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import software.amazon.awssdk.utils.Validate;
import software.amazon.awssdk.utils.builder.CopyableBuilder;
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
import software.amazon.awssdk.utils.cache.CacheRefreshUtils;
import software.amazon.awssdk.utils.cache.CachedSupplier;
import software.amazon.awssdk.utils.cache.NonBlocking;
import software.amazon.awssdk.utils.cache.RefreshResult;
Expand Down Expand Up @@ -100,6 +101,7 @@ public final class ContainerCredentialsProvider
private final String providerName;
private final Duration staleTime;
private final Duration prefetchTime;
private final boolean prefetchTimeExplicitlySet;

/**
* @see #builder()
Expand All @@ -115,6 +117,7 @@ private ContainerCredentialsProvider(BuilderImpl builder) {
this.httpCredentialsLoader = HttpCredentialsLoader.create(this.providerName);
this.staleTime = Optional.ofNullable(builder.staleTime).orElse(DEFAULT_STALE_TIME);
this.prefetchTime = Optional.ofNullable(builder.prefetchTime).orElse(DEFAULT_PREFETCH_TIME);
this.prefetchTimeExplicitlySet = builder.prefetchTime != null;
Validate.isTrue(this.staleTime.compareTo(this.prefetchTime) <= 0,
"staleTime (%s) must be less than or equal to prefetchTime (%s).", this.staleTime, this.prefetchTime);

Expand Down Expand Up @@ -172,7 +175,13 @@ private Instant prefetchTime(Instant expiration) {
if (expiration == null) {
return Instant.now().plus(1, ChronoUnit.HOURS);
}
return expiration.minus(prefetchTime);

Instant now = Instant.now();
Duration effectivePrefetchWindow = prefetchTimeExplicitlySet
? prefetchTime
: CacheRefreshUtils.computeDynamicPrefetchWindow(expiration, now);

return expiration.minus(effectivePrefetchWindow);
}

@Override
Expand Down Expand Up @@ -356,7 +365,10 @@ public interface Builder extends HttpCredentialsProvider.Builder<ContainerCreden
* <p>This value must be greater than or equal to {@link #staleTime(Duration)}. Setting this equal to
* {@code staleTime} effectively disables prefetch, causing all refreshes to be mandatory (blocking).
*
* <p>By default, this is 5 minutes.
* <p>If not explicitly set, the advisory refresh window is computed dynamically based on the credential's
* remaining lifetime: 5 minutes for credentials with less than 20 minutes remaining, 15 minutes for 20-90
* minutes remaining, and 60 minutes for 90+ minutes remaining. This dynamic window is recomputed on each
* successful refresh.
*
* @param prefetchTime the duration before expiration that triggers advisory (proactive) refresh
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import software.amazon.awssdk.utils.Validate;
import software.amazon.awssdk.utils.builder.CopyableBuilder;
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
import software.amazon.awssdk.utils.cache.CacheRefreshUtils;
import software.amazon.awssdk.utils.cache.CachedSupplier;
import software.amazon.awssdk.utils.cache.NonBlocking;
import software.amazon.awssdk.utils.cache.RefreshResult;
Expand Down Expand Up @@ -94,6 +95,8 @@ public final class InstanceProfileCredentialsProvider

private final Duration prefetchTime;

private final boolean prefetchTimeExplicitlySet;

private final String sourceChain;
private final String providerName;

Expand Down Expand Up @@ -123,6 +126,7 @@ private InstanceProfileCredentialsProvider(BuilderImpl builder) {

this.staleTime = Validate.getOrDefault(builder.staleTime, () -> Duration.ofMinutes(1));
this.prefetchTime = Validate.getOrDefault(builder.prefetchTime, () -> Duration.ofMinutes(5));
this.prefetchTimeExplicitlySet = builder.prefetchTime != null;
Validate.isTrue(this.staleTime.compareTo(this.prefetchTime) <= 0,
"staleTime (%s) must be less than or equal to prefetchTime (%s).", this.staleTime, this.prefetchTime);

Expand Down Expand Up @@ -208,12 +212,16 @@ private Instant prefetchTime(Instant expiration) {
return null;
}

// Advisory refresh window: use configured prefetchTime before expiry.
// If remaining lifetime < prefetchTime, refresh immediately.
if (timeUntilExpiration.compareTo(prefetchTime) < 0) {
// Use dynamic window when user has not explicitly configured prefetchTime
Duration effectivePrefetchWindow = prefetchTimeExplicitlySet
? prefetchTime
: CacheRefreshUtils.computeDynamicPrefetchWindow(expiration, now);

// If remaining lifetime < the advisory window, refresh immediately.
if (timeUntilExpiration.compareTo(effectivePrefetchWindow) < 0) {
return now;
}
return expiration.minus(prefetchTime);
return expiration.minus(effectivePrefetchWindow);
}

@Override
Expand Down Expand Up @@ -391,7 +399,10 @@ public interface Builder extends HttpCredentialsProvider.Builder<InstanceProfile
* <p>This value must be greater than or equal to {@link #staleTime(Duration)}. Setting this equal to
* {@code staleTime} effectively disables prefetch, causing all refreshes to be mandatory (blocking).
*
* <p>By default, this is 5 minutes.
* <p>If not explicitly set, the advisory refresh window is computed dynamically based on the credential's
* remaining lifetime: 5 minutes for credentials with less than 20 minutes remaining, 15 minutes for 20-90
* minutes remaining, and 60 minutes for 90+ minutes remaining. This dynamic window is recomputed on each
* successful refresh.
*
* @param duration the duration before expiration that triggers advisory (proactive) refresh
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import software.amazon.awssdk.utils.Validate;
import software.amazon.awssdk.utils.builder.CopyableBuilder;
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
import software.amazon.awssdk.utils.cache.CacheRefreshUtils;
import software.amazon.awssdk.utils.cache.CachedSupplier;
import software.amazon.awssdk.utils.cache.NonBlocking;
import software.amazon.awssdk.utils.cache.RefreshResult;
Expand Down Expand Up @@ -103,6 +104,7 @@ public final class ProcessCredentialsProvider
private final String providerName;
private final Duration staleTime;
private final Duration prefetchTime;
private final boolean prefetchTimeExplicitlySet;

/**
* @see #builder()
Expand All @@ -120,6 +122,7 @@ private ProcessCredentialsProvider(Builder builder) {
: builder.sourceChain + "," + PROVIDER_NAME;
this.staleTime = Optional.ofNullable(builder.staleTime).orElse(DEFAULT_STALE_TIME);
this.prefetchTime = Optional.ofNullable(builder.prefetchTime).orElse(DEFAULT_PREFETCH_TIME);
this.prefetchTimeExplicitlySet = builder.prefetchTime != null;
Validate.isTrue(this.staleTime.compareTo(this.prefetchTime) <= 0,
"staleTime (%s) must be less than or equal to prefetchTime (%s).", this.staleTime, this.prefetchTime);

Expand Down Expand Up @@ -194,7 +197,12 @@ private Instant prefetchTime(Instant expiration) {
if (expiration == null || expiration.equals(Instant.MAX)) {
return Instant.MAX;
}
return expiration.minus(prefetchTime);
if (prefetchTimeExplicitlySet) {
return expiration.minus(prefetchTime);
}
Instant now = Instant.now();
Duration dynamicWindow = CacheRefreshUtils.computeDynamicPrefetchWindow(expiration, now);
return expiration.minus(dynamicWindow);
}

/**
Expand Down Expand Up @@ -382,7 +390,10 @@ public Builder staleTime(Duration staleTime) {
* <p>This value must be greater than or equal to {@link #staleTime(Duration)}. Setting this equal to
* {@code staleTime} effectively disables prefetch, causing all refreshes to be mandatory (blocking).
*
* <p>By default, this is 5 minutes.</p>
* <p>If not explicitly set, the advisory refresh window is computed dynamically based on the credential's
* remaining lifetime: 5 minutes for credentials with less than 20 minutes remaining, 15 minutes for 20-90
* minutes remaining, and 60 minutes for 90+ minutes remaining. This dynamic window is recomputed on each
* successful refresh.</p>
*
* @param prefetchTime the duration before expiration that triggers advisory (proactive) refresh
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,10 @@ public interface Builder extends CopyableBuilder<Builder, WebIdentityTokenFileCr
* <p>This value must be greater than or equal to {@link #staleTime(Duration)}. Setting this equal to
* {@code staleTime} effectively disables prefetch, causing all refreshes to be mandatory (blocking).
*
* <p>By default, this is 5 minutes.
* <p>If not explicitly set, the advisory refresh window is computed dynamically based on the credential's
* remaining lifetime: 5 minutes for credentials with less than 20 minutes remaining, 15 minutes for 20-90
* minutes remaining, and 60 minutes for 90+ minutes remaining. This dynamic window is recomputed on each
* successful refresh.
*
* @param prefetchTime the duration before expiration that triggers advisory (proactive) refresh
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -639,13 +639,13 @@ void imdsCallFrequencyIsLimited() {
stubSecureCredentialsResponse(aResponse().withBody(successfulCredentialsResponse1));
AwsCredentials credentialsAtStart = credentialsProvider.resolveCredentials();

// Move time forward but still before the prefetch window (5 min before expiry).
// Since prefetchTime = expiration - 5min = now + 5h55m, anything before that should not trigger refresh.
clock.time = now.plus(5, HOURS);
// Move time forward but still before the prefetch window (60 min before expiry for 6h credentials).
// Since dynamic prefetchTime = expiration - 60min = now + 5h, anything before that should not trigger refresh.
clock.time = now.plus(4, HOURS);
stubSecureCredentialsResponse(aResponse().withBody(successfulCredentialsResponse2));
AwsCredentials credentials5HoursLater = credentialsProvider.resolveCredentials();
AwsCredentials credentialsLater = credentialsProvider.resolveCredentials();

assertThat(credentials5HoursLater).isEqualTo(credentialsAtStart);
assertThat(credentialsLater).isEqualTo(credentialsAtStart);
assertThat(credentialsAtStart.secretAccessKey()).isEqualTo("SECRET_ACCESS_KEY");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import software.amazon.awssdk.utils.Validate;
import software.amazon.awssdk.utils.builder.CopyableBuilder;
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
import software.amazon.awssdk.utils.cache.CacheRefreshUtils;
import software.amazon.awssdk.utils.cache.CachedSupplier;
import software.amazon.awssdk.utils.cache.NonBlocking;
import software.amazon.awssdk.utils.cache.RefreshResult;
Expand Down Expand Up @@ -90,6 +91,7 @@ public final class LoginCredentialsProvider implements
private final SigninClient signinClient;
private final Duration staleTime;
private final Duration prefetchTime;
private final boolean prefetchTimeExplicitlySet;
private final Path tokenCacheLocation;

private final CachedSupplier<AwsCredentials> credentialCache;
Expand All @@ -107,6 +109,7 @@ private LoginCredentialsProvider(BuilderImpl builder) {

this.staleTime = Optional.ofNullable(builder.staleTime).orElse(DEFAULT_STALE_TIME);
this.prefetchTime = Optional.ofNullable(builder.prefetchTime).orElse(DEFAULT_PREFETCH_TIME);
this.prefetchTimeExplicitlySet = builder.prefetchTime != null;
Validate.isTrue(this.staleTime.compareTo(this.prefetchTime) <= 0,
"staleTime (%s) must be less than or equal to prefetchTime (%s).", this.staleTime, this.prefetchTime);
this.sourceChain = builder.sourceChain;
Expand Down Expand Up @@ -157,9 +160,14 @@ && shouldNotRefresh(currentExpirationTime, prefetchTime)) {
.providerName(this.providerName)
.build();

Instant now = Instant.now();
Duration effectivePrefetchWindow = prefetchTimeExplicitlySet
? prefetchTime
: CacheRefreshUtils.computeDynamicPrefetchWindow(currentExpirationTime, now);

return RefreshResult.builder(credentials)
.staleTime(currentExpirationTime.minus(staleTime))
.prefetchTime(currentExpirationTime.minus(prefetchTime))
.prefetchTime(currentExpirationTime.minus(effectivePrefetchWindow))
.build();
}

Expand Down Expand Up @@ -203,7 +211,9 @@ private RefreshResult<AwsCredentials> refreshFromSigninService(LoginAccessToken

return RefreshResult.builder((AwsCredentials) updatedCredentials)
.staleTime(newExpiration.minus(staleTime))
.prefetchTime(newExpiration.minus(prefetchTime))
.prefetchTime(newExpiration.minus(prefetchTimeExplicitlySet
? prefetchTime
: CacheRefreshUtils.computeDynamicPrefetchWindow(newExpiration, Instant.now())))
.build();
} catch (AccessDeniedException accessDeniedException) {
if (accessDeniedException.error() == null) {
Expand Down Expand Up @@ -355,7 +365,10 @@ public interface Builder extends CopyableBuilder<Builder, LoginCredentialsProvid
* <p>This value must be greater than or equal to {@link #staleTime(Duration)}. Setting this equal to
* {@code staleTime} effectively disables prefetch, causing all refreshes to be mandatory (blocking).
*
* <p>By default, this is 5 minutes.
* <p>If not explicitly set, the advisory refresh window is computed dynamically based on the credential's
* remaining lifetime: 5 minutes for credentials with less than 20 minutes remaining, 15 minutes for 20-90
* minutes remaining, and 60 minutes for 90+ minutes remaining. This dynamic window is recomputed on each
* successful refresh.
*
* @param prefetchTime the duration before expiration that triggers advisory (proactive) refresh
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.awssdk.utils.builder.CopyableBuilder;
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
import software.amazon.awssdk.utils.cache.CacheRefreshUtils;
import software.amazon.awssdk.utils.cache.CachedSupplier;
import software.amazon.awssdk.utils.cache.NonBlocking;
import software.amazon.awssdk.utils.cache.RefreshResult;
Expand Down Expand Up @@ -70,6 +71,7 @@ public final class SsoCredentialsProvider implements AwsCredentialsProvider, Sdk
private final SsoClient ssoClient;
private final Duration staleTime;
private final Duration prefetchTime;
private final boolean prefetchTimeExplicitlySet;

private final CachedSupplier<SessionCredentialsHolder> credentialCache;

Expand All @@ -84,6 +86,7 @@ private SsoCredentialsProvider(BuilderImpl builder) {

this.staleTime = Optional.ofNullable(builder.staleTime).orElse(DEFAULT_STALE_TIME);
this.prefetchTime = Optional.ofNullable(builder.prefetchTime).orElse(DEFAULT_PREFETCH_TIME);
this.prefetchTimeExplicitlySet = builder.prefetchTime != null;
isTrue(this.staleTime.compareTo(this.prefetchTime) <= 0,
"staleTime (%s) must be less than or equal to prefetchTime (%s).", this.staleTime, this.prefetchTime);
this.sourceChain = builder.sourceChain;
Expand Down Expand Up @@ -114,9 +117,14 @@ private RefreshResult<SessionCredentialsHolder> updateSsoCredentials() {
SessionCredentialsHolder credentials = getUpdatedCredentials(ssoClient);
Instant actualTokenExpiration = credentials.sessionCredentialsExpiration();

Instant now = Instant.now();
Duration effectivePrefetchWindow = prefetchTimeExplicitlySet
? prefetchTime
: CacheRefreshUtils.computeDynamicPrefetchWindow(actualTokenExpiration, now);

return RefreshResult.builder(credentials)
.staleTime(actualTokenExpiration.minus(staleTime))
.prefetchTime(actualTokenExpiration.minus(prefetchTime))
.prefetchTime(actualTokenExpiration.minus(effectivePrefetchWindow))
.build();
}

Expand Down Expand Up @@ -225,7 +233,10 @@ public interface Builder extends CopyableBuilder<Builder, SsoCredentialsProvider
* <p>This value must be greater than or equal to {@link #staleTime(Duration)}. Setting this equal to
* {@code staleTime} effectively disables prefetch, causing all refreshes to be mandatory (blocking).
*
* <p>By default, this is 5 minutes.</p>
* <p>If not explicitly set, the advisory refresh window is computed dynamically based on the credential's
* remaining lifetime: 5 minutes for credentials with less than 20 minutes remaining, 15 minutes for 20-90
* minutes remaining, and 60 minutes for 90+ minutes remaining. This dynamic window is recomputed on each
* successful refresh.</p>
*
* @param prefetchTime the duration before expiration that triggers advisory (proactive) refresh
*/
Expand Down
Loading
Loading