From c8c37bb36f3347d3bcb43cd357800826e9975448 Mon Sep 17 00:00:00 2001 From: "Bala.FA" Date: Tue, 15 Sep 2026 19:56:14 +0530 Subject: [PATCH] Support AWS EKS Pod Identity in IamAwsProvider EKS Pod Identity Agent exposes credentials over a container credential endpoint; it injects AWS_CONTAINER_CREDENTIALS_FULL_URI pointing to its link-local address and passes the authorization token in a file. Two gaps denied it. - AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE was not read at all, hence no authorization token was sent. It is read now and takes precedence over AWS_CONTAINER_AUTHORIZATION_TOKEN as done by AWS. Trailing newline of the file is removed as it is denied as HTTP header value. - The host of AWS_CONTAINER_CREDENTIALS_FULL_URI was denied unless it resolved to loopback addresses only. As done by AWS, the ECS and EKS Pod Identity link-local addresses 169.254.170.2, 169.254.170.23 and fd00:ec2::23 are allowed as well, and the check is skipped for HTTPS endpoints. Along with it, AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is parsed as URI than added as path segments; AWS sets the value with leading slash which resulted in an empty path segment like http://169.254.170.2//v2/xxx. A malformed value of either variable raises ProviderException than NPE. Fixes #1717 Signed-off-by: Bala.FA --- .../io/minio/credentials/IamAwsProvider.java | 110 +++++++++++++++--- 1 file changed, 92 insertions(+), 18 deletions(-) diff --git a/api/src/main/java/io/minio/credentials/IamAwsProvider.java b/api/src/main/java/io/minio/credentials/IamAwsProvider.java index 74accf59e..9e6f603aa 100644 --- a/api/src/main/java/io/minio/credentials/IamAwsProvider.java +++ b/api/src/main/java/io/minio/credentials/IamAwsProvider.java @@ -31,7 +31,10 @@ import java.nio.file.Paths; import java.security.ProviderException; import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import javax.annotation.Nullable; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; @@ -43,9 +46,17 @@ /** * Credential provider using IAM roles - * for Amazon EC2. + * for Amazon EC2, IAM + * roles for service accounts, ECS task roles and EKS Pod Identity. */ public class IamAwsProvider extends EnvironmentProvider { + // Hosts of ECS task metadata and EKS Pod Identity Agent credential endpoints. AWS allows these + // link-local addresses in AWS_CONTAINER_CREDENTIALS_FULL_URI in addition to loopback addresses. + private static final Set ALLOWED_CONTAINER_ADDRESSES = + allowedContainerAddresses("169.254.170.2", "169.254.170.23", "fd00:ec2::23"); + // Custom endpoint to fetch IAM role credentials. private final HttpUrl customEndpoint; private final OkHttpClient httpClient; @@ -70,15 +81,64 @@ public IamAwsProvider(@Nullable String customEndpoint, @Nullable OkHttpClient cu .build(); } - private void checkLoopbackHost(HttpUrl url) { - try { - for (InetAddress addr : InetAddress.getAllByName(url.host())) { - if (!addr.isLoopbackAddress()) { - throw new ProviderException(url.host() + " is not loopback only host"); - } + private static Set allowedContainerAddresses(String... hosts) { + Set addresses = new HashSet<>(); + for (String host : hosts) { + try { + // Literal IP addresses are parsed without doing a DNS lookup. + addresses.add(InetAddress.getByName(host)); + } catch (UnknownHostException e) { + throw new ProviderException("Unable to parse address " + host, e); } + } + return Collections.unmodifiableSet(addresses); + } + + /** + * Check the host of container credential endpoint is allowed as done by AWS; it must either + * resolve to loopback addresses only, resolve to the ECS or EKS Pod Identity link-local addresses + * only, or be accessed over HTTPS. + */ + private void checkContainerHost(HttpUrl url) { + if (url.isHttps()) return; + + InetAddress[] addrs; + try { + addrs = InetAddress.getAllByName(url.host()); } catch (UnknownHostException e) { - throw new ProviderException("Host in " + url + " is not loopback address"); + throw new ProviderException("Host in " + url + " is not loopback address", e); + } + + boolean loopback = addrs.length > 0; + boolean allowed = addrs.length > 0; + for (InetAddress addr : addrs) { + loopback = loopback && addr.isLoopbackAddress(); + allowed = allowed && ALLOWED_CONTAINER_ADDRESSES.contains(addr); + } + + if (!loopback && !allowed) { + throw new ProviderException( + url.host() + + " is neither loopback only host nor ECS/EKS credential endpoint; use HTTPS scheme" + + " to fetch credentials from other hosts"); + } + } + + /** + * Get authorization token of container credential endpoint. + * AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE takes precedence over AWS_CONTAINER_AUTHORIZATION_TOKEN + * as done by AWS; EKS Pod Identity sets the former only. + */ + private String containerAuthorizationToken() { + String filename = getProperty("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE"); + if (filename == null) return getProperty("AWS_CONTAINER_AUTHORIZATION_TOKEN"); + + try { + byte[] data = Files.readAllBytes(Paths.get(filename)); + // The token is passed as HTTP header value; trailing newline in the file must go away. + return new String(data, StandardCharsets.UTF_8).trim(); + } catch (IOException e) { + throw new ProviderException("Error in reading file " + filename, e); } } @@ -211,19 +271,33 @@ public synchronized Credentials fetch() { } String tokenHeader = Http.Headers.AUTHORIZATION; - String token = getProperty("AWS_CONTAINER_AUTHORIZATION_TOKEN"); - if (getProperty("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") != null) { + String token; + String relativeUri = getProperty("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"); + String fullUri = getProperty("AWS_CONTAINER_CREDENTIALS_FULL_URI"); + if (relativeUri != null) { + token = containerAuthorizationToken(); if (url == null) { + // AWS sets the value with leading slash and it may have query parameters; parsing the + // whole URI avoids the empty path segment added by HttpUrl.Builder.addPathSegments(). url = - new HttpUrl.Builder() - .scheme("http") - .host("169.254.170.2") - .addPathSegments(getProperty("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")) - .build(); + HttpUrl.parse( + "http://169.254.170.2" + + (relativeUri.startsWith("/") ? relativeUri : "/" + relativeUri)); + if (url == null) { + throw new ProviderException( + "Invalid AWS_CONTAINER_CREDENTIALS_RELATIVE_URI value " + relativeUri); + } + } + } else if (fullUri != null) { + token = containerAuthorizationToken(); + if (url == null) { + url = HttpUrl.parse(fullUri); + if (url == null) { + throw new ProviderException( + "Invalid AWS_CONTAINER_CREDENTIALS_FULL_URI value " + fullUri); + } } - } else if (getProperty("AWS_CONTAINER_CREDENTIALS_FULL_URI") != null) { - if (url == null) url = HttpUrl.parse(getProperty("AWS_CONTAINER_CREDENTIALS_FULL_URI")); - checkLoopbackHost(url); + checkContainerHost(url); } else { token = fetchImdsToken(); tokenHeader = "X-aws-ec2-metadata-token";