From e03ee9f6ef92bab3f73f40d36305812e6ae57a35 Mon Sep 17 00:00:00 2001 From: hwayoungjun Date: Wed, 6 May 2026 14:28:28 +0900 Subject: [PATCH 1/3] fix: refresh EKS authentication token per request --- .../util/credentials/EKSAuthentication.java | 69 +++++-- .../credentials/EKSAuthenticationTest.java | 181 ++++++++++++++++-- 2 files changed, 217 insertions(+), 33 deletions(-) diff --git a/util/src/main/java/io/kubernetes/client/util/credentials/EKSAuthentication.java b/util/src/main/java/io/kubernetes/client/util/credentials/EKSAuthentication.java index afa180b918..a099547bcc 100644 --- a/util/src/main/java/io/kubernetes/client/util/credentials/EKSAuthentication.java +++ b/util/src/main/java/io/kubernetes/client/util/credentials/EKSAuthentication.java @@ -13,28 +13,32 @@ package io.kubernetes.client.util.credentials; import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.util.exception.TokenAquisitionException; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Base64; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.SdkHttpRequest; -import software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner; import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner; import software.amazon.awssdk.http.auth.spi.signer.SignedRequest; -import software.amazon.awssdk.utils.http.SdkHttpUtils; - -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Base64; /** * EKS cluster authentication which generates a bearer token from AWS AK/SK. It doesn't require an "aws" * command line tool in the $PATH. */ -public class EKSAuthentication implements Authentication { +public class EKSAuthentication implements Authentication, Interceptor { private static final Logger log = LoggerFactory.getLogger(EKSAuthentication.class); @@ -50,6 +54,11 @@ public EKSAuthentication(AwsCredentialsProvider provider, String region, String } public EKSAuthentication(AwsCredentialsProvider provider, String region, String clusterName, int expirySeconds) { + this(provider, region, clusterName, expirySeconds, Clock.systemUTC()); + } + + EKSAuthentication( + AwsCredentialsProvider provider, String region, String clusterName, int expirySeconds, Clock clock) { this.provider = provider; this.region = region; this.clusterName = clusterName; @@ -57,7 +66,8 @@ public EKSAuthentication(AwsCredentialsProvider provider, String region, String expirySeconds = MAX_EXPIRY_SECONDS; } this.expirySeconds = expirySeconds; - this.stsEndpoint = URI.create("https://sts." + this.region + ".amazonaws.com"); + this.stsEndpoint = URI.create("https://sts." + this.region + ".amazonaws.com/"); + this.clock = clock; } private static final int MAX_EXPIRY_SECONDS = 60 * 15; @@ -67,22 +77,47 @@ public EKSAuthentication(AwsCredentialsProvider provider, String region, String private final URI stsEndpoint; private final int expirySeconds; + private final Clock clock; + private Instant expiry = Instant.MIN; + private String token; @Override public void provide(ApiClient client) { + OkHttpClient withInterceptor = client.getHttpClient().newBuilder().addInterceptor(this).build(); + client.setHttpClient(withInterceptor); + } + + @Override + public Response intercept(Interceptor.Chain chain) throws IOException { + Request request = chain.request(); + Request newRequest = request.newBuilder().header("Authorization", "Bearer " + getToken()).build(); + return chain.proceed(newRequest); + } + + private synchronized String getToken() { + if (Instant.now(clock).isAfter(expiry)) { + try { + token = generateToken(); + } catch (RuntimeException e) { + throw new TokenAquisitionException(e); + } + expiry = Instant.now(clock).plus(expirySeconds, ChronoUnit.SECONDS); + log.info("Generated BEARER token for ApiClient, expiring at {}", expiry); + } + return token; + } + + private String generateToken() { SdkHttpRequest httpRequest = generateStsRequest(); String presignedUrl = requestToPresignedUrl(httpRequest); String encodedUrl = presignedUrlToEncodedUrl(presignedUrl); - String token = "k8s-aws-v1." + encodedUrl; - client.setApiKeyPrefix("Bearer"); - client.setApiKey(token); - log.info("Generated BEARER token for ApiClient, expiring at {}", Instant.now().plus(expirySeconds, ChronoUnit.SECONDS)); + return "k8s-aws-v1." + encodedUrl; } private static String presignedUrlToEncodedUrl(String presignedUrl) { return Base64.getUrlEncoder() .withoutPadding() - .encodeToString(SdkHttpUtils.urlEncodeIgnoreSlashes(presignedUrl).getBytes(StandardCharsets.UTF_8)); + .encodeToString(presignedUrl.getBytes(StandardCharsets.UTF_8)); } private SdkHttpRequest generateStsRequest() { @@ -103,7 +138,7 @@ private String requestToPresignedUrl(SdkHttpRequest httpRequest) { .putProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "sts") .putProperty(AwsV4HttpSigner.REGION_NAME, region) .putProperty(AwsV4HttpSigner.AUTH_LOCATION, AwsV4HttpSigner.AuthLocation.QUERY_STRING) - .putProperty(AwsV4HttpSigner.EXPIRATION_DURATION, Duration.of(60, ChronoUnit.SECONDS))); + .putProperty(AwsV4HttpSigner.EXPIRATION_DURATION, Duration.of(expirySeconds, ChronoUnit.SECONDS))); SdkHttpRequest request = signedRequest.request(); return request.getUri().toString(); } diff --git a/util/src/test/java/io/kubernetes/client/util/credentials/EKSAuthenticationTest.java b/util/src/test/java/io/kubernetes/client/util/credentials/EKSAuthenticationTest.java index 0284ca73de..8200e005dc 100644 --- a/util/src/test/java/io/kubernetes/client/util/credentials/EKSAuthenticationTest.java +++ b/util/src/test/java/io/kubernetes/client/util/credentials/EKSAuthenticationTest.java @@ -12,37 +12,186 @@ */ package io.kubernetes.client.util.credentials; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.okForContentType; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.Configuration; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import java.nio.charset.StandardCharsets; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.Base64; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - @ExtendWith(MockitoExtension.class) class EKSAuthenticationTest { - @Mock - private AwsCredentialsProvider provider; + private static final String REGION = "us-west-2"; + private static final String CLUSTER_NAME = "test-2"; + private static final String LIST_PODS_PATH = "/api/v1/pods"; + private static final String BEARER_TOKEN_PREFIX = "Bearer k8s-aws-v1."; + + @RegisterExtension + static WireMockExtension apiServer = + WireMockExtension.newInstance().options(options().dynamicPort()).build(); + + @Mock private AwsCredentialsProvider provider; + + private ApiClient apiClient; + private Instant instant; + private MockClock clock; + + @BeforeEach + void setup() { + this.apiClient = new ApiClient(); + this.apiClient.setBasePath("http://localhost:" + apiServer.getPort()); + Configuration.setDefaultApiClient(this.apiClient); + + this.instant = Instant.now(); + this.clock = new MockClock(this.instant); + } + + @Test + void addsBearerTokenToRequests() throws ApiException { + when(provider.resolveCredentials()).thenReturn(credentials("ak", "session")); + CoreV1Api api = authenticatedApi(new EKSAuthentication(provider, REGION, CLUSTER_NAME)); + + stubListPods(); + listPods(api); + + apiServer.verify( + 1, + getRequestedFor(urlPathEqualTo(LIST_PODS_PATH)) + .withHeader("Authorization", matching("Bearer k8s-aws-v1\\..+"))); + verify(provider).resolveCredentials(); + } + + @Test + void reusesTokenBeforeExpiry() throws ApiException { + when(provider.resolveCredentials()).thenReturn(credentials("ak", "session")); + CoreV1Api api = authenticatedApi(new EKSAuthentication(provider, REGION, CLUSTER_NAME, 60, clock)); + + stubListPods(); + listPods(api); + listPods(api); + + List authorizations = authorizationHeaders(); + assertThat(authorizations).hasSize(2); + assertThat(authorizations.get(0)).isEqualTo(authorizations.get(1)); + verify(provider).resolveCredentials(); + } - @Mock - private ApiClient apiClient; + @Test + void refreshesTokenAfterExpiry() throws ApiException { + when(provider.resolveCredentials()) + .thenReturn(credentials("ak-1", "session-1")) + .thenReturn(credentials("ak-2", "session-2")); + CoreV1Api api = authenticatedApi(new EKSAuthentication(provider, REGION, CLUSTER_NAME, 60, clock)); - private String region = "us-west-2"; + stubListPods(); + listPods(api); + clock.setInstant(instant.plusSeconds(70)); + listPods(api); - private String clusterName = "test-2"; + List authorizations = authorizationHeaders(); + assertThat(authorizations).hasSize(2); + assertThat(authorizations.get(0)).isNotEqualTo(authorizations.get(1)); + assertThat(decodedTokenUrl(authorizations.get(0))).contains("X-Amz-Credential=ak-1%2F"); + assertThat(decodedTokenUrl(authorizations.get(1))).contains("X-Amz-Credential=ak-2%2F"); + verify(provider, times(2)).resolveCredentials(); + } @Test - void provideApiClient() { - when(provider.resolveCredentials()).thenReturn(AwsSessionCredentials.create("ak", "sk", "session")); - EKSAuthentication authentication = new EKSAuthentication(provider, region, clusterName); - authentication.provide(apiClient); - verify(apiClient).setApiKey(anyString()); - verify(apiClient).setApiKeyPrefix(anyString()); + void expirySecondsAreCapped() throws ApiException { + when(provider.resolveCredentials()).thenReturn(credentials("ak", "session")); + CoreV1Api api = authenticatedApi(new EKSAuthentication(provider, REGION, CLUSTER_NAME, 1_000)); + + stubListPods(); + listPods(api); + + assertThat(decodedTokenUrl(authorizationHeaders().get(0))) + .startsWith("https://sts.us-west-2.amazonaws.com/?") + .contains("X-Amz-Expires=900"); + } + + private CoreV1Api authenticatedApi(EKSAuthentication authentication) { + authentication.provide(apiClient); + return new CoreV1Api(); + } + + private AwsSessionCredentials credentials(String accessKeyId, String sessionToken) { + return AwsSessionCredentials.create(accessKeyId, "sk", sessionToken); + } + + private void stubListPods() { + apiServer.stubFor( + get(urlPathEqualTo(LIST_PODS_PATH)) + .willReturn(okForContentType("application/json", "{\"items\":[]}"))); + } + + private void listPods(CoreV1Api api) throws ApiException { + api.listPodForAllNamespaces().execute(); + } + + private List authorizationHeaders() { + return apiServer.getAllServeEvents().stream() + .sorted(Comparator.comparing(event -> event.getRequest().getLoggedDate())) + .map(event -> event.getRequest().getHeader("Authorization")) + .collect(Collectors.toList()); + } + + private String decodedTokenUrl(String authorization) { + String encodedToken = authorization.substring(BEARER_TOKEN_PREFIX.length()); + return new String(Base64.getUrlDecoder().decode(encodedToken), StandardCharsets.UTF_8); + } + + static class MockClock extends Clock { + Instant now; + + MockClock(Instant start) { + this.now = start; + } + + void setInstant(Instant instant) { + this.now = instant; + } + + @Override + public Instant instant() { + return now; + } + + @Override + public ZoneId getZone() { + return ZoneOffset.UTC; + } + + @Override + public Clock withZone(ZoneId zone) { + throw new UnsupportedOperationException(); } + } } From 11c4de8b5cc5b2e3ab9e2b172b863bffcf03f606 Mon Sep 17 00:00:00 2001 From: hwayoungjun Date: Sat, 9 May 2026 00:41:33 +0900 Subject: [PATCH 2/3] fix: reuse RefreshAuthentication for EKS token refresh --- .../util/credentials/EKSAuthentication.java | 82 ++++++------------- 1 file changed, 25 insertions(+), 57 deletions(-) diff --git a/util/src/main/java/io/kubernetes/client/util/credentials/EKSAuthentication.java b/util/src/main/java/io/kubernetes/client/util/credentials/EKSAuthentication.java index a099547bcc..432efd6e42 100644 --- a/util/src/main/java/io/kubernetes/client/util/credentials/EKSAuthentication.java +++ b/util/src/main/java/io/kubernetes/client/util/credentials/EKSAuthentication.java @@ -12,9 +12,6 @@ */ package io.kubernetes.client.util.credentials; -import io.kubernetes.client.openapi.ApiClient; -import io.kubernetes.client.util.exception.TokenAquisitionException; -import java.io.IOException; import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.Clock; @@ -22,10 +19,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Base64; -import okhttp3.Interceptor; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; +import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; @@ -38,9 +32,10 @@ * EKS cluster authentication which generates a bearer token from AWS AK/SK. It doesn't require an "aws" * command line tool in the $PATH. */ -public class EKSAuthentication implements Authentication, Interceptor { +public class EKSAuthentication extends RefreshAuthentication { private static final Logger log = LoggerFactory.getLogger(EKSAuthentication.class); + private static final int MAX_EXPIRY_SECONDS = 60 * 15; /** * Instantiates a new Eks authentication. @@ -59,58 +54,30 @@ public EKSAuthentication(AwsCredentialsProvider provider, String region, String EKSAuthentication( AwsCredentialsProvider provider, String region, String clusterName, int expirySeconds, Clock clock) { - this.provider = provider; - this.region = region; - this.clusterName = clusterName; - if (expirySeconds > MAX_EXPIRY_SECONDS) { - expirySeconds = MAX_EXPIRY_SECONDS; - } - this.expirySeconds = expirySeconds; - this.stsEndpoint = URI.create("https://sts." + this.region + ".amazonaws.com/"); - this.clock = clock; - } - - private static final int MAX_EXPIRY_SECONDS = 60 * 15; - private final AwsCredentialsProvider provider; - private final String region; - private final String clusterName; - private final URI stsEndpoint; - - private final int expirySeconds; - private final Clock clock; - private Instant expiry = Instant.MIN; - private String token; - - @Override - public void provide(ApiClient client) { - OkHttpClient withInterceptor = client.getHttpClient().newBuilder().addInterceptor(this).build(); - client.setHttpClient(withInterceptor); + super( + tokenSupplier(provider, region, clusterName, cappedExpirySeconds(expirySeconds), clock), + Duration.of(cappedExpirySeconds(expirySeconds), ChronoUnit.SECONDS), + clock); + setExpiry(Instant.now(clock).plus(cappedExpirySeconds(expirySeconds), ChronoUnit.SECONDS)); } - @Override - public Response intercept(Interceptor.Chain chain) throws IOException { - Request request = chain.request(); - Request newRequest = request.newBuilder().header("Authorization", "Bearer " + getToken()).build(); - return chain.proceed(newRequest); + private static int cappedExpirySeconds(int expirySeconds) { + return Math.min(expirySeconds, MAX_EXPIRY_SECONDS); } - private synchronized String getToken() { - if (Instant.now(clock).isAfter(expiry)) { - try { - token = generateToken(); - } catch (RuntimeException e) { - throw new TokenAquisitionException(e); - } - expiry = Instant.now(clock).plus(expirySeconds, ChronoUnit.SECONDS); - log.info("Generated BEARER token for ApiClient, expiring at {}", expiry); - } - return token; + private static Supplier tokenSupplier( + AwsCredentialsProvider provider, String region, String clusterName, int expirySeconds, Clock clock) { + return () -> generateToken(provider, region, clusterName, expirySeconds, clock); } - private String generateToken() { - SdkHttpRequest httpRequest = generateStsRequest(); - String presignedUrl = requestToPresignedUrl(httpRequest); + private static String generateToken( + AwsCredentialsProvider provider, String region, String clusterName, int expirySeconds, Clock clock) { + SdkHttpRequest httpRequest = generateStsRequest(region, clusterName); + String presignedUrl = requestToPresignedUrl(httpRequest, provider, region, expirySeconds); String encodedUrl = presignedUrlToEncodedUrl(presignedUrl); + log.info( + "Generated BEARER token for ApiClient, expiring at {}", + Instant.now(clock).plus(expirySeconds, ChronoUnit.SECONDS)); return "k8s-aws-v1." + encodedUrl; } @@ -120,9 +87,9 @@ private static String presignedUrlToEncodedUrl(String presignedUrl) { .encodeToString(presignedUrl.getBytes(StandardCharsets.UTF_8)); } - private SdkHttpRequest generateStsRequest() { + private static SdkHttpRequest generateStsRequest(String region, String clusterName) { return SdkHttpRequest.builder() - .uri(stsEndpoint) + .uri(URI.create("https://sts." + region + ".amazonaws.com/")) .putRawQueryParameter("Version", "2011-06-15") .putRawQueryParameter("Action", "GetCallerIdentity") .method(SdkHttpMethod.GET) @@ -130,10 +97,11 @@ private SdkHttpRequest generateStsRequest() { .build(); } - private String requestToPresignedUrl(SdkHttpRequest httpRequest) { + private static String requestToPresignedUrl( + SdkHttpRequest httpRequest, AwsCredentialsProvider provider, String region, int expirySeconds) { AwsV4HttpSigner signer = AwsV4HttpSigner.create(); SignedRequest signedRequest = - signer.sign(r -> r.identity(this.provider.resolveCredentials()) + signer.sign(r -> r.identity(provider.resolveCredentials()) .request(httpRequest) .putProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "sts") .putProperty(AwsV4HttpSigner.REGION_NAME, region) From 9af4ea0f9745d67a1f4802e954fe2b777d706b72 Mon Sep 17 00:00:00 2001 From: hwayoungjun Date: Thu, 14 May 2026 21:14:35 +0900 Subject: [PATCH 3/3] fix: correct EKS auth token encoding and extract token supplier Base64-encode the presigned STS URL directly instead of URL-encoding the whole URL before Base64 encoding. The AWS signer already percent-encodes query parameters, and encoding the full URL produces a decoded token payload starting with https%3A//, which EKS rejects with 401. Set the STS request path with encodedPath("/") so the presigned URL targets the root STS resource while keeping the endpoint URI itself pathless. Replace the static tokenSupplier lambda factory with an EksTokenSupplier inner class that implements Supplier. The class holds the provider, region, cluster name, expiry, and pre-built STS endpoint URI as fields so each token generation reuses them instead of rebuilding per call. Verified against a real EKS cluster: the decoded payload starts with https://sts.../? and the Kubernetes API request succeeds. --- .../util/credentials/EKSAuthentication.java | 99 +++++++++++-------- 1 file changed, 58 insertions(+), 41 deletions(-) diff --git a/util/src/main/java/io/kubernetes/client/util/credentials/EKSAuthentication.java b/util/src/main/java/io/kubernetes/client/util/credentials/EKSAuthentication.java index 432efd6e42..f425273605 100644 --- a/util/src/main/java/io/kubernetes/client/util/credentials/EKSAuthentication.java +++ b/util/src/main/java/io/kubernetes/client/util/credentials/EKSAuthentication.java @@ -55,7 +55,7 @@ public EKSAuthentication(AwsCredentialsProvider provider, String region, String EKSAuthentication( AwsCredentialsProvider provider, String region, String clusterName, int expirySeconds, Clock clock) { super( - tokenSupplier(provider, region, clusterName, cappedExpirySeconds(expirySeconds), clock), + new EksTokenSupplier(provider, region, clusterName, cappedExpirySeconds(expirySeconds)), Duration.of(cappedExpirySeconds(expirySeconds), ChronoUnit.SECONDS), clock); setExpiry(Instant.now(clock).plus(cappedExpirySeconds(expirySeconds), ChronoUnit.SECONDS)); @@ -65,49 +65,66 @@ private static int cappedExpirySeconds(int expirySeconds) { return Math.min(expirySeconds, MAX_EXPIRY_SECONDS); } - private static Supplier tokenSupplier( - AwsCredentialsProvider provider, String region, String clusterName, int expirySeconds, Clock clock) { - return () -> generateToken(provider, region, clusterName, expirySeconds, clock); - } + private static class EksTokenSupplier implements Supplier { + private final AwsCredentialsProvider provider; + private final String region; + private final String clusterName; + private final int expirySeconds; + private final URI stsEndpoint; - private static String generateToken( - AwsCredentialsProvider provider, String region, String clusterName, int expirySeconds, Clock clock) { - SdkHttpRequest httpRequest = generateStsRequest(region, clusterName); - String presignedUrl = requestToPresignedUrl(httpRequest, provider, region, expirySeconds); - String encodedUrl = presignedUrlToEncodedUrl(presignedUrl); - log.info( - "Generated BEARER token for ApiClient, expiring at {}", - Instant.now(clock).plus(expirySeconds, ChronoUnit.SECONDS)); - return "k8s-aws-v1." + encodedUrl; - } + EksTokenSupplier(AwsCredentialsProvider provider, String region, String clusterName, int expirySeconds) { + this.provider = provider; + this.region = region; + this.clusterName = clusterName; + this.expirySeconds = expirySeconds; + this.stsEndpoint = URI.create("https://sts." + this.region + ".amazonaws.com"); + } - private static String presignedUrlToEncodedUrl(String presignedUrl) { - return Base64.getUrlEncoder() - .withoutPadding() - .encodeToString(presignedUrl.getBytes(StandardCharsets.UTF_8)); - } + @Override + public String get() { + return generateToken(); + } - private static SdkHttpRequest generateStsRequest(String region, String clusterName) { - return SdkHttpRequest.builder() - .uri(URI.create("https://sts." + region + ".amazonaws.com/")) - .putRawQueryParameter("Version", "2011-06-15") - .putRawQueryParameter("Action", "GetCallerIdentity") - .method(SdkHttpMethod.GET) - .putHeader("x-k8s-aws-id", clusterName) - .build(); - } + private String generateToken() { + SdkHttpRequest httpRequest = generateStsRequest(); + String presignedUrl = requestToPresignedUrl(httpRequest); + String encodedUrl = presignedUrlToEncodedUrl(presignedUrl); + log.info( + "Generated BEARER token for ApiClient, expiring at {}", + Instant.now().plus(expirySeconds, ChronoUnit.SECONDS)); + return "k8s-aws-v1." + encodedUrl; + } + + private static String presignedUrlToEncodedUrl(String presignedUrl) { + return Base64.getUrlEncoder() + .withoutPadding() + .encodeToString(presignedUrl.getBytes(StandardCharsets.UTF_8)); + } + + private SdkHttpRequest generateStsRequest() { + return SdkHttpRequest.builder() + .uri(stsEndpoint) + .encodedPath("/") + .putRawQueryParameter("Version", "2011-06-15") + .putRawQueryParameter("Action", "GetCallerIdentity") + .method(SdkHttpMethod.GET) + .putHeader("x-k8s-aws-id", clusterName) + .build(); + } - private static String requestToPresignedUrl( - SdkHttpRequest httpRequest, AwsCredentialsProvider provider, String region, int expirySeconds) { - AwsV4HttpSigner signer = AwsV4HttpSigner.create(); - SignedRequest signedRequest = - signer.sign(r -> r.identity(provider.resolveCredentials()) - .request(httpRequest) - .putProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "sts") - .putProperty(AwsV4HttpSigner.REGION_NAME, region) - .putProperty(AwsV4HttpSigner.AUTH_LOCATION, AwsV4HttpSigner.AuthLocation.QUERY_STRING) - .putProperty(AwsV4HttpSigner.EXPIRATION_DURATION, Duration.of(expirySeconds, ChronoUnit.SECONDS))); - SdkHttpRequest request = signedRequest.request(); - return request.getUri().toString(); + private String requestToPresignedUrl(SdkHttpRequest httpRequest) { + AwsV4HttpSigner signer = AwsV4HttpSigner.create(); + SignedRequest signedRequest = + signer.sign(r -> r.identity(this.provider.resolveCredentials()) + .request(httpRequest) + .putProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "sts") + .putProperty(AwsV4HttpSigner.REGION_NAME, region) + .putProperty(AwsV4HttpSigner.AUTH_LOCATION, AwsV4HttpSigner.AuthLocation.QUERY_STRING) + .putProperty( + AwsV4HttpSigner.EXPIRATION_DURATION, + Duration.of(expirySeconds, ChronoUnit.SECONDS))); + SdkHttpRequest request = signedRequest.request(); + return request.getUri().toString(); + } } }