Skip to content

Commit c813681

Browse files
committed
Add option to disable IMDS v1 fallback when token is not returned for IMDS credential calls
1 parent e5e9fa8 commit c813681

File tree

9 files changed

+553
-195
lines changed

9 files changed

+553
-195
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 for credentials 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

+29-7
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
@@ -225,17 +230,15 @@ private String getToken(String imdsHostname) {
225230
return HttpResourcesUtils.instance().readResource(tokenEndpoint, "PUT");
226231
} catch (SdkServiceException e) {
227232
if (e.statusCode() == 400) {
233+
228234
throw SdkClientException.builder()
229235
.message("Unable to fetch metadata token.")
230236
.cause(e)
231237
.build();
232238
}
233-
234-
log.debug(() -> "Ignoring non-fatal exception while attempting to load metadata token from instance profile.", e);
235-
return null;
239+
return handleTokenErrorResponse(e);
236240
} catch (Exception e) {
237-
log.debug(() -> "Ignoring non-fatal exception while attempting to load metadata token from instance profile.", e);
238-
return null;
241+
return handleTokenErrorResponse(e);
239242
}
240243
}
241244

@@ -247,6 +250,25 @@ private URI getTokenEndpoint(String imdsHostname) {
247250
return URI.create(finalHost + TOKEN_RESOURCE);
248251
}
249252

253+
private String handleTokenErrorResponse(Exception e) {
254+
if (isInsecureFallbackDisabled()) {
255+
String message = String.format("Failed to retrieve IMDS token, and fallback to IMDS v1 is disabled via the %s "
256+
+ "environment variable and/or %s system property",
257+
SdkSystemSetting.AWS_EC2_METADATA_V1_DISABLED.environmentVariable(),
258+
SdkSystemSetting.AWS_EC2_METADATA_V1_DISABLED.property());
259+
throw SdkClientException.builder()
260+
.message(message)
261+
.cause(e)
262+
.build();
263+
}
264+
log.debug(() -> "Ignoring non-fatal exception while attempting to load metadata token from instance profile.", e);
265+
return null;
266+
}
267+
268+
private boolean isInsecureFallbackDisabled() {
269+
return Ec2MetadataDisableV1Resolver.create(profileFile, profileName).resolve();
270+
}
271+
250272
private String[] getSecurityCredentials(String imdsHostname, String metadataToken) {
251273
ResourcesEndpointProvider securityCredentialsEndpoint =
252274
new StaticResourcesEndpointProvider(URI.create(imdsHostname + SECURITY_CREDENTIALS_RESOURCE),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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.ProfileFileSystemSetting;
24+
import software.amazon.awssdk.profiles.ProfileProperty;
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+
32+
private Ec2MetadataDisableV1Resolver(Supplier<ProfileFile> profileFile, String profileName) {
33+
this.profileFile = profileFile;
34+
this.profileName = profileName;
35+
}
36+
37+
public static Ec2MetadataDisableV1Resolver create(Supplier<ProfileFile> profileFile, String profileName) {
38+
return new Ec2MetadataDisableV1Resolver(profileFile, profileName);
39+
}
40+
41+
public boolean resolve() {
42+
return OptionalUtils.firstPresent(fromSystemSettings(),
43+
() -> fromProfileFile(profileFile, profileName))
44+
.orElse(false);
45+
}
46+
47+
private static Optional<Boolean> fromSystemSettings() {
48+
return SdkSystemSetting.AWS_EC2_METADATA_V1_DISABLED.getBooleanValue();
49+
}
50+
51+
private static Optional<Boolean> fromProfileFile(Supplier<ProfileFile> profileFile, String profileName) {
52+
profileFile = profileFile != null ? profileFile : ProfileFile::defaultProfileFile;
53+
profileName = profileName != null ? profileName : ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow();
54+
if (profileFile.get() == null) {
55+
return Optional.empty();
56+
}
57+
return profileFile.get()
58+
.profile(profileName)
59+
.flatMap(p -> p.property(ProfileProperty.EC2_METADATA_V1_DISABLED))
60+
.map(Ec2MetadataDisableV1Resolver::safeProfileStringToBoolean);
61+
}
62+
63+
private static boolean safeProfileStringToBoolean(String value) {
64+
if (value.equalsIgnoreCase("true")) {
65+
return true;
66+
}
67+
if (value.equalsIgnoreCase("false")) {
68+
return false;
69+
}
70+
71+
throw new IllegalStateException("Profile property '" + ProfileProperty.EC2_METADATA_V1_DISABLED + "', "
72+
+ "was defined as '" + value + "', but should be 'false' or 'true'");
73+
}
74+
75+
}

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

+7-6
Original file line numberDiff line numberDiff line change
@@ -264,13 +264,14 @@ private AwsCredentialsProvider credentialSourceCredentialProvider(CredentialSour
264264
case EC2_INSTANCE_METADATA:
265265
// The IMDS credentials provider should source the endpoint config properties from the currently active profile
266266
Ec2MetadataConfigProvider configProvider = Ec2MetadataConfigProvider.builder()
267-
.profileFile(() -> profileFile)
268-
.profileName(name)
269-
.build();
270-
267+
.profileFile(() -> profileFile)
268+
.profileName(name)
269+
.build();
271270
return InstanceProfileCredentialsProvider.builder()
272-
.endpoint(configProvider.getEndpoint())
273-
.build();
271+
.endpoint(configProvider.getEndpoint())
272+
.profileFile(profileFile)
273+
.profileName(name)
274+
.build();
274275
case ENVIRONMENT:
275276
return AwsCredentialsProviderChain.builder()
276277
.addCredentialsProvider(SystemPropertyCredentialsProvider.create())

0 commit comments

Comments
 (0)