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 @@ -16,6 +16,7 @@
package software.amazon.awssdk.auth.credentials;

import static java.nio.charset.StandardCharsets.UTF_8;
import static software.amazon.awssdk.utils.cache.CachedSupplier.StaleValueBehavior.ALLOW;

import java.io.IOException;
import java.net.InetAddress;
Expand All @@ -24,6 +25,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
Expand All @@ -43,7 +45,6 @@
import software.amazon.awssdk.core.util.SdkUserAgent;
import software.amazon.awssdk.regions.util.ResourcesEndpointProvider;
import software.amazon.awssdk.regions.util.ResourcesEndpointRetryPolicy;
import software.amazon.awssdk.utils.ComparableUtils;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.awssdk.utils.ToString;
import software.amazon.awssdk.utils.Validate;
Expand Down Expand Up @@ -85,6 +86,9 @@ public final class ContainerCredentialsProvider
private static final List<String> VALID_LOOP_BACK_IPV4 = Arrays.asList(ECS_CONTAINER_HOST, EKS_CONTAINER_HOST_IPV4);
private static final List<String> VALID_LOOP_BACK_IPV6 = Arrays.asList(EKS_CONTAINER_HOST_IPV6);

private static final Duration DEFAULT_STALE_TIME = Duration.ofMinutes(1);
private static final Duration DEFAULT_PREFETCH_TIME = Duration.ofMinutes(5);

private final String endpoint;
private final HttpCredentialsLoader httpCredentialsLoader;
private final CachedSupplier<AwsCredentials> credentialsCache;
Expand All @@ -94,6 +98,8 @@ public final class ContainerCredentialsProvider
private final String asyncThreadName;
private final String sourceChain;
private final String providerName;
private final Duration staleTime;
private final Duration prefetchTime;

/**
* @see #builder()
Expand All @@ -107,16 +113,22 @@ private ContainerCredentialsProvider(BuilderImpl builder) {
? PROVIDER_NAME
: builder.sourceChain + "," + PROVIDER_NAME;
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);
Validate.isTrue(this.staleTime.compareTo(this.prefetchTime) < 0,
"staleTime (%s) must be less than prefetchTime (%s).", this.staleTime, this.prefetchTime);

if (Boolean.TRUE.equals(builder.asyncCredentialUpdateEnabled)) {
Validate.paramNotBlank(builder.asyncThreadName, "asyncThreadName");
this.credentialsCache = CachedSupplier.builder(this::refreshCredentials)
.cachedValueName(toString())
.prefetchStrategy(new NonBlocking(builder.asyncThreadName))
.staleValueBehavior(ALLOW)
.build();
} else {
this.credentialsCache = CachedSupplier.builder(this::refreshCredentials)
.cachedValueName(toString())
.staleValueBehavior(ALLOW)
.build();
}
}
Expand Down Expand Up @@ -153,19 +165,14 @@ private Instant staleTime(Instant expiration) {
return null;
}

return expiration.minus(1, ChronoUnit.MINUTES);
return expiration.minus(staleTime);
}

private Instant prefetchTime(Instant expiration) {
Instant oneHourFromNow = Instant.now().plus(1, ChronoUnit.HOURS);

if (expiration == null) {
return oneHourFromNow;
return Instant.now().plus(1, ChronoUnit.HOURS);
}

Instant fifteenMinutesBeforeExpiration = expiration.minus(15, ChronoUnit.MINUTES);

return ComparableUtils.minimum(oneHourFromNow, fifteenMinutesBeforeExpiration);
return expiration.minus(prefetchTime);
}

@Override
Expand Down Expand Up @@ -320,13 +327,47 @@ public boolean isMetadataServiceEndpoint(String host) {
*/
public interface Builder extends HttpCredentialsProvider.Builder<ContainerCredentialsProvider, Builder>,
CopyableBuilder<Builder, ContainerCredentialsProvider> {

/**
* Configure the amount of time, relative to credential expiration, that defines the mandatory refresh window. When
* the cached credentials are within this window (i.e., their remaining lifetime is less than this duration), the
* provider will block all callers until a refresh attempt completes. If the refresh attempt fails, the provider
* returns the cached credentials and will not attempt another refresh until a backoff period has elapsed.
*
* <p>This value must be less than {@link #prefetchTime(Duration)}.
*
* <p>By default, this is 1 minute.
*
* @param staleTime the duration before expiration that triggers mandatory (blocking) refresh
*/
Builder staleTime(Duration staleTime);

/**
* Configure the amount of time, relative to credential expiration, that defines the advisory refresh window. When
* the cached credentials are within this window (i.e., their remaining lifetime is less than this duration), the
* provider will attempt to refresh them proactively. If the refresh fails, the provider returns the existing cached
* credentials without error and will not attempt another refresh until a backoff period has elapsed.
*
* <p>When {@link #asyncCredentialUpdateEnabled(Boolean)} is true, advisory refreshes happen in a background thread
* and callers immediately receive the current cached credentials. When it is false, one caller will block to perform
* the refresh while other callers receive the current cached credentials.
*
* <p>This value must be greater than {@link #staleTime(Duration)}.
*
* <p>By default, this is 5 minutes.
*
* @param prefetchTime the duration before expiration that triggers advisory (proactive) refresh
*/
Builder prefetchTime(Duration prefetchTime);
}

