Skip to content

Commit 4a76367

Browse files
committed
Additional Changes
-created a new integration test file for IMDS extended url separating it from legacy -Included the status code to the fallback logic
1 parent 76be465 commit 4a76367

File tree

7 files changed

+150
-7
lines changed

7 files changed

+150
-7
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"type": "feature",
3-
"category": "AWS EC2",
3+
"category": "AWS SDK for Java v2",
44
"contributor": "",
55
"description": "EC2 IMDS Changes to Support Account ID"
66
}

core/auth/src/it/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderIntegrationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public class InstanceProfileCredentialsProviderIntegrationTest {
3535
/** Starts up the mock EC2 Instance Metadata Service. */
3636
@Before
3737
public void setUp() throws Exception {
38-
mockServer = new EC2MetadataServiceMock("/latest/meta-data/iam/security-credentials-extended/");
38+
mockServer = new EC2MetadataServiceMock("/latest/meta-data/iam/security-credentials/");
3939
mockServer.start();
4040
}
4141

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,6 @@ private RefreshResult<AwsCredentials> refreshCredentials() {
168168

169169
try {
170170
LoadedCredentials credentials = httpCredentialsLoader.loadCredentials(createEndpointProvider());
171-
if (apiVersion == ApiVersion.UNKNOWN) {
172-
apiVersion = ApiVersion.EXTENDED;
173-
}
174-
175171
Instant expiration = credentials.getExpiration().orElse(null);
176172
log.debug(() -> "Loaded credentials from IMDS with expiration time of " + expiration);
177173

@@ -180,7 +176,7 @@ private RefreshResult<AwsCredentials> refreshCredentials() {
180176
.prefetchTime(prefetchTime(expiration))
181177
.build();
182178
} catch (Ec2MetadataClientException e) {
183-
if (apiVersion == ApiVersion.UNKNOWN) {
179+
if (e.statusCode() == 404 && apiVersion == ApiVersion.EXTENDED) {
184180
apiVersion = ApiVersion.LEGACY;
185181
resolvedProfile = null;
186182
return refreshCredentials();

core/auth/src/test/java/software/amazon/awssdk/auth/credentials/EC2MetadataServiceMock.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class EC2MetadataServiceMock {
4242
"Content-Type: text/html\r\n" +
4343
"Content-Length: ";
4444
private static final String OUTPUT_END_OF_HEADERS = "\r\n\r\n";
45+
private static final String EXTENDED_PATH = "/latest/meta-data/iam/security-credentials-extended/";
4546
private final String securityCredentialsResource;
4647
private EC2MockMetadataServiceListenerThread hosmMockServerThread;
4748

@@ -140,6 +141,15 @@ public void run() {
140141
String[] strings = requestLine.split(" ");
141142
String resourcePath = strings[1];
142143

144+
// Return 404 for extended path when in legacy mode
145+
if (!credentialsResource.equals(EXTENDED_PATH) &&
146+
(resourcePath.equals(EXTENDED_PATH) || resourcePath.startsWith(EXTENDED_PATH))) {
147+
String notFound = "HTTP/1.1 404 Not Found\r\n" +
148+
"Content-Length: 0\r\n" +
149+
"\r\n";
150+
outputStream.write(notFound.getBytes());
151+
continue;
152+
}
143153

144154
String httpResponse = null;
145155

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20+
import static org.junit.Assert.assertNotNull;
21+
import static org.junit.Assert.fail;
22+
23+
import org.junit.After;
24+
import org.junit.Before;
25+
import org.junit.Test;
26+
import software.amazon.awssdk.core.SdkSystemSetting;
27+
import software.amazon.awssdk.core.exception.SdkClientException;
28+
29+
/**
30+
* Tests for {@link InstanceProfileCredentialsProvider} using the extended IMDS path.
31+
*/
32+
public class InstanceProfileCredentialsProviderExtendedIntegrationTest {
33+
private static final String EXTENDED_PATH = "/latest/meta-data/iam/security-credentials-extended/";
34+
private EC2MetadataServiceMock mockServer;
35+
36+
@Before
37+
public void setUp() throws Exception {
38+
mockServer = new EC2MetadataServiceMock(EXTENDED_PATH);
39+
mockServer.start();
40+
}
41+
42+
@After
43+
public void tearDown() {
44+
mockServer.stop();
45+
}
46+
47+
@Test
48+
public void resolveCredentials_withExtendedPath_includesAccountId() {
49+
mockServer.setAvailableSecurityCredentials("test-role");
50+
mockServer.setResponseFileName("sessionResponseExtended");
51+
52+
InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider.builder().build();
53+
AwsCredentials credentials = provider.resolveCredentials();
54+
55+
assertThat(credentials.accountId()).isPresent();
56+
assertThat(credentials.accountId().get()).isEqualTo("123456789012");
57+
}
58+
59+
@Test
60+
public void resolveCredentials_withExtendedPath_hasCorrectCredentials() {
61+
mockServer.setAvailableSecurityCredentials("test-role");
62+
mockServer.setResponseFileName("sessionResponseExtended");
63+
64+
InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider.builder().build();
65+
AwsCredentials credentials = provider.resolveCredentials();
66+
67+
assertThat(credentials).isInstanceOf(AwsSessionCredentials.class);
68+
assertThat(credentials.accessKeyId()).isEqualTo("ACCESS_KEY_ID");
69+
assertThat(credentials.secretAccessKey()).isEqualTo("SECRET_ACCESS_KEY");
70+
assertThat(((AwsSessionCredentials) credentials).sessionToken()).isEqualTo("TOKEN");
71+
}
72+
73+
@Test
74+
public void testSessionCredentials_MultipleInstanceProfiles() {
75+
mockServer.setAvailableSecurityCredentials("test-credentials");
76+
mockServer.setResponseFileName("sessionResponseExtended");
77+
78+
InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider.builder().build();
79+
AwsCredentials credentials = provider.resolveCredentials();
80+
81+
assertThat(credentials).isInstanceOf(AwsSessionCredentials.class);
82+
assertThat(credentials.accessKeyId()).isEqualTo("ACCESS_KEY_ID");
83+
assertThat(credentials.secretAccessKey()).isEqualTo("SECRET_ACCESS_KEY");
84+
assertThat(((AwsSessionCredentials) credentials).sessionToken()).isEqualTo("TOKEN");
85+
}
86+
87+
@Test
88+
public void testNoInstanceProfiles() throws Exception {
89+
mockServer.setResponseFileName("sessionResponseExtended");
90+
mockServer.setAvailableSecurityCredentials("");
91+
92+
try (InstanceProfileCredentialsProvider credentialsProvider = InstanceProfileCredentialsProvider.create()) {
93+
94+
try {
95+
credentialsProvider.resolveCredentials();
96+
fail("Expected an SdkClientException, but wasn't thrown");
97+
} catch (SdkClientException ace) {
98+
assertNotNull(ace.getMessage());
99+
}
100+
}
101+
}
102+
103+
@Test
104+
public void resolveCredentials_withDisabledMetadata_throwsException() {
105+
System.setProperty(SdkSystemSetting.AWS_EC2_METADATA_DISABLED.property(), "true");
106+
try {
107+
InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider.builder().build();
108+
assertThatThrownBy(() -> provider.resolveCredentials())
109+
.isInstanceOf(SdkClientException.class)
110+
.hasMessageContaining("IMDS credentials have been disabled");
111+
} finally {
112+
System.clearProperty(SdkSystemSetting.AWS_EC2_METADATA_DISABLED.property());
113+
}
114+
}
115+
116+
@Test
117+
public void resolveCredentials_withNoAvailableCredentials_throwsException() {
118+
mockServer.setAvailableSecurityCredentials("");
119+
mockServer.setResponseFileName("sessionResponseExtended");
120+
121+
InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider.builder().build();
122+
assertThatThrownBy(() -> provider.resolveCredentials())
123+
.isInstanceOf(SdkClientException.class)
124+
.hasMessageContaining("Failed to load credentials from IMDS.");
125+
}
126+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"Code" : "Success",
3+
"LastUpdated" : "2025-02-13T18:00:00Z",
4+
"Type" : "AWS-HMAC",
5+
"AccessKeyId" : "ACCESS_KEY_ID",
6+
"SecretAccessKey" : "SECRET_ACCESS_KEY",
7+
"Token" : "TOKEN",
8+
"Expiration" : "2025-02-13T19:00:00Z",
9+
"AccountId" : "123456789012"
10+
}

core/regions/src/main/java/software/amazon/awssdk/regions/util/HttpResourcesUtils.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ public String readResource(ResourcesEndpointProvider endpointProvider, String me
120120
} else if (statusCode == HttpURLConnection.HTTP_NOT_FOUND) {
121121
// This is to preserve existing behavior of EC2 Instance metadata service.
122122
throw Ec2MetadataClientException.builder()
123+
.statusCode(404)
123124
.message("The requested metadata is not found at " + connection.getURL())
124125
.build();
125126
} else {

0 commit comments

Comments
 (0)