<p>How can I get a Kubernetes authentication token from AWS EKS using the AWS Java SDK v2?  An authentication token that can then be used to authenticate with Kubernetes using a Kubernetes SDK.  In other words I want to get an authentication token from EKS to use for authentication with Kubernetes so that I don't have to create a "kube config".</p>

<p>I actually got a solution working with AWS Java SDK v1 (not v2) looking at the code examples in the following <a href="https://github.com/aws/aws-sdk-java/issues/1795" rel="noreferrer">open issue</a>.  There is also a Python code example <a href="https://github.com/kubernetes-sigs/aws-iam-authenticator#api-authorization-from-outside-a-cluster" rel="noreferrer">here</a> BUT I'm not having any success with AWS Java SDK v2.  My attempt at doing it with AWS Java SDK v2:</p>

<pre class="lang-java prettyprint-override"><code>public static String getAuthenticationToken(AwsCredentialsProvider awsAuth, Region awsRegion, String clusterName) {
    try {
        SdkHttpFullRequest requestToSign = SdkHttpFullRequest
                .builder()
                .method(SdkHttpMethod.GET)
                .uri(new URI("https", String.format("sts.%s.amazonaws.com", awsRegion.id()), null, null))
                .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())
                .expirationTime(expirationDate.toInstant())
                .signingName("sts")
                .signingRegion(awsRegion)
                .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 token";
        logger.error(errorMessage, e);
        throw new RuntimeException(errorMessage, e);
    }
}
</code></pre>

<p>It generates a token, but when I use the token in my Kubernetes client (the official Java Kubernetes SDK) I get back an "Unauthorized" response - so I'm missing something I can't put my finger on...</p>

<p>The AWS Java SDK v1 version looks something like this:
(From the <a href="https://github.com/aws/aws-sdk-java/issues/1795" rel="noreferrer">open issue</a> mentioned earlier)</p>

<p>I got it working, but I'm struggling to get something similar to work in AWS Java SDK v2.</p>

<pre class="lang-java prettyprint-override"><code>private String generateToken(String clusterName,
                                 Date expirationDate,
                                 String serviceName,
                                 String region,
                                 AWSSecurityTokenServiceClient awsSecurityTokenServiceClient,
                                 AWSCredentialsProvider credentialsProvider,
                                 String scheme,
                                 String host) throws URISyntaxException {
        try {
            DefaultRequest&lt;GetCallerIdentityRequest&gt; callerIdentityRequestDefaultRequest = new DefaultRequest&lt;&gt;(new GetCallerIdentityRequest(), serviceName);
            URI uri = new URI(scheme, host, null, null);
            callerIdentityRequestDefaultRequest.setResourcePath("/");
            callerIdentityRequestDefaultRequest.setEndpoint(uri);
            callerIdentityRequestDefaultRequest.setHttpMethod(HttpMethodName.GET);
            callerIdentityRequestDefaultRequest.addParameter("Action", "GetCallerIdentity");
            callerIdentityRequestDefaultRequest.addParameter("Version", "2011-06-15");
            callerIdentityRequestDefaultRequest.addHeader("x-k8s-aws-id", clusterName);

            Signer signer = SignerFactory.createSigner(SignerFactory.VERSION_FOUR_SIGNER, new SignerParams(serviceName, region));
            SignerProvider signerProvider = new DefaultSignerProvider(awsSecurityTokenServiceClient, signer);
            PresignerParams presignerParams = new PresignerParams(uri,
                    credentialsProvider,
                    signerProvider,
                    SdkClock.STANDARD);

            PresignerFacade presignerFacade = new PresignerFacade(presignerParams);
            URL url = presignerFacade.presign(callerIdentityRequestDefaultRequest, expirationDate);
            String encodedUrl = Base64.getUrlEncoder().withoutPadding().encodeToString(url.toString().getBytes());
            log.info("Token [{}]", encodedUrl);
            return "k8s-aws-v1." + encodedUrl;
        } catch (URISyntaxException e) {
            log.error("could not generate token", e);
            throw e;
        }
    }
</code></pre>

<p>Okay, I finally got it working.</p>

<p>The AWS Java SDK v2 version:</p>

<pre class="lang-java prettyprint-override"><code>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);
    }
}
</code></pre>

<p>The problem was in my STS endpoint Uri:</p>

<pre class="lang-java prettyprint-override"><code>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);
    }
}
</code></pre>

<p>Note the <code>/</code> in the <code>path</code> (third) argument for the <code>URI</code> object.  The AWS Java SDK v1 version didn't create the URI like that, but specified the <code>/</code> elsewhere.  If I now print out the <code>URI</code> as a String I get <code>https://sts.eu-west-1.amazonaws.com/</code>, while the original version in the question just returned <code>https://sts.eu-west-1.amazonaws.com</code></p>

<p>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 <code>Unauthorized</code> response from the Kubernetes service.</p>

<p>After changing the STS endpoint everything worked, but I made one more change:</p>

<p>I added the following line to my <code>Aws4PresignerParams</code>:</p>

<pre class="lang-java prettyprint-override"><code>.signingClockOverride(Clock.systemUTC())
</code></pre>

<p>It wasn't required, but the original AWS Java SDK v1 did do something with a clock when it specified <code>SdkClock.STANDARD</code>, and the <code>ZonedDateTime</code> that I use in the AWS Java SDK v2 version does use the UTC timezone.</p>
