Skip to content

Commit 18cefdc

Browse files
authored
Merge pull request #1567 from dagnir/imds-token-other-calls
Add token path for other IMDS calls
2 parents 362ea4f + 4fd4dc7 commit 18cefdc

File tree

6 files changed

+279
-56
lines changed

6 files changed

+279
-56
lines changed

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

Lines changed: 3 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222
import software.amazon.awssdk.annotations.SdkPublicApi;
2323
import software.amazon.awssdk.core.SdkSystemSetting;
2424
import software.amazon.awssdk.core.exception.SdkClientException;
25-
import software.amazon.awssdk.core.exception.SdkServiceException;
25+
2626
import software.amazon.awssdk.core.internal.util.UserAgentUtils;
27+
import software.amazon.awssdk.regions.internal.util.EC2MetadataUtils;
2728
import software.amazon.awssdk.regions.util.HttpResourcesUtils;
2829
import software.amazon.awssdk.regions.util.ResourcesEndpointProvider;
29-
import software.amazon.awssdk.utils.Logger;
3030
import software.amazon.awssdk.utils.ToString;
3131

3232
/**
@@ -38,15 +38,9 @@
3838
*/
3939
@SdkPublicApi
4040
public final class InstanceProfileCredentialsProvider extends HttpCredentialsProvider {
41-
private static final Logger log = Logger.loggerFor(InstanceProfileCredentialsProvider.class);
42-
43-
private static final String TOKEN_RESOURCE_PATH = "/latest/api/token";
4441
private static final String EC2_METADATA_TOKEN_HEADER = "x-aws-ec2-metadata-token";
45-
private static final String EC2_METADATA_TOKEN_TTL_HEADER = "x-aws-ec2-metadata-token-ttl-seconds";
46-
private static final String DEFAULT_TOKEN_TTL = "21600";
4742

4843
private static final String SECURITY_CREDENTIALS_RESOURCE = "/latest/meta-data/iam/security-credentials/";
49-
private final InstanceProviderTokenEndpointProvider tokenEndpointProvider = new InstanceProviderTokenEndpointProvider();
5044

5145
/**
5246
* @see #builder()
@@ -87,28 +81,7 @@ public String toString() {
8781
}
8882

8983
private String getToken() {
90-
try {
91-
return HttpResourcesUtils.instance().readResource(getTokenEndpointProvider(), "PUT");
92-
} catch (Exception e) {
93-
log.debug(() -> "Error retrieving credentials metadata token", e);
94-
95-
boolean is400ServiceException = e instanceof SdkServiceException
96-
&& ((SdkServiceException) e).statusCode() == 400;
97-
98-
// Credentials resolution must not continue to the token-less flow for a 400
99-
if (is400ServiceException) {
100-
throw SdkClientException.builder()
101-
.message("Unable to load credentials from service endpoint")
102-
.cause(e)
103-
.build();
104-
}
105-
106-
return null;
107-
}
108-
}
109-
110-
private ResourcesEndpointProvider getTokenEndpointProvider() {
111-
return tokenEndpointProvider;
84+
return EC2MetadataUtils.getToken();
11285
}
11386

11487
private static ResourcesEndpointProvider includeTokenHeader(ResourcesEndpointProvider provider, String token) {
@@ -170,27 +143,6 @@ public Map<String, String> headers() {
170143
}
171144
}
172145

173-
private static final class InstanceProviderTokenEndpointProvider implements ResourcesEndpointProvider {
174-
@Override
175-
public URI endpoint() {
176-
String host = SdkSystemSetting.AWS_EC2_METADATA_SERVICE_ENDPOINT.getStringValueOrThrow();
177-
if (host.endsWith("/")) {
178-
host = host.substring(0, host.length() - 1);
179-
}
180-
return URI.create(host + TOKEN_RESOURCE_PATH);
181-
}
182-
183-
@Override
184-
public Map<String, String> headers() {
185-
Map<String, String> requestHeaders = new HashMap<>();
186-
requestHeaders.put("User-Agent", UserAgentUtils.getUserAgent());
187-
requestHeaders.put("Accept", "*/*");
188-
requestHeaders.put("Connection", "keep-alive");
189-
requestHeaders.put(EC2_METADATA_TOKEN_TTL_HEADER, DEFAULT_TOKEN_TTL);
190-
191-
return requestHeaders;
192-
}
193-
}
194146

