Skip to content

Commit c7417f0

Browse files
authored
Add option to disable EC2 metadata (IMDS) calls without token (#4866)
* Add option to disable IMDS v1 fallback when token is not returned (#4840) * Add support for disable IMDS v1 fallback for regions
1 parent 2fdfa3b commit c7417f0

File tree

14 files changed

+795
-219
lines changed

14 files changed

+795
-219
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"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."
6+
}

core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java

+39-12
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import software.amazon.awssdk.annotations.SdkPublicApi;
3232
import software.amazon.awssdk.annotations.SdkTestInternalApi;
3333
import software.amazon.awssdk.auth.credentials.internal.Ec2MetadataConfigProvider;
34+
import software.amazon.awssdk.auth.credentials.internal.Ec2MetadataDisableV1Resolver;
3435
import software.amazon.awssdk.auth.credentials.internal.HttpCredentialsLoader;
3536
import software.amazon.awssdk.auth.credentials.internal.HttpCredentialsLoader.LoadedCredentials;
3637
import software.amazon.awssdk.auth.credentials.internal.StaticResourcesEndpointProvider;
@@ -40,6 +41,7 @@
4041
import software.amazon.awssdk.profiles.ProfileFile;
4142
import software.amazon.awssdk.profiles.ProfileFileSupplier;
4243
import software.amazon.awssdk.profiles.ProfileFileSystemSetting;
44+
import software.amazon.awssdk.profiles.ProfileProperty;
4345
import software.amazon.awssdk.regions.util.HttpResourcesUtils;
4446
import software.amazon.awssdk.regions.util.ResourcesEndpointProvider;
4547
import software.amazon.awssdk.utils.Logger;
@@ -53,10 +55,13 @@
5355

5456
/**
5557
* Credentials provider implementation that loads credentials from the Amazon EC2 Instance Metadata Service.
56-
*
57-
* <P>
58+
* <p>
5859
* If {@link SdkSystemSetting#AWS_EC2_METADATA_DISABLED} is set to true, it will not try to load
5960
* credentials from EC2 metadata service and will return null.
61+
* <p>
62+
* If {@link SdkSystemSetting#AWS_EC2_METADATA_V1_DISABLED} or {@link ProfileProperty#EC2_METADATA_V1_DISABLED}
63+
* is set to true, credentials will only be loaded from EC2 metadata service if a token is successfully retrieved -
64+
* fallback to load credentials without a token will be disabled.
6065
*/
6166
@SdkPublicApi
6267
public final class InstanceProfileCredentialsProvider
@@ -73,6 +78,7 @@ public final class InstanceProfileCredentialsProvider
7378
private final Clock clock;
7479
private final String endpoint;
7580
private final Ec2MetadataConfigProvider configProvider;
81+
private final Ec2MetadataDisableV1Resolver ec2MetadataDisableV1Resolver;
7682
private final HttpCredentialsLoader httpCredentialsLoader;
7783
private final CachedSupplier<AwsCredentials> credentialsCache;
7884

@@ -92,15 +98,18 @@ private InstanceProfileCredentialsProvider(BuilderImpl builder) {
9298
this.endpoint = builder.endpoint;
9399
this.asyncCredentialUpdateEnabled = builder.asyncCredentialUpdateEnabled;
94100
this.asyncThreadName = builder.asyncThreadName;
95-
this.profileFile = builder.profileFile;
96-
this.profileName = builder.profileName;
101+
this.profileFile = Optional.ofNullable(builder.profileFile)
102+
.orElseGet(() -> ProfileFileSupplier.fixedProfileFile(ProfileFile.defaultProfileFile()));
103+
this.profileName = Optional.ofNullable(builder.profileName)
104+
.orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow);
97105

98106
this.httpCredentialsLoader = HttpCredentialsLoader.create();
99107
this.configProvider =
100108
Ec2MetadataConfigProvider.builder()
101-
.profileFile(builder.profileFile)
102-
.profileName(builder.profileName)
109+
.profileFile(profileFile)
110+
.profileName(profileName)
103111
.build();
112+
this.ec2MetadataDisableV1Resolver = Ec2MetadataDisableV1Resolver.create(profileFile, profileName);
104113

105114
if (Boolean.TRUE.equals(builder.asyncCredentialUpdateEnabled)) {
106115
Validate.paramNotBlank(builder.asyncThreadName, "asyncThreadName");
@@ -135,7 +144,6 @@ public static InstanceProfileCredentialsProvider create() {
135144
return builder().build();
136145
}
137146

138-
139147
@Override
140148
public AwsCredentials resolveCredentials() {
141149
return credentialsCache.get();
@@ -225,17 +233,15 @@ private String getToken(String imdsHostname) {
225233
return HttpResourcesUtils.instance().readResource(tokenEndpoint, "PUT");
226234
} catch (SdkServiceException e) {
227235
if (e.statusCode() == 400) {
236+
228237
throw SdkClientException.builder()
229238
.message("Unable to fetch metadata token.")
230239
.cause(e)
231240
.build();
232241
}
233-
234-
log.debug(() -> "Ignoring non-fatal exception while attempting to load metadata token from instance profile.", e);
235-
return null;
242+
return handleTokenErrorResponse(e);
236243
} catch (Exception e) {
237-
log.debug(() -> "Ignoring non-fatal exception while attempting to load metadata token from instance profile.", e);
238-
return null;
244+
return handleTokenErrorResponse(e);
239245
}
240246
}
241247

@@ -247,6 +253,27 @@ private URI getTokenEndpoint(String imdsHostname) {
247253
return URI.create(finalHost + TOKEN_RESOURCE);
248254
}
249255

256+
private String handleTokenErrorResponse(Exception e) {
257+
if (isInsecureFallbackDisabled()) {
258+
String message = String.format("Failed to retrieve IMDS token, and fallback to IMDS v1 is disabled via the "
259+
+ "%s system property, %s environment variable, or %s configuration file profile"
260+
+ " setting.",
261+
SdkSystemSetting.AWS_EC2_METADATA_V1_DISABLED.environmentVariable(),
262+
SdkSystemSetting.AWS_EC2_METADATA_V1_DISABLED.property(),
263+
ProfileProperty.EC2_METADATA_V1_DISABLED);
264+
throw SdkClientException.builder()
265+
.message(message)
266+
.cause(e)
267+
.build();
268+
}
269+
log.debug(() -> "Ignoring non-fatal exception while attempting to load metadata token from instance profile.", e);
270+
return null;
271+
}
272+
273+
private boolean isInsecureFallbackDisabled() {
274+
return ec2MetadataDisableV1Resolver.resolve();
275+
}
276+
250277
private String[] getSecurityCredentials(String imdsHostname, String metadataToken) {
251278
ResourcesEndpointProvider securityCredentialsEndpoint =
252279
new StaticResourcesEndpointProvider(URI.create(imdsHostname + SECURITY_CREDENTIALS_RESOURCE),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.auth.credentials.internal;
17+
18+
import java.util.Optional;
19+
import java.util.function.Supplier;
20+
import software.amazon.awssdk.annotations.SdkInternalApi;
21+
import software.amazon.awssdk.core.SdkSystemSetting;
22+
import software.amazon.awssdk.profiles.ProfileFile;
23+
import software.amazon.awssdk.profiles.ProfileProperty;
24+
import software.amazon.awssdk.utils.Lazy;
25+
import software.amazon.awssdk.utils.OptionalUtils;
26+
27+
@SdkInternalApi
28+
public final class Ec2MetadataDisableV1Resolver {
29+
private final Supplier<ProfileFile> profileFile;
30+
private final String profileName;
31+
private final Lazy<Boolean> resolvedValue;
32+
33+
private Ec2MetadataDisableV1Resolver(Supplier<ProfileFile> profileFile, String profileName) {
34+
this.profileFile = profileFile;
35+
this.profileName = profileName;
36+
this.resolvedValue = new Lazy<>(this::doResolve);
37+
}
38+
39+
public static Ec2MetadataDisableV1Resolver create(Supplier<ProfileFile> profileFile, String profileName) {
40+
return new Ec2MetadataDisableV1Resolver(profileFile, profileName);
41+
}
42+
43+
public boolean resolve() {
44+
return resolvedValue.getValue();
45+
}
46+
47+
public boolean doResolve() {
48+
return OptionalUtils.firstPresent(fromSystemSettings(),
49+
() -> fromProfileFile(profileFile, profileName))
50+
.orElse(false);
51+
}
52+
53+
private static Optional<Boolean> fromSystemSettings() {
54+
return SdkSystemSetting.AWS_EC2_METADATA_V1_DISABLED.getBooleanValue();
55+
}
56+
57+
private static Optional<Boolean> fromProfileFile(Supplier<ProfileFile> profileFile, String profileName) {
58+
return profileFile.get()
59+
.profile(profileName)
60+
.flatMap(p -> p.booleanProperty(ProfileProperty.EC2_METADATA_V1_DISABLED));
61+
}
62+
63+
}

core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/ProfileCredentialsUtils.java

+3-8
Original file line numberDiff line numberDiff line change
@@ -262,15 +262,10 @@ private AwsCredentialsProvider credentialSourceCredentialProvider(CredentialSour
262262
case ECS_CONTAINER:
263263
return ContainerCredentialsProvider.builder().build();
264264
case EC2_INSTANCE_METADATA:
265-
// The IMDS credentials provider should source the endpoint config properties from the currently active profile
266-
Ec2MetadataConfigProvider configProvider = Ec2MetadataConfigProvider.builder()
267-
.profileFile(() -> profileFile)
268-
.profileName(name)
269-
.build();
270-
271265
return InstanceProfileCredentialsProvider.builder()
272-
.endpoint(configProvider.getEndpoint())
273-
.build();
266+
.profileFile(profileFile)
267+
.profileName(name)
268+
.build();
274269
case ENVIRONMENT:
275270
return AwsCredentialsProviderChain.builder()
276271
.addCredentialsProvider(SystemPropertyCredentialsProvider.create())

0 commit comments

Comments
 (0)