Get authentication token from AWS EKS using the AWS Java SDK v2
Okay, I finally got it working.
The AWS Java SDK v2 version:
public static String getAuthenticationToken(AwsCredentialsProvider awsAuth, Region awsRegion, String clusterName) { try { SdkHttpFullRequest requestToSign = SdkHttpFullRequest .builder() .method(SdkHttpMethod.GET) .uri(StsUtil.getStsRegionalEndpointUri(awsRegion)) .appendHeader("x-k8s-aws-id", clusterName) .appendRawQueryParameter("Action", "GetCallerIdentity") .appendRawQueryParameter("Version", "2011-06-15") .build(); ZonedDateTime expirationDate = DateUtil.addSeconds(DateUtil.now(), 60); Aws4PresignerParams presignerParams = Aws4PresignerParams.builder() .awsCredentials(awsAuth.resolveCredentials()) .signingRegion(awsRegion) .signingName("sts") .signingClockOverride(Clock.systemUTC()) .expirationTime(expirationDate.toInstant()) .build(); SdkHttpFullRequest signedRequest = Aws4Signer.create().presign(requestToSign, presignerParams); String encodedUrl = Base64.getUrlEncoder().withoutPadding().encodeToString(signedRequest.getUri().toString().getBytes(CharSet.UTF_8.getCharset())); return ("k8s-aws-v1." + encodedUrl); } catch (Exception e) { String errorMessage = "A problem occurred generating an Eks authentication token for cluster: " + clusterName; logger.error(errorMessage, e); throw new RuntimeException(errorMessage, e); }}
The problem was in my STS endpoint Uri:
public static URI getStsRegionalEndpointUri(Region awsRegion) { try { return new URI("https", String.format("sts.%s.amazonaws.com", awsRegion.id()), "/", null); } catch (URISyntaxException shouldNotHappen) { String errorMessage = "An error occurred creating the STS regional endpoint Uri"; logger.error(errorMessage, shouldNotHappen); throw new RuntimeException(errorMessage, shouldNotHappen); }}
Note the /
in the path
(third) argument for the URI
object. The AWS Java SDK v1 version didn't create the URI like that, but specified the /
elsewhere. If I now print out the URI
as a String I get https://sts.eu-west-1.amazonaws.com/
, while the original version in the question just returned https://sts.eu-west-1.amazonaws.com
Interesting enough - the original version did also generate a token, but the token was rejected by Kubernetes. One should expect similar behavior if the expiration date is too far into the future - you'll get a token, but it will lead to an Unauthorized
response from the Kubernetes service.
After changing the STS endpoint everything worked, but I made one more change:
I added the following line to my Aws4PresignerParams
:
.signingClockOverride(Clock.systemUTC())
It wasn't required, but the original AWS Java SDK v1 did do something with a clock when it specified SdkClock.STANDARD
, and the ZonedDateTime
that I use in the AWS Java SDK v2 version does use the UTC timezone.
Just for completeness sake, in case you want to use the token with the Kubernetes client (io.kubernetes:client-java), you can add an interceptor that generates a valid token before every request:
private ApiClient createClient(AwsCredentialsProvider awsAuth, Region awsRegion, String clusterName) { ApiClient client = Config.fromUrl(eksEndpoint, false); OkHttpClient httpClient = createHttpClient(client.getHttpClient(), awsAuth, awsRegion, clusterName); client.setHttpClient(httpClient); return client; } @NotNull private OkHttpClient createHttpClient( OkHttpClient baseHttpClient, AwsCredentialsProvider awsAuth, Region awsRegion, String clusterName ) { OkHttpClient.Builder builder = new OkHttpClient.Builder(baseHttpClient); builder.addInterceptor(chain -> { Request request = chain.request(); String token = getAuthenticationToken(awsAuth, awsRegion, clusterName); Request newRequest = request .newBuilder() .header("Authorization", "Bearer " + token) .build(); return chain.proceed(newRequest); }); return builder.build(); }