195147
/**
196148
* A builder for creating a custom a {@link InstanceProfileCredentialsProvider}.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ public void resolveCredentials_queriesTokenResource_405Error_fallbackToInsecure(
168168
@Test
169169
public void resolveCredentials_queriesTokenResource_400Error_throws() {
170170
thrown.expect(SdkClientException.class);
171-
thrown.expectMessage("Unable to load credentials");
171+
thrown.expectMessage("token");
172172

173173
stubFor(put(urlPathEqualTo(TOKEN_RESOURCE_PATH)).willReturn(aResponse().withStatus(400).withBody("oops")));
174174

core/regions/src/main/java/software/amazon/awssdk/regions/internal/util/EC2MetadataUtils.java

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,14 @@
3535
import org.slf4j.Logger;
3636
import org.slf4j.LoggerFactory;
3737
import software.amazon.awssdk.annotations.SdkInternalApi;
38+
import software.amazon.awssdk.annotations.SdkTestInternalApi;
3839
import software.amazon.awssdk.core.SdkSystemSetting;
3940
import software.amazon.awssdk.core.exception.SdkClientException;
41+
import software.amazon.awssdk.core.exception.SdkServiceException;
42+
import software.amazon.awssdk.core.internal.util.UserAgentUtils;
4043
import software.amazon.awssdk.core.util.json.JacksonUtils;
4144
import software.amazon.awssdk.regions.util.HttpResourcesUtils;
45+
import software.amazon.awssdk.regions.util.ResourcesEndpointProvider;
4246

4347
/**
4448
* Utility class for retrieving Amazon EC2 instance metadata.<br>
@@ -71,12 +75,18 @@ public final class EC2MetadataUtils {
7175
private static final String EC2_METADATA_ROOT = "/latest/meta-data";
7276
private static final String EC2_USERDATA_ROOT = "/latest/user-data/";
7377
private static final String EC2_DYNAMICDATA_ROOT = "/latest/dynamic/";
78+
79+
private static final String EC2_METADATA_TOKEN_HEADER = "x-aws-ec2-metadata-token";
80+
7481
private static final int DEFAULT_QUERY_RETRIES = 3;
7582
private static final int MINIMUM_RETRY_WAIT_TIME_MILLISECONDS = 250;
7683
private static final ObjectMapper MAPPER = new ObjectMapper();
7784
private static final Logger log = LoggerFactory.getLogger(EC2MetadataUtils.class);
7885
private static Map<String, String> cache = new ConcurrentHashMap<>();
7986

87+
private static final InstanceProviderTokenEndpointProvider TOKEN_ENDPOINT_PROVIDER =
88+
new InstanceProviderTokenEndpointProvider();
89+
8090
private EC2MetadataUtils() {}
8191

8292
static {
@@ -379,6 +389,11 @@ public static List<String> getItems(String path, int tries) {
379389
return getItems(path, tries, false);
380390
}
381391

392+
@SdkTestInternalApi
393+
static void clearCache() {
394+
cache.clear();
395+
}
396+
382397
private static List<String> getItems(String path, int tries, boolean slurp) {
383398
if (tries == 0) {
384399
throw SdkClientException.builder().message("Unable to contact EC2 metadata service.").build();
@@ -389,9 +404,12 @@ private static List<String> getItems(String path, int tries, boolean slurp) {
389404
}
390405

391406
List<String> items;
407+
408+
String token = getToken();
409+
392410
try {
393411
String hostAddress = SdkSystemSetting.AWS_EC2_METADATA_SERVICE_ENDPOINT.getStringValueOrThrow();
394-
String response = HttpResourcesUtils.instance().readResource(new URI(hostAddress + path));
412+
String response = doReadResource(new URI(hostAddress + path), token);
395413
if (slurp) {
396414
items = Collections.singletonList(response);
397415
} else {
@@ -414,6 +432,31 @@ private static List<String> getItems(String path, int tries, boolean slurp) {
414432
}
415433
}
416434

435+
private static String doReadResource(URI resource, String token) throws IOException {
436+
return HttpResourcesUtils.instance().readResource(new DefaultEndpointProvider(resource, token), "GET");
437+
}
438+
439+
public static String getToken() {
440+
try {
441+
return HttpResourcesUtils.instance().readResource(TOKEN_ENDPOINT_PROVIDER, "PUT");
442+
} catch (Exception e) {
443+
444+
boolean is400ServiceException = e instanceof SdkServiceException
445+
&& ((SdkServiceException) e).statusCode() == 400;
446+
447+
// metadata resolution must not continue to the token-less flow for a 400
448+
if (is400ServiceException) {
449+
throw SdkClientException.builder()
450+
.message("Unable to fetch metadata token")
451+
.cause(e)
452+
.build();
453+
}
454+
455+
return null;
456+
}
457+
}
458+
459+
417460
private static String fetchData(String path) {
418461
return fetchData(path, false);
419462
}
@@ -428,6 +471,8 @@ private static String fetchData(String path, boolean force) {
428471
cache.put(path, getData(path));
429472
}
430473
return cache.get(path);
474+
} catch (SdkClientException e) {
475+
throw e;
431476
} catch (RuntimeException e) {
432477
return null;
433478
}
@@ -758,4 +803,33 @@ private List<String> getItems(String key) {
758803
}
759804
}
760805
}
806+
807+
private static final class DefaultEndpointProvider implements ResourcesEndpointProvider {
808+
private final URI endpoint;
809+
private final String metadataToken;
810+
811+
private DefaultEndpointProvider(URI endpoint, String metadataToken) {
812+
this.endpoint = endpoint;
813+
this.metadataToken = metadataToken;
814+
}
815+
816+
@Override
817+
public URI endpoint() {
818+
return endpoint;
819+
}
820+
821+
@Override
822+
public Map<String, String> headers() {
823+
Map<String, String> requestHeaders = new HashMap<>();
824+
requestHeaders.put("User-Agent", UserAgentUtils.getUserAgent());
825+
requestHeaders.put("Accept", "*/*");
826+
requestHeaders.put("Connection", "keep-alive");
827+
828+
if (metadataToken != null) {
829+
requestHeaders.put(EC2_METADATA_TOKEN_HEADER, metadataToken);
830+
}
831+
832+
return requestHeaders;
833+
}
834+
}
761835
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2010-2019 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.regions.internal.util;
17+
18+
import java.net.URI;
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
import software.amazon.awssdk.annotations.SdkInternalApi;
22+
import software.amazon.awssdk.core.SdkSystemSetting;
23+
import software.amazon.awssdk.core.internal.util.UserAgentUtils;
24+
import software.amazon.awssdk.regions.util.ResourcesEndpointProvider;
25+
26+
@SdkInternalApi
27+
public final class InstanceProviderTokenEndpointProvider implements ResourcesEndpointProvider {
28+
private static final String TOKEN_RESOURCE_PATH = "/latest/api/token";
29+
private static final String EC2_METADATA_TOKEN_TTL_HEADER = "x-aws-ec2-metadata-token-ttl-seconds";
30+
private static final String DEFAULT_TOKEN_TTL = "21600";
31+
32+
@Override
33+
public URI endpoint() {
34+
String host = SdkSystemSetting.AWS_EC2_METADATA_SERVICE_ENDPOINT.getStringValueOrThrow();
35+
if (host.endsWith("/")) {
36+
host = host.substring(0, host.length() - 1);
37+
}
38+
return URI.create(host + TOKEN_RESOURCE_PATH);
39+
}
40+
41+
@Override
42+
public Map<String, String> headers() {
43+
Map<String, String> requestHeaders = new HashMap<>();
44+
requestHeaders.put("User-Agent", UserAgentUtils.getUserAgent());
45+
requestHeaders.put("Accept", "*/*");
46+
requestHeaders.put("Connection", "keep-alive");
47+
requestHeaders.put(EC2_METADATA_TOKEN_TTL_HEADER, DEFAULT_TOKEN_TTL);
48+
49+
return requestHeaders;
50+
}
51+
}