private static final class BuilderImpl implements Builder {
private String endpoint;
private Boolean asyncCredentialUpdateEnabled;
private String asyncThreadName;
private String sourceChain;
private Duration staleTime;
private Duration prefetchTime;

private BuilderImpl() {
asyncThreadName("container-credentials-provider");
Expand All @@ -337,6 +378,8 @@ private BuilderImpl(ContainerCredentialsProvider credentialsProvider) {
this.asyncCredentialUpdateEnabled = credentialsProvider.asyncCredentialUpdateEnabled;
this.asyncThreadName = credentialsProvider.asyncThreadName;
this.sourceChain = credentialsProvider.sourceChain;
this.staleTime = credentialsProvider.staleTime;
this.prefetchTime = credentialsProvider.prefetchTime;
}

@Override
Expand Down Expand Up @@ -369,6 +412,26 @@ public void setAsyncThreadName(String asyncThreadName) {
asyncThreadName(asyncThreadName);
}

@Override
public Builder staleTime(Duration staleTime) {
this.staleTime = staleTime;
return this;
}

public void setStaleTime(Duration staleTime) {
staleTime(staleTime);
}

@Override
public Builder prefetchTime(Duration prefetchTime) {
this.prefetchTime = prefetchTime;
return this;
}

public void setPrefetchTime(Duration prefetchTime) {
prefetchTime(prefetchTime);
}

/**
* An optional string denoting previous credentials providers that are chained with this one.
* <p><b>Note:</b> This method is primarily intended for use by AWS SDK internal components
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
public interface HttpCredentialsProvider extends AwsCredentialsProvider, SdkAutoCloseable {
interface Builder<TypeToBuildT extends HttpCredentialsProvider, BuilderT extends Builder<?, ?>> {
/**
* Configure whether the provider should fetch credentials asynchronously in the background. If this is true,
* threads are less likely to block when credentials are loaded, but additional resources are used to maintain
* the provider.
* Configure whether the provider should fetch credentials asynchronously in the background. When enabled, a
* dedicated thread performs credential refreshes during the advisory refresh window, so that callers are less
* likely to block waiting for credentials. Additional resources (a thread) are used to maintain the provider.
*
* <p>By default, this is disabled.</p>
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package software.amazon.awssdk.auth.credentials;

import static java.time.temporal.ChronoUnit.MINUTES;
import static software.amazon.awssdk.utils.ComparableUtils.maximum;
import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
import static software.amazon.awssdk.utils.cache.CachedSupplier.StaleValueBehavior.ALLOW;

Expand Down Expand Up @@ -93,6 +92,8 @@ public final class InstanceProfileCredentialsProvider

private final Duration staleTime;

private final Duration prefetchTime;

private final String sourceChain;
private final String providerName;

Expand Down Expand Up @@ -120,7 +121,10 @@ private InstanceProfileCredentialsProvider(BuilderImpl builder) {
.profileName(profileName)
.build();

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

if (Boolean.TRUE.equals(builder.asyncCredentialUpdateEnabled)) {
Validate.paramNotBlank(builder.asyncThreadName, "asyncThreadName");
Expand Down Expand Up @@ -204,7 +208,12 @@ private Instant prefetchTime(Instant expiration) {
return null;
}

return now.plus(maximum(timeUntilExpiration.dividedBy(2), Duration.ofMinutes(5)));
// Advisory refresh window: use configured prefetchTime before expiry.
// If remaining lifetime < prefetchTime, refresh immediately.
if (timeUntilExpiration.compareTo(prefetchTime) < 0) {
return now;
}
return expiration.minus(prefetchTime);
}

@Override
Expand Down Expand Up @@ -355,17 +364,37 @@ public interface Builder extends HttpCredentialsProvider.Builder<InstanceProfile
Builder profileName(String profileName);

/**
* Configure the amount of time before the moment of expiration of credentials for which to consider the credentials to
* be stale. A higher value can lead to a higher rate of request being made to the Amazon EC2 Instance Metadata Service.
* The default is 1 sec.
* <p>Increasing this value to a higher value (10s or more) may help with situations where a higher load on the instance
* metadata service causes it to return 503s error, for which the SDK may not be able to recover fast enough and
* returns expired credentials.
* Configure the amount of time, relative to credential expiration, that defines the mandatory refresh window. When
* the cached credentials are within this window (i.e., their remaining lifetime is less than this duration), the
* provider will block all callers until a refresh attempt completes. If the refresh attempt fails, the provider
* returns the cached credentials and will not attempt another refresh until a backoff period has elapsed.
*
* <p>This value must be less than {@link #prefetchTime(Duration)}.
*
* @param duration the amount of time before expiration for when to consider the credentials to be stale and need refresh
* <p>By default, this is 1 minute.
*
* @param duration the duration before expiration that triggers mandatory (blocking) refresh
*/
Builder staleTime(Duration duration);

/**
* Configure the amount of time, relative to credential expiration, that defines the advisory refresh window. When
* the cached credentials are within this window (i.e., their remaining lifetime is less than this duration), the
* provider will attempt to refresh them proactively. If the refresh fails, the provider returns the existing cached
* credentials without error and will not attempt another refresh until a backoff period has elapsed.
*
* <p>When {@link #asyncCredentialUpdateEnabled(Boolean)} is true, advisory refreshes happen in a background thread
* and callers immediately receive the current cached credentials. When it is false, one caller will block to perform
* the refresh while other callers receive the current cached credentials.
*
* <p>This value must be greater than {@link #staleTime(Duration)}.
*
* <p>By default, this is 5 minutes.
*
* @param duration the duration before expiration that triggers advisory (proactive) refresh
*/
Builder prefetchTime(Duration duration);

/**
* Build a {@link InstanceProfileCredentialsProvider} from the provided configuration.
*/
Expand All @@ -382,6 +411,7 @@ static final class BuilderImpl implements Builder {
private Supplier<ProfileFile> profileFile;
private String profileName;
private Duration staleTime;
private Duration prefetchTime;
private String sourceChain;

private BuilderImpl() {
Expand All @@ -396,6 +426,7 @@ private BuilderImpl(InstanceProfileCredentialsProvider provider) {
this.profileFile = provider.profileFile;
this.profileName = provider.profileName;
this.staleTime = provider.staleTime;
this.prefetchTime = provider.prefetchTime;
this.sourceChain = provider.sourceChain;
}

Expand Down Expand Up @@ -475,6 +506,16 @@ public void setStaleTime(Duration duration) {
staleTime(duration);
}

@Override
public Builder prefetchTime(Duration duration) {
this.prefetchTime = duration;
return this;
}

public void setPrefetchTime(Duration duration) {
prefetchTime(duration);
}

/**
* An optional string denoting previous credentials providers that are chained with this one.
* <p><b>Note:</b> This method is primarily intended for use by AWS SDK internal components
Expand Down
Loading
Loading