Skip to content

Validate that localhost resolves to loopback address #3672

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -65,7 +65,9 @@
public final class ContainerCredentialsProvider
implements HttpCredentialsProvider,
ToCopyableBuilder<ContainerCredentialsProvider.Builder, ContainerCredentialsProvider> {
private static final Set<String> ALLOWED_HOSTS = unmodifiableSet(new HashSet<>(Arrays.asList("localhost", "127.0.0.1")));
private static final Predicate<InetAddress> IS_LOOPBACK_ADDRESS = InetAddress::isLoopbackAddress;
private static final Predicate<InetAddress> ALLOWED_HOSTS_RULES = IS_LOOPBACK_ADDRESS;
private static final String HTTPS = "https";

private final String endpoint;
private final HttpCredentialsLoader httpCredentialsLoader;
Expand Down Expand Up @@ -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.
* <p>
* 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.
* </p>
* @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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a comment that this is best-effort: if DNS caching is disabled or the cache expires between our check here and when we actually invoke the API, the endpoint might not be loopback.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add Javadoc.


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);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down