All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider Maven / Gradle / Ivy

Go to download

The AWS SDK for Java - Auth module holds the classes that are used for authentication with services

There is a newer version: 2.29.15
Show newest version
/*
 * 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.
 */

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;

import java.net.URI;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.SdkTestInternalApi;
import software.amazon.awssdk.auth.credentials.internal.Ec2MetadataConfigProvider;
import software.amazon.awssdk.auth.credentials.internal.Ec2MetadataDisableV1Resolver;
import software.amazon.awssdk.auth.credentials.internal.HttpCredentialsLoader;
import software.amazon.awssdk.auth.credentials.internal.HttpCredentialsLoader.LoadedCredentials;
import software.amazon.awssdk.auth.credentials.internal.StaticResourcesEndpointProvider;
import software.amazon.awssdk.core.SdkSystemSetting;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.exception.SdkServiceException;
import software.amazon.awssdk.profiles.ProfileFile;
import software.amazon.awssdk.profiles.ProfileFileSupplier;
import software.amazon.awssdk.profiles.ProfileFileSystemSetting;
import software.amazon.awssdk.profiles.ProfileProperty;
import software.amazon.awssdk.regions.util.HttpResourcesUtils;
import software.amazon.awssdk.regions.util.ResourcesEndpointProvider;
import software.amazon.awssdk.utils.Logger;
import software.amazon.awssdk.utils.ToString;
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.CachedSupplier;
import software.amazon.awssdk.utils.cache.NonBlocking;
import software.amazon.awssdk.utils.cache.RefreshResult;

