From c3367761ea55c37a30b4a17fa6608aa967016657 Mon Sep 17 00:00:00 2001 From: David Negrete Date: Thu, 5 Jan 2023 09:34:30 -0700 Subject: [PATCH 1/4] Validate that localhost resolves to loopback address --- ...zonElasticContainerServiceECS-09f6115.json | 6 +++ .../ContainerCredentialsProvider.java | 51 +++++++++++++++---- ...tainerCredentialsEndpointProviderTest.java | 42 ++++++++++++++- 3 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 .changes/next-release/bugfix-AmazonElasticContainerServiceECS-09f6115.json diff --git a/.changes/next-release/bugfix-AmazonElasticContainerServiceECS-09f6115.json b/.changes/next-release/bugfix-AmazonElasticContainerServiceECS-09f6115.json new file mode 100644 index 000000000000..71f286fb7d6a --- /dev/null +++ b/.changes/next-release/bugfix-AmazonElasticContainerServiceECS-09f6115.json @@ -0,0 +1,6 @@ +{ + "category": "Amazon Elastic Container Service (ECS)", + "contributor": "", + "type": "bugfix", + "description": "HTTP(S) credential provider requires the implementation to verify that the resolved addresses for the host are actually loopback addresses." +} 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..0440520a6c3b 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,18 @@ package software.amazon.awssdk.auth.credentials; -import static java.util.Collections.unmodifiableSet; - import java.io.IOException; +import java.net.Inet6Address; +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 +66,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 ALLOWED_HOSTS_IPv4_RULES = InetAddress::isLoopbackAddress; + private static final Predicate ALLOWED_HOSTS_IPv6_RULES = InetAddress::isLoopbackAddress; + private static final String HTTPS = "https"; private final String endpoint; private final HttpCredentialsLoader httpCredentialsLoader; @@ -207,18 +210,44 @@ 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.", + uri, envVarName)) .build(); } return uri; } + + private boolean isHttps(URI endpoint) { + return Objects.equals(HTTPS, endpoint.getScheme()); + } + + 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) { + if (inetAddress instanceof Inet6Address) { + return ALLOWED_HOSTS_IPv6_RULES.test(inetAddress); + } + + return ALLOWED_HOSTS_IPv4_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(); } From 0d34f4e6354664441ec31cb677070e1b3b210029 Mon Sep 17 00:00:00 2001 From: David Negrete Date: Fri, 6 Jan 2023 09:59:02 -0700 Subject: [PATCH 2/4] Addressed review comments --- ...zonElasticContainerServiceECS-09f6115.json | 6 ------ .../ContainerCredentialsProvider.java | 20 +++++++++++-------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.changes/next-release/bugfix-AmazonElasticContainerServiceECS-09f6115.json b/.changes/next-release/bugfix-AmazonElasticContainerServiceECS-09f6115.json index 71f286fb7d6a..e69de29bb2d1 100644 --- a/.changes/next-release/bugfix-AmazonElasticContainerServiceECS-09f6115.json +++ b/.changes/next-release/bugfix-AmazonElasticContainerServiceECS-09f6115.json @@ -1,6 +0,0 @@ -{ - "category": "Amazon Elastic Container Service (ECS)", - "contributor": "", - "type": "bugfix", - "description": "HTTP(S) credential provider requires the implementation to verify that the resolved addresses for the host are actually loopback addresses." -} 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 0440520a6c3b..81d21208ff4f 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 @@ -16,7 +16,6 @@ package software.amazon.awssdk.auth.credentials; import java.io.IOException; -import java.net.Inet6Address; import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; @@ -66,8 +65,8 @@ public final class ContainerCredentialsProvider implements HttpCredentialsProvider, ToCopyableBuilder { - private static final Predicate ALLOWED_HOSTS_IPv4_RULES = InetAddress::isLoopbackAddress; - private static final Predicate ALLOWED_HOSTS_IPv6_RULES = InetAddress::isLoopbackAddress; + 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; @@ -226,6 +225,15 @@ 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); @@ -242,11 +250,7 @@ private boolean isAllowedHost(String host) { } private boolean matchesAllowedHostRules(InetAddress inetAddress) { - if (inetAddress instanceof Inet6Address) { - return ALLOWED_HOSTS_IPv6_RULES.test(inetAddress); - } - - return ALLOWED_HOSTS_IPv4_RULES.test(inetAddress); + return ALLOWED_HOSTS_RULES.test(inetAddress); } } From b7a837c7756f4050acaa20d5a14fb638373cdceb Mon Sep 17 00:00:00 2001 From: David Negrete Date: Fri, 6 Jan 2023 11:00:14 -0700 Subject: [PATCH 3/4] Improved exception message --- .../awssdk/auth/credentials/ContainerCredentialsProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 81d21208ff4f..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 @@ -213,8 +213,8 @@ private URI createGenericContainerUrl() { 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 should resolve to a loopback" + - " address.", + "%s has an invalid host. Host should resolve to a loopback " + + "address or have the full URI be HTTPS.", uri, envVarName)) .build(); } From 76df19ef1fbe9ee2d5f012bc968964f20e892075 Mon Sep 17 00:00:00 2001 From: David Negrete Date: Tue, 17 Jan 2023 10:41:51 -0700 Subject: [PATCH 4/4] Removed changelog entry --- .../bugfix-AmazonElasticContainerServiceECS-09f6115.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .changes/next-release/bugfix-AmazonElasticContainerServiceECS-09f6115.json diff --git a/.changes/next-release/bugfix-AmazonElasticContainerServiceECS-09f6115.json b/.changes/next-release/bugfix-AmazonElasticContainerServiceECS-09f6115.json deleted file mode 100644 index e69de29bb2d1..000000000000