core/regions/src/test/java/software/amazon/awssdk/regions/internal/util/EC2MetadataUtilsServer.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ private void handleConnection(BufferedReader input,
9090
if (parts.length != 3) {
9191
throw new RuntimeException("Bogus request: " + line);
9292
}
93-
if (!"GET".equals(parts[0])) {
93+
if (!"GET".equals(parts[0]) && !"PUT".equals(parts[0])) {
9494
throw new RuntimeException("Bogus verb: " + line);
9595
}
9696

@@ -107,13 +107,13 @@ private void handleConnection(BufferedReader input,
107107
.startsWith("/latest/meta-data/iam/security-credentials/")) {
108108

109109
outputIamCred(output);
110-
111110
} else if (path.equals("/latest/dynamic/instance-identity/document")) {
112111
outputInstanceInfo(output);
113112

114113
} else if (path.equals("/latest/dynamic/instance-identity/signature")) {
115114
outputInstanceSignature(output);
116-
115+
} else if (path.equals("/latest/api/token")) {
116+
outputToken(output);
117117
} else {
118118
throw new RuntimeException("Unknown path: " + path);
119119
}
@@ -132,6 +132,18 @@ private void ignoreRequest(BufferedReader input) throws IOException {
132132
}
133133
}
134134

135+
private void outputToken(PrintWriter output) {
136+
String payload = "test-token";
137+
138+
output.println("HTTP/1.1 200 OK");
139+
output.println("Connection: close");
140+
output.println("Content-Length: " + payload.length());
141+
output.println();
142+
143+
output.print(payload);
144+
output.flush();
145+
}
146+
135147
private void outputIamInfo(PrintWriter output) throws IOException {
136148

137149
String payload =

0 commit comments

Comments
 (0)