/**
 * Credentials provider implementation that loads credentials from the Amazon EC2 Instance Metadata Service.
 * 

* If {@link SdkSystemSetting#AWS_EC2_METADATA_DISABLED} is set to true, it will not try to load * credentials from EC2 metadata service and will return null. *

* If {@link SdkSystemSetting#AWS_EC2_METADATA_V1_DISABLED} or {@link ProfileProperty#EC2_METADATA_V1_DISABLED} * is set to true, credentials will only be loaded from EC2 metadata service if a token is successfully retrieved - * fallback to load credentials without a token will be disabled. */ @SdkPublicApi public final class InstanceProfileCredentialsProvider implements HttpCredentialsProvider, ToCopyableBuilder { private static final Logger log = Logger.loggerFor(InstanceProfileCredentialsProvider.class); private static final String PROVIDER_NAME = "InstanceProfileCredentialsProvider"; private static final String EC2_METADATA_TOKEN_HEADER = "x-aws-ec2-metadata-token"; private static final String SECURITY_CREDENTIALS_RESOURCE = "/latest/meta-data/iam/security-credentials/"; private static final String TOKEN_RESOURCE = "/latest/api/token"; private static final String EC2_METADATA_TOKEN_TTL_HEADER = "x-aws-ec2-metadata-token-ttl-seconds"; private static final String DEFAULT_TOKEN_TTL = "21600"; private final Clock clock; private final String endpoint; private final Ec2MetadataConfigProvider configProvider; private final Ec2MetadataDisableV1Resolver ec2MetadataDisableV1Resolver; private final HttpCredentialsLoader httpCredentialsLoader; private final CachedSupplier credentialsCache; private final Boolean asyncCredentialUpdateEnabled; private final String asyncThreadName; private final Supplier profileFile; private final String profileName; /** * @see #builder() */ private InstanceProfileCredentialsProvider(BuilderImpl builder) { this.clock = builder.clock; this.endpoint = builder.endpoint; this.asyncCredentialUpdateEnabled = builder.asyncCredentialUpdateEnabled; this.asyncThreadName = builder.asyncThreadName; this.profileFile = Optional.ofNullable(builder.profileFile) .orElseGet(() -> ProfileFileSupplier.fixedProfileFile(ProfileFile.defaultProfileFile())); this.profileName = Optional.ofNullable(builder.profileName) .orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow); this.httpCredentialsLoader = HttpCredentialsLoader.create(PROVIDER_NAME); this.configProvider = Ec2MetadataConfigProvider.builder() .profileFile(profileFile) .profileName(profileName) .build(); this.ec2MetadataDisableV1Resolver = Ec2MetadataDisableV1Resolver.create(profileFile, profileName); 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) .clock(clock) .build(); } else { this.credentialsCache = CachedSupplier.builder(this::refreshCredentials) .cachedValueName(toString()) .staleValueBehavior(ALLOW) .clock(clock) .build(); } } /** * Create a builder for creating a {@link InstanceProfileCredentialsProvider}. */ public static Builder builder() { return new BuilderImpl(); } /** * Create a {@link InstanceProfileCredentialsProvider} with default values. * * @return a {@link InstanceProfileCredentialsProvider} */ public static InstanceProfileCredentialsProvider create() { return builder().build(); } @Override public AwsCredentials resolveCredentials() { return credentialsCache.get(); } private RefreshResult refreshCredentials() { if (isLocalCredentialLoadingDisabled()) { throw SdkClientException.create("IMDS credentials have been disabled by environment variable or system property."); } try { LoadedCredentials credentials = httpCredentialsLoader.loadCredentials(createEndpointProvider()); Instant expiration = credentials.getExpiration().orElse(null); log.debug(() -> "Loaded credentials from IMDS with expiration time of " + expiration); return RefreshResult.builder(credentials.getAwsCredentials()) .staleTime(staleTime(expiration)) .prefetchTime(prefetchTime(expiration)) .build(); } catch (RuntimeException e) { throw SdkClientException.create("Failed to load credentials from IMDS.", e); } } private boolean isLocalCredentialLoadingDisabled() { return SdkSystemSetting.AWS_EC2_METADATA_DISABLED.getBooleanValueOrThrow(); } private Instant staleTime(Instant expiration) { if (expiration == null) { return null; } return expiration.minusSeconds(1); } private Instant prefetchTime(Instant expiration) { Instant now = clock.instant(); if (expiration == null) { return now.plus(60, MINUTES); } Duration timeUntilExpiration = Duration.between(now, expiration); if (timeUntilExpiration.isNegative()) { // IMDS gave us a time in the past. We're already stale. Don't prefetch. return null; } return now.plus(maximum(timeUntilExpiration.dividedBy(2), Duration.ofMinutes(5))); } @Override public void close() { credentialsCache.close(); } @Override public String toString() { return ToString.create(PROVIDER_NAME); } private ResourcesEndpointProvider createEndpointProvider() { String imdsHostname = getImdsEndpoint(); String token = getToken(imdsHostname); String[] securityCredentials = getSecurityCredentials(imdsHostname, token); return new StaticResourcesEndpointProvider(URI.create(imdsHostname + SECURITY_CREDENTIALS_RESOURCE + securityCredentials[0]), getTokenHeaders(token)); } private String getImdsEndpoint() { if (endpoint != null) { return endpoint; } return configProvider.getEndpoint(); } private String getToken(String imdsHostname) { Map tokenTtlHeaders = Collections.singletonMap(EC2_METADATA_TOKEN_TTL_HEADER, DEFAULT_TOKEN_TTL); ResourcesEndpointProvider tokenEndpoint = new StaticResourcesEndpointProvider(getTokenEndpoint(imdsHostname), tokenTtlHeaders); try { return HttpResourcesUtils.instance().readResource(tokenEndpoint, "PUT"); } catch (SdkServiceException e) { if (e.statusCode() == 400) { throw SdkClientException.builder() .message("Unable to fetch metadata token.") .cause(e) .build(); } return handleTokenErrorResponse(e); } catch (Exception e) { return handleTokenErrorResponse(e); } } private URI getTokenEndpoint(String imdsHostname) { String finalHost = imdsHostname; if (finalHost.endsWith("/")) { finalHost = finalHost.substring(0, finalHost.length() - 1); } return URI.create(finalHost + TOKEN_RESOURCE); } private String handleTokenErrorResponse(Exception e) { if (isInsecureFallbackDisabled()) { String message = String.format("Failed to retrieve IMDS token, and fallback to IMDS v1 is disabled via the " + "%s system property, %s environment variable, or %s configuration file profile" + " setting.", SdkSystemSetting.AWS_EC2_METADATA_V1_DISABLED.environmentVariable(), SdkSystemSetting.AWS_EC2_METADATA_V1_DISABLED.property(), ProfileProperty.EC2_METADATA_V1_DISABLED); throw SdkClientException.builder() .message(message) .cause(e) .build(); } log.debug(() -> "Ignoring non-fatal exception while attempting to load metadata token from instance profile.", e); return null; } private boolean isInsecureFallbackDisabled() { return ec2MetadataDisableV1Resolver.resolve(); } private String[] getSecurityCredentials(String imdsHostname, String metadataToken) { ResourcesEndpointProvider securityCredentialsEndpoint = new StaticResourcesEndpointProvider(URI.create(imdsHostname + SECURITY_CREDENTIALS_RESOURCE), getTokenHeaders(metadataToken)); String securityCredentialsList = invokeSafely(() -> HttpResourcesUtils.instance().readResource(securityCredentialsEndpoint)); String[] securityCredentials = securityCredentialsList.trim().split("\n"); if (securityCredentials.length == 0) { throw SdkClientException.builder().message("Unable to load credentials path").build(); } return securityCredentials; } private Map getTokenHeaders(String metadataToken) { if (metadataToken == null) { return Collections.emptyMap(); } return Collections.singletonMap(EC2_METADATA_TOKEN_HEADER, metadataToken); } @Override public Builder toBuilder() { return new BuilderImpl(this); } /** * A builder for creating a custom a {@link InstanceProfileCredentialsProvider}. */ public interface Builder extends HttpCredentialsProvider.Builder, CopyableBuilder { /** * Configure the profile file used for loading IMDS-related configuration, like the endpoint mode (IPv4 vs IPv6). * *

By default, {@link ProfileFile#defaultProfileFile()} is used. * * @see #profileFile(Supplier) */ Builder profileFile(ProfileFile profileFile); /** * Define the mechanism for loading profile files. * * @param profileFileSupplier Supplier interface for generating a ProfileFile instance. * @see #profileFile(ProfileFile) */ Builder profileFile(Supplier profileFileSupplier); /** * Configure the profile name used for loading IMDS-related configuration, like the endpoint mode (IPv4 vs IPv6). * *

By default, {@link ProfileFileSystemSetting#AWS_PROFILE} is used. */ Builder profileName(String profileName); /** * Build a {@link InstanceProfileCredentialsProvider} from the provided configuration. */ @Override InstanceProfileCredentialsProvider build(); } @SdkTestInternalApi static final class BuilderImpl implements Builder { private Clock clock = Clock.systemUTC(); private String endpoint; private Boolean asyncCredentialUpdateEnabled; private String asyncThreadName; private Supplier profileFile; private String profileName; private BuilderImpl() { asyncThreadName("instance-profile-credentials-provider"); } private BuilderImpl(InstanceProfileCredentialsProvider provider) { this.clock = provider.clock; this.endpoint = provider.endpoint; this.asyncCredentialUpdateEnabled = provider.asyncCredentialUpdateEnabled; this.asyncThreadName = provider.asyncThreadName; this.profileFile = provider.profileFile; this.profileName = provider.profileName; } Builder clock(Clock clock) { this.clock = clock; return this; } @Override public Builder endpoint(String endpoint) { this.endpoint = endpoint; return this; } public void setEndpoint(String endpoint) { endpoint(endpoint); } @Override public Builder asyncCredentialUpdateEnabled(Boolean asyncCredentialUpdateEnabled) { this.asyncCredentialUpdateEnabled = asyncCredentialUpdateEnabled; return this; } public void setAsyncCredentialUpdateEnabled(boolean asyncCredentialUpdateEnabled) { asyncCredentialUpdateEnabled(asyncCredentialUpdateEnabled); } @Override public Builder asyncThreadName(String asyncThreadName) { this.asyncThreadName = asyncThreadName; return this; } public void setAsyncThreadName(String asyncThreadName) { asyncThreadName(asyncThreadName); } @Override public Builder profileFile(ProfileFile profileFile) { return profileFile(Optional.ofNullable(profileFile) .map(ProfileFileSupplier::fixedProfileFile) .orElse(null)); } public void setProfileFile(ProfileFile profileFile) { profileFile(profileFile); } @Override public Builder profileFile(Supplier profileFileSupplier) { this.profileFile = profileFileSupplier; return this; } public void setProfileFile(Supplier profileFileSupplier) { profileFile(profileFileSupplier); } @Override public Builder profileName(String profileName) { this.profileName = profileName; return this; } public void setProfileName(String profileName) { profileName(profileName); } @Override public InstanceProfileCredentialsProvider build() { return new InstanceProfileCredentialsProvider(this); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy