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".
I actually got a solution working with AWS Java SDK v1 (not v2) looking at the code examples in the following open issue. There is also a Python code example here BUT I'm not having any success with AWS Java SDK v2. My attempt at doing it with AWS Java SDK v2:
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);
}
}
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...
The AWS Java SDK v1 version looks something like this: (From the open issue mentioned earlier)
I got it working, but I'm struggling to get something similar to work in AWS Java SDK v2.
private String generateToken(String clusterName,
Date expirationDate,
String serviceName,
String region,
AWSSecurityTokenServiceClient awsSecurityTokenServiceClient,
AWSCredentialsProvider credentialsProvider,
String scheme,
String host) throws URISyntaxException {
try {
DefaultRequest<GetCallerIdentityRequest> callerIdentityRequestDefaultRequest = new DefaultRequest<>(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;
}
}
Okay, I finally got it working.
The AWS Java SDK v2 version:
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.signer.Aws4Signer;
import software.amazon.awssdk.auth.signer.params.Aws4PresignerParams;
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.regions.Region;
import util.string.CharSet;
import java.time.Clock;
import java.time.ZonedDateTime;
import java.util.Base64;
public class EksUtil {
private static final Logger logger = LoggerFactory.getLogger(EksUtil.class);
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.