Skip to content

Commit

Permalink
Add option to disable EC2 metadata (IMDS) calls without token (#4866)
Browse files Browse the repository at this point in the history
* Add option to disable IMDS v1 fallback when token is not returned (#4840)
* Add support for disable IMDS v1 fallback for regions
  • Loading branch information
cenedhryn authored Feb 1, 2024
1 parent 2fdfa3b commit c7417f0
Show file tree
Hide file tree
Showing 14 changed files with 795 additions and 219 deletions.
6 changes: 6 additions & 0 deletions .changes/next-release/feature-AWSSDKforJavav2-2c52c12.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "AWS SDK for Java v2",
"contributor": "",
"description": "Adds setting to disable making EC2 Instance Metadata Service (IMDS) calls without a token header when prefetching a token does not work. This feature can be configured through environment variables (AWS_EC2_METADATA_V1_DISABLED), system property (aws.disableEc2MetadataV1) or AWS config file (ec2_metadata_v1_disabled). When you configure this setting to true, no calls without token headers will be made to IMDS."
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
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;
Expand All @@ -40,6 +41,7 @@
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;
Expand All @@ -53,10 +55,13 @@

/**
* Credentials provider implementation that loads credentials from the Amazon EC2 Instance Metadata Service.
*
* <P>
* <p>
* 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.
* <p>
* 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
Expand All @@ -73,6 +78,7 @@ public final class InstanceProfileCredentialsProvider
private final Clock clock;
private final String endpoint;
private final Ec2MetadataConfigProvider configProvider;
private final Ec2MetadataDisableV1Resolver ec2MetadataDisableV1Resolver;
private final HttpCredentialsLoader httpCredentialsLoader;
private final CachedSupplier<AwsCredentials> credentialsCache;

Expand All @@ -92,15 +98,18 @@ private InstanceProfileCredentialsProvider(BuilderImpl builder) {
this.endpoint = builder.endpoint;
this.asyncCredentialUpdateEnabled = builder.asyncCredentialUpdateEnabled;
this.asyncThreadName = builder.asyncThreadName;
this.profileFile = builder.profileFile;
this.profileName = builder.profileName;
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();
this.configProvider =
Ec2MetadataConfigProvider.builder()
.profileFile(builder.profileFile)
.profileName(builder.profileName)
.profileFile(profileFile)
.profileName(profileName)
.build();
this.ec2MetadataDisableV1Resolver = Ec2MetadataDisableV1Resolver.create(profileFile, profileName);

if (Boolean.TRUE.equals(builder.asyncCredentialUpdateEnabled)) {
Validate.paramNotBlank(builder.asyncThreadName, "asyncThreadName");
Expand Down Expand Up @@ -135,7 +144,6 @@ public static InstanceProfileCredentialsProvider create() {
return builder().build();
}


@Override
public AwsCredentials resolveCredentials() {
return credentialsCache.get();
Expand Down Expand Up @@ -225,17 +233,15 @@ private String getToken(String imdsHostname) {
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();
}

log.debug(() -> "Ignoring non-fatal exception while attempting to load metadata token from instance profile.", e);
return null;
return handleTokenErrorResponse(e);
} catch (Exception e) {
log.debug(() -> "Ignoring non-fatal exception while attempting to load metadata token from instance profile.", e);
return null;
return handleTokenErrorResponse(e);
}
}

Expand All @@ -247,6 +253,27 @@ private URI getTokenEndpoint(String imdsHostname) {
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),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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.internal;

import java.util.Optional;
import java.util.function.Supplier;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.core.SdkSystemSetting;
import software.amazon.awssdk.profiles.ProfileFile;
import software.amazon.awssdk.profiles.ProfileProperty;
import software.amazon.awssdk.utils.Lazy;
import software.amazon.awssdk.utils.OptionalUtils;

@SdkInternalApi
public final class Ec2MetadataDisableV1Resolver {
private final Supplier<ProfileFile> profileFile;
private final String profileName;
private final Lazy<Boolean> resolvedValue;

private Ec2MetadataDisableV1Resolver(Supplier<ProfileFile> profileFile, String profileName) {
this.profileFile = profileFile;
this.profileName = profileName;
this.resolvedValue = new Lazy<>(this::doResolve);
}

public static Ec2MetadataDisableV1Resolver create(Supplier<ProfileFile> profileFile, String profileName) {
return new Ec2MetadataDisableV1Resolver(profileFile, profileName);
}

public boolean resolve() {
return resolvedValue.getValue();
}

public boolean doResolve() {
return OptionalUtils.firstPresent(fromSystemSettings(),
() -> fromProfileFile(profileFile, profileName))
.orElse(false);
}

private static Optional<Boolean> fromSystemSettings() {
return SdkSystemSetting.AWS_EC2_METADATA_V1_DISABLED.getBooleanValue();
}

private static Optional<Boolean> fromProfileFile(Supplier<ProfileFile> profileFile, String profileName) {
return profileFile.get()
.profile(profileName)
.flatMap(p -> p.booleanProperty(ProfileProperty.EC2_METADATA_V1_DISABLED));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -262,15 +262,10 @@ private AwsCredentialsProvider credentialSourceCredentialProvider(CredentialSour
case ECS_CONTAINER:
return ContainerCredentialsProvider.builder().build();
case EC2_INSTANCE_METADATA:
// The IMDS credentials provider should source the endpoint config properties from the currently active profile
Ec2MetadataConfigProvider configProvider = Ec2MetadataConfigProvider.builder()
.profileFile(() -> profileFile)
.profileName(name)
.build();

return InstanceProfileCredentialsProvider.builder()
.endpoint(configProvider.getEndpoint())
.build();
.profileFile(profileFile)
.profileName(name)
.build();
case ENVIRONMENT:
return AwsCredentialsProviderChain.builder()
.addCredentialsProvider(SystemPropertyCredentialsProvider.create())
Expand Down
Loading

0 comments on commit c7417f0

Please sign in to comment.