diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProvider.java index af00ac932b13..e43ab3e65df9 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProvider.java @@ -15,17 +15,17 @@ package software.amazon.awssdk.auth.credentials; -import static java.util.Collections.unmodifiableSet; - import java.io.IOException; +import java.net.InetAddress; import java.net.URI; +import java.net.UnknownHostException; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; +import java.util.Objects; +import java.util.function.Predicate; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.auth.credentials.internal.ContainerCredentialsRetryPolicy; import software.amazon.awssdk.auth.credentials.internal.HttpCredentialsLoader; @@ -65,7 +65,9 @@ public final class ContainerCredentialsProvider implements HttpCredentialsProvider, ToCopyableBuilder { - private static final Set ALLOWED_HOSTS = unmodifiableSet(new HashSet<>(Arrays.asList("localhost", "127.0.0.1"))); + private static final Predicate IS_LOOPBACK_ADDRESS = InetAddress::isLoopbackAddress; + private static final Predicate ALLOWED_HOSTS_RULES = IS_LOOPBACK_ADDRESS; + private static final String HTTPS = "https"; private final String endpoint; private final HttpCredentialsLoader httpCredentialsLoader; @@ -207,18 +209,49 @@ private URI createUri(String relativeUri) { private URI createGenericContainerUrl() { URI uri = URI.create(SdkSystemSetting.AWS_CONTAINER_CREDENTIALS_FULL_URI.getStringValueOrThrow()); - if (!ALLOWED_HOSTS.contains(uri.getHost())) { + if (!isHttps(uri) && !isAllowedHost(uri.getHost())) { String envVarName = SdkSystemSetting.AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(); throw SdkClientException.builder() - .message(String.format("The full URI (%s) contained within environment " + - "variable %s has an invalid host. Host can only be one of [%s].", - uri, - envVarName, - String.join(",", ALLOWED_HOSTS))) + .message(String.format("The full URI (%s) contained within environment variable " + + "%s has an invalid host. Host should resolve to a loopback " + + "address or have the full URI be HTTPS.", + uri, envVarName)) .build(); } return uri; } + + private boolean isHttps(URI endpoint) { + return Objects.equals(HTTPS, endpoint.getScheme()); + } + + /** + * Determines if the addresses for a given host are resolved to a loopback address. + *

+ * This is a best-effort in determining what address a host will be resolved to. DNS caching might be disabled, + * or could expire between this check and when the API is invoked. + *

+ * @param host The name or IP address of the host. + * @return A boolean specifying whether the host is allowed as an endpoint for credentials loading. + */ + private boolean isAllowedHost(String host) { + try { + InetAddress[] addresses = InetAddress.getAllByName(host); + + return addresses.length > 0 && Arrays.stream(addresses) + .allMatch(this::matchesAllowedHostRules); + + } catch (UnknownHostException e) { + throw SdkClientException.builder() + .cause(e) + .message(String.format("host (%s) could not be resolved to an IP address.", host)) + .build(); + } + } + + private boolean matchesAllowedHostRules(InetAddress inetAddress) { + return ALLOWED_HOSTS_RULES.test(inetAddress); + } } /** diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsEndpointProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsEndpointProviderTest.java index acb286e9af00..e9745c4fc0dc 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsEndpointProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsEndpointProviderTest.java @@ -71,9 +71,49 @@ public void theLoopbackAddressIsAlsoAcceptable() throws IOException { assertThat(sut.endpoint().toString(), equalTo(fullUri)); } + @Test + public void theLoopbackIpv6AddressIsAlsoAcceptable() throws IOException { + String fullUri = "http://[::1]:9851/endpoint"; + helper.set(AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(), fullUri); + + assertThat(sut.endpoint().toString(), equalTo(fullUri)); + } + + @Test + public void anyHttpsAddressIsAlsoAcceptable() throws IOException { + String fullUri = "https://192.168.10.120:9851/endpoint"; + helper.set(AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(), fullUri); + + assertThat(sut.endpoint().toString(), equalTo(fullUri)); + } + + @Test + public void anyHttpsIpv6AddressIsAlsoAcceptable() throws IOException { + String fullUri = "https://[::FFFF:152.16.24.123]/endpoint"; + helper.set(AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(), fullUri); + + assertThat(sut.endpoint().toString(), equalTo(fullUri)); + } + + @Test(expected = SdkClientException.class) + public void nonLoopbackAddressIsNotAcceptable() throws IOException { + String fullUri = "http://192.168.10.120:9851/endpoint"; + helper.set(AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(), fullUri); + + assertThat(sut.endpoint().toString(), equalTo(fullUri)); + } + + @Test(expected = SdkClientException.class) + public void nonLoopbackIpv6AddressIsNotAcceptable() throws IOException { + String fullUri = "http://[::FFFF:152.16.24.123]/endpoint"; + helper.set(AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(), fullUri); + + assertThat(sut.endpoint().toString(), equalTo(fullUri)); + } + @Test(expected = SdkClientException.class) public void onlyLocalHostAddressesAreValid() throws IOException { - helper.set(AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(), "https://google.com/endpoint"); + helper.set(AWS_CONTAINER_CREDENTIALS_FULL_URI.environmentVariable(), "http://google.com/endpoint"); sut.endpoint(); }