Skip to content

Commit 5fc1347

Browse files
authored
Validate that localhost resolves to loopback address (#3672)
* Validate that localhost resolves to loopback address * Addressed review comments * Improved exception message * Removed changelog entry
1 parent 3884a66 commit 5fc1347

File tree

2 files changed

+85
-12
lines changed

2 files changed

+85
-12
lines changed

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

+44-11
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@
1515

1616
package software.amazon.awssdk.auth.credentials;
1717

18-
import static java.util.Collections.unmodifiableSet;
19-
2018
import java.io.IOException;
19+
import java.net.InetAddress;
2120
import java.net.URI;
21+
import java.net.UnknownHostException;
2222
import java.time.Instant;
2323
import java.time.temporal.ChronoUnit;
2424
import java.util.Arrays;
2525
import java.util.HashMap;
26-
import java.util.HashSet;
2726
import java.util.Map;
28-
import java.util.Set;
27+
import java.util.Objects;
28+
import java.util.function.Predicate;
2929
import software.amazon.awssdk.annotations.SdkPublicApi;
3030
import software.amazon.awssdk.auth.credentials.internal.ContainerCredentialsRetryPolicy;
3131
import software.amazon.awssdk.auth.credentials.internal.HttpCredentialsLoader;
@@ -65,7 +65,9 @@
6565
public final class ContainerCredentialsProvider
6666
implements HttpCredentialsProvider,
6767
ToCopyableBuilder<ContainerCredentialsProvider.Builder, ContainerCredentialsProvider> {
68-
private static final Set<String> ALLOWED_HOSTS = unmodifiableSet(new HashSet<>(Arrays.asList("localhost", "127.0.0.1")));
68+
private static final Predicate<InetAddress> IS_LOOPBACK_ADDRESS = InetAddress::isLoopbackAddress;
69+
private static final Predicate<InetAddress> ALLOWED_HOSTS_RULES = IS_LOOPBACK_ADDRESS;
70+
private static final String HTTPS = "https";
6971

7072
private final String endpoint;
7173
private final HttpCredentialsLoader httpCredentialsLoader;
@@ -207,18 +209,49 @@ private URI createUri(String relativeUri) {
207209

208210
private URI createGenericContainerUrl() {
209211
URI uri = URI.create(SdkSystemSetting.AWS_CONTAINER_CREDENTIALS_FULL_URI.getStringValueOrThrow());
210-
if (!ALLOWED_HOSTS.contains(uri.getHost())) {
212+
if (!isHttps(uri) && !isAllowedHost(uri.getHost())) {
211213
String envVarName = SdkSystemSetting.AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable();
212214
throw SdkClientException.builder()
213-
.message(String.format("The full URI (%s) contained within environment " +
214-
"variable %s has an invalid host. Host can only be one of [%s].",
215-
uri,
216-
envVarName,
217-
String.join(",", ALLOWED_HOSTS)))
215+
.message(String.format("The full URI (%s) contained within environment variable " +
216+
"%s has an invalid host. Host should resolve to a loopback " +
217+
"address or have the full URI be HTTPS.",
218+
uri, envVarName))
218219
.build();
219220
}
220221
return uri;
221222
}
223+
224+
private boolean isHttps(URI endpoint) {
225+
return Objects.equals(HTTPS, endpoint.getScheme());
226+
}
227+
228+
/**
229+
* Determines if the addresses for a given host are resolved to a loopback address.
230+
* <p>
231+
* This is a best-effort in determining what address a host will be resolved to. DNS caching might be disabled,
232+
* or could expire between this check and when the API is invoked.
233+
* </p>
234+
* @param host The name or IP address of the host.
235+
* @return A boolean specifying whether the host is allowed as an endpoint for credentials loading.
236+
*/
237+
private boolean isAllowedHost(String host) {
238+
try {
239+
InetAddress[] addresses = InetAddress.getAllByName(host);
240+
241+
return addresses.length > 0 && Arrays.stream(addresses)
242+
.allMatch(this::matchesAllowedHostRules);
243+
244+
} catch (UnknownHostException e) {
245+
throw SdkClientException.builder()
246+
.cause(e)
247+
.message(String.format("host (%s) could not be resolved to an IP address.", host))
248+
.build();
249+
}
250+
}
251+
252+
private boolean matchesAllowedHostRules(InetAddress inetAddress) {
253+
return ALLOWED_HOSTS_RULES.test(inetAddress);
254+
}
222255
}
223256

224257
/**

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

+41-1
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,49 @@ public void theLoopbackAddressIsAlsoAcceptable() throws IOException {
7171
assertThat(sut.endpoint().toString(), equalTo(fullUri));
7272
}
7373

74+
@Test
75+
public void theLoopbackIpv6AddressIsAlsoAcceptable() throws IOException {
76+
String fullUri = "http://[::1]:9851/endpoint";
77+
helper.set(AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(), fullUri);
78+
79+
assertThat(sut.endpoint().toString(), equalTo(fullUri));
80+
}
81+
82+
@Test
83+
public void anyHttpsAddressIsAlsoAcceptable() throws IOException {
84+
String fullUri = "https://192.168.10.120:9851/endpoint";
85+
helper.set(AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(), fullUri);
86+
87+
assertThat(sut.endpoint().toString(), equalTo(fullUri));
88+
}
89+
90+
@Test
91+
public void anyHttpsIpv6AddressIsAlsoAcceptable() throws IOException {
92+
String fullUri = "https://[::FFFF:152.16.24.123]/endpoint";
93+
helper.set(AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(), fullUri);
94+
95+
assertThat(sut.endpoint().toString(), equalTo(fullUri));
96+
}
97+
98+
@Test(expected = SdkClientException.class)
99+
public void nonLoopbackAddressIsNotAcceptable() throws IOException {
100+
String fullUri = "http://192.168.10.120:9851/endpoint";
101+
helper.set(AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(), fullUri);
102+
103+
assertThat(sut.endpoint().toString(), equalTo(fullUri));
104+
}
105+
106+
@Test(expected = SdkClientException.class)
107+
public void nonLoopbackIpv6AddressIsNotAcceptable() throws IOException {
108+
String fullUri = "http://[::FFFF:152.16.24.123]/endpoint";
109+
helper.set(AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(), fullUri);
110+
111+
assertThat(sut.endpoint().toString(), equalTo(fullUri));
112+
}
113+
74114
@Test(expected = SdkClientException.class)
75115
public void onlyLocalHostAddressesAreValid() throws IOException {
76-
helper.set(AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(), "https://google.com/endpoint");
116+
helper.set(AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(), "http://google.com/endpoint");
77117
sut.endpoint();
78118
}
79119

0 commit comments

Comments
 (0)