aws-sdk-java icon indicating copy to clipboard operation
aws-sdk-java copied to clipboard

Need aws-iam-authenticator support in java sdk

Open vinayakanzen opened this issue 6 years ago • 36 comments

As of now, there is no "aws-iam-authenticator" like support in Java SDK. Through code, the user can't authenticate the cluster due to AWS IAM restrictions, while GKE and AKS provide the credentials to authenticate the clusters respectively.

vinayakanzen avatar Oct 24 '18 15:10 vinayakanzen

To clarify, you are asking for something like this tool: https://github.com/kubernetes-sigs/aws-iam-authenticator to be fully implemented in the Java SDK?

debora-ito avatar Oct 25 '18 00:10 debora-ito

Hello,

Is there an update about when SDK support for the aws-iam-authenticator might be available?

shashank-g172 avatar Nov 21 '18 15:11 shashank-g172

Hello,

Is there any work around to connect to cluster from java code ?

evolv34 avatar Dec 04 '18 12:12 evolv34

@shashank-g172 @vinayakanzen I just tried a simple work around to create a token. It worked for me. I am using java sdk version 1.11.458

Value for the below function

clusterName = <cluster-name>
expirationDate = date that the token would expire 0 - 60.
schema = "https"
host= "sts.amazonaws.com"
region = <cluster-region>
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;
        }
    }

I hope this is helpful.

evolv34 avatar Dec 07 '18 13:12 evolv34

Hello @evolv34 , thank you for the suggestion.

I tried it out with these values:

generateToken({clusterName}, {current date + 2 hours}, "AmazonEKS", Regions.US_EAST_1.getName(), new AWSSecurityTokenServiceClient(), new DefaultAWSCredentialsProviderChain().getInstance(), "https", "sts.amazonaws.com");

and I could retrieve a token which looks a lot like the token generated by the aws-iam-authenticator service. However, when I tried to deploy the config Map in order to attach the worker nodes to the cluster, I get an "Unauthorized" error saying that the token may have expired.

Unfortunately, that message seems to be overloaded because I've seen it in other contexts without getting to the actual problem.

Failure executing: POST at:{clusterEndPont}/api/v1/namespaces/kube-system/configmaps. Message: Unauthorized! Token may have expired! Please log-in again. Unauthorized.

I'm creating a cluster as described by the AWS docs, and retrieving the nodeInstaceId and deploying the config map as outlined here: https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html#eks-launch-workers

Were you able to successfully deploy the configMap?

shashank-g172 avatar Dec 10 '18 19:12 shashank-g172

@shashank-g172 Yes it worked for me. Can you try setting expiration time to 60 seconds instead of 2 hours https://github.com/kubernetes-sigs/aws-iam-authenticator/blob/master/pkg/token/token.go#L240-L242 The server code has a limitation. I hope that fixes the issue.

Also if it does nt work. Could you please share more details about which version of the aws sdk version are you using ?

Thanks.

evolv34 avatar Dec 10 '18 23:12 evolv34

Hello @evolv34 , I tried setting it like you suggested to 60 seconds. Here are my SDK versions (snipped for brevity):

<dependencies> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-s3</artifactId> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-eks</artifactId> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-cloudformation</artifactId> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-iam</artifactId> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-sts</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-bom</artifactId> <version>1.11.458</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>`

If it helps, I'm trying to deploy the configMap using The fabric8 Kubenretes client after retrieving the roleArn (the nodeInstanceId from the cloudFormation stack where the worker nodes are deployed) like this:

public void deployAWSMapToCluster(String roleArn, KubernetesClient kubernetesClient) { Map<String, String> data = new LinkedHashMap<String, String>(); data.put("mapRoles", "- rolearn: " + roleArn + "\n username: system:node:{{EC2PrivateDNSName}}\n groups:\n - system:bootstrappers\n - system:nodes\n"); ConfigMap builtConfigMap = new ConfigMapBuilder().withApiVersion("v1").withKind("ConfigMap").withNewMetadata() .withName("aws-auth").withNamespace("kube-system").endMetadata().withData(data).build(); kubernetesClient.configMaps().inNamespace("kube-system").withName("aws-auth") .create(builtConfigMap); logger.info("Deployed config map: '{}'", builtConfigMap.toString()); }

shashank-g172 avatar Dec 11 '18 14:12 shashank-g172

Hi @shashank-g172, Let me try it. I am using

<dependency>
  <groupId>io.kubernetes</groupId>
  <artifactId>client-java</artifactId>
  <version>2.0.0</version>
</dependency>

I am using official kubernetes java client https://github.com/kubernetes-client/java. Let me try it with fabric8 and I will get back to you if there is anything. I am missing on this.

evolv34 avatar Dec 11 '18 15:12 evolv34

Thank you. I appreciate the help. Here's some more information if needed. this is now I'm creating the Kubernetes client :

// The cluster name is being provided in this case and not created public KubernetesClient createKubernetesClientWithKnownToken(Optional<String> providedClusterName, String token) { String clusterToCheck = providedClusterName.or(createdClusterName); DescribeClusterRequest describeClusterRequest = new DescribeClusterRequest().withName(clusterToCheck); logger.info("Cluster being checked: '{}'", clusterToCheck); String endPoint = AmazonEKSAsyncClientBuilder.standard().withRegion(Regions.US_EAST_1).build().describeCluster(describeClusterRequest).getCluster().getEndpoint(); return new DefaultKubernetesClient(new ConfigBuilder().withMasterUrl(endPoint) .withOauthToken(token).withTrustCerts(true).build()); }

shashank-g172 avatar Dec 11 '18 16:12 shashank-g172

Hi @evolv34 , this may be a long shot , but is there any way you ran into the exact same issue I did with the expired token?

shashank-g172 avatar Jan 16 '19 19:01 shashank-g172

Hi @shashank-g172 @evolv34, Is there any way through Java code where I can deploy a "deployment", "ClusterRoleBinding", "ClusterRole", "ClusterRole", "Namespace" after deploying the config map. If Yes, can you please help me with that.

vinayakanzen avatar Jan 19 '19 12:01 vinayakanzen

Great thanks for that. Here's my 2 cents:

  1. basically (as you can see from the sample), token is actually "k8s-aws-v1." + base64EncodedUrl. What I did is just compared output of aws-iam-authenticator token -i <ClusterName> and the result above to understand what I did wrong
  2. service name is "sts", not EKS or smth like this.
  3. region is "us-east-1"
  4. I didn't provide any expiration date.
  5. Don't bother clicking the generated URL - it doesn't work (complains on signature) - it's not designed for that.

Below goes my working sample (in Kotlin):

fun connect(){
        val credentialsProvider = AWSStaticCredentialsProvider(BasicAWSCredentials(ACCESS_KEY, SECRET_KEY))
        val eks = AmazonEKSClientBuilder.standard()
                .withCredentials(credentialsProvider)
                .withRegion("eu-west-1")
                .build()
        val cluster = eks.describeCluster(DescribeClusterRequest().withName("My-Test")).cluster
        println(cluster.clientRequestToken)
        val tokenService : AWSSecurityTokenServiceClient = AWSSecurityTokenServiceClientBuilder
                .standard()
                .withRegion(Regions.EU_WEST_1)
                .withCredentials(credentialsProvider)
                .build() as AWSSecurityTokenServiceClient
        val token = generateToken(cluster.name,
                Date(),
                "sts", "us-east-1", tokenService, credentialsProvider, "https", "sts.amazonaws.com"
        )
        println(token)
    }

    private fun generateToken(clusterName: String,
                              expirationDate: Date,
                              serviceName: String,
                              region: String,
                              awsSecurityTokenServiceClient: AWSSecurityTokenServiceClient,
                              credentialsProvider: AWSCredentialsProvider,
                              scheme: String,
                              host: String): String {
        try {
            val callerIdentityRequestDefaultRequest = DefaultRequest<GetCallerIdentityRequest>(GetCallerIdentityRequest(), serviceName)
            val uri = URI(scheme, host, null, null)
            callerIdentityRequestDefaultRequest.resourcePath = "/"
            callerIdentityRequestDefaultRequest.endpoint = uri
            callerIdentityRequestDefaultRequest.httpMethod = HttpMethodName.GET
            callerIdentityRequestDefaultRequest.addParameter("Action", "GetCallerIdentity")
            callerIdentityRequestDefaultRequest.addParameter("Version", "2011-06-15")
            callerIdentityRequestDefaultRequest.addHeader("x-k8s-aws-id", clusterName)
            val signer = SignerFactory.createSigner(SignerFactory.VERSION_FOUR_SIGNER, SignerParams(serviceName, region))
            val signerProvider = DefaultSignerProvider(awsSecurityTokenServiceClient, signer)
            val presignerParams = PresignerParams(uri,
                    credentialsProvider,
                    signerProvider,
                    SdkClock.STANDARD)
            val presignerFacade = PresignerFacade(presignerParams)
            val url = presignerFacade.presign(callerIdentityRequestDefaultRequest, expirationDate)
            val encodedUrl = Base64.getUrlEncoder().withoutPadding().encodeToString(url.toString().toByteArray())
            println("$url")
            return "k8s-aws-v1.$encodedUrl"
        } catch (e: URISyntaxException) {
            e.printStackTrace()
            throw e
        }
    }

paksv avatar Feb 04 '19 22:02 paksv

Changing it from EKS to sts worked. I can now list my nodes and events from the kubenretesClient object.

Thank you very much @paksv

shashank-g172 avatar Feb 08 '19 20:02 shashank-g172

Hello @evolv34, I am using official Kubernetes Java API to connect to EKS Cluster. When I try to get ApiClient object I am getting Unauthorized exception. I have followed the code which you shared.

Below is the code I have used to get ApiClient object

AWSCredentialsProvider credentialsProvider =  new DefaultAWSCredentialsProviderChain();
AWSSecurityTokenServiceClient  tokenService = (AWSSecurityTokenServiceClient) AWSSecurityTokenServiceClientBuilder.standard().withCredentials(provider)
                        .withRegion(Regions.US_WEST_2)
                        .build();						
String initialToken = generateToken(clusterName,new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(60)),"sts",Regions.US_WEST_2.getName(),tokenService,credentialsProvider,"https", "sts.amazonaws.com");	
				
ApiClient client = Config.fromToken(url,initialToken ); 

I have added below dependencies.

<dependency>
  <groupId>io.kubernetes</groupId>
  <artifactId>client-java</artifactId>
  <version>4.0.0</version>  			
</dependency>
<dependency>
	 <groupId>com.amazonaws</groupId>
	 <artifactId>aws-java-sdk</artifactId>
	 <version>1.11.509</version>
</dependency>		   

<dependency>
	<groupId>com.amazonaws</groupId>
	<artifactId>aws-java-sdk-core</artifactId>
	<version>1.11.509</version>
</dependency>

<dependency>
	<groupId>com.amazonaws</groupId>
	<artifactId>aws-java-sdk-eks</artifactId>
	<version>1.11.509</version>
</dependency>
<dependency>
	<groupId>com.amazonaws</groupId>
	<artifactId>aws-java-sdk-sts</artifactId>
	<version>1.11.509</version>
</dependency>

seshu452 avatar Apr 01 '19 13:04 seshu452

Hi @seshu452, try to use "sts.your-region.amazonaws.com" instead of "sts.amazonaws.com". e.g. sts.us-west-2.amazonaws.com

precompiler avatar Apr 30 '19 19:04 precompiler

Any ETA on supporting this in the SDK?

hugomcfonseca avatar Jul 31 '19 20:07 hugomcfonseca

does anyone has this working? Pretty annoying I tried everything above and nothing works. I still get

Failure executing: GET at: https://xyzzzz.us-west-2.eks.amazonaws.com/api/v1/pods. Message: Unauthorized! Token may have expired! Please log-in again. Unauthorized.

The idea is

  1. Generate EKS token from STS
private static 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;
    }
  }
  1. Use this token and configure client ApiClient apiClient = (new ClientBuilder()) .setBasePath(endPoint) .setAuthentication(new AccessTokenAuthentication(token)) .setVerifyingSsl(true) .setCertificateAuthority(bytes) .build();

I checked the token generated with above code from Step 1 and also below command line and they look pretty similar and no issues from what I see.

aws eks get-token --cluster-name <cluster name>

rkand4 avatar Jan 03 '20 05:01 rkand4

@rkand4 , what version of the SDK are you using? When I finally got my solution working, I believe I was on 1.11.458, but that was a year ago.

What does upgrading your dependencies to the latest stable version do?

shashank-g172 avatar Jan 03 '20 14:01 shashank-g172

@shashank-g172 I have the following the latest. So frustrating and the token looks very much alike with that generated by the command line I put before but doesn't work.

<dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk</artifactId>
            <version>1.11.700</version>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-core</artifactId>
            <version>1.11.700</version>
        </dependency>

        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-eks</artifactId>
            <version>1.11.700</version>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-sts</artifactId>
            <version>1.11.700</version>
        </dependency>

rkand4 avatar Jan 06 '20 05:01 rkand4

@shashank-g172 I just got it working. The issue was with the region. I was passing the region all the way which was different than US_EAST_1 but looks for STS service token generation flow the region MUST BE US_EAST_1. Then it worked now. Phew ... I mean depending on what account setting has. Looks like you need to check your account to see STS is enabled in other regions and stuff. https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html

rkand4 avatar Jan 06 '20 06:01 rkand4

@rkand4 Awesome! Shout out to @evolv34 again for the suggestion! Looks like the original request has been solved with this work around. I know it's worked for at least 3 of us.

shashank-g172 avatar Jan 06 '20 14:01 shashank-g172

Has anyone solved this problem with v2 of the AWS Java SDK?

CloudNinja42 avatar Jan 20 '20 11:01 CloudNinja42

I created a question on stackoverflow around solving a similar problem with version 2 of the AWS Java SDK. I was able to generate a token, but Kubernetes returned "Unauthorized" so I'm missing something..

CloudNinja42 avatar Jan 29 '20 07:01 CloudNinja42

No luck for me..I used the code above to generate the token but does work. Get SignatureDoesNotMatch when verifying the token. Describing all experiments below to see if somebody has a hint what might be going on. Doing a (demo is cluster name): aws-iam-authenticator.exe token -i demo And copy/paste the token to the fabric8 code to connect to cluster works perfect. Buy when the token is generated via the Java code described in this page. it does not work gives Unauthorized. I checked the generated token with (TK is the generated token): aws-iam-authenticator.exe verify -i demo -t %TK% Gives:

could not verify token: sts getCallerIdentity failed: error from AWS (expected 200, got 403). Body: {"Error":{"Code":"SignatureDoesNotMatch","Message":"The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.","Type":"Sender"},"RequestId":"1e370b71-d40f-4ac4-9c08-7e6a304ddd12"}

I decoded the token generated by JAVA code and the token generated by the aws-iam-authenticator.exe tool. They are about the same: This is the valid one:

https://sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJOE5HZU7HAXJQV3A/20200805/us-east-1/sts/aws4_request&X-Amz-Date=20200805T110621Z&X-Amz-Expires=0&X-Amz-SignedHeaders=host;x-k8s-aws-id&X-Amz-Signature=46c394758cac9e4eddda5a852f560ef93cd54673649edb2d3a2a7d6e0407174e

This is the invalid one generated by the Java Signer:

https://sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20200805T105706Z&X-Amz-SignedHeaders=host;x-k8s-aws-id&X-Amz-Expires=59&X-Amz-Credential=AKIAW6OIBUAA6C7Z2KOH/20200805/us-east-1/sts/aws4_request&X-Amz-Signature=336d9e4cd955ada2a89fc6b071a20c296ec0f61b8f96de53056382830d0844c6

Tried config both with the default default credentials (looked from .aws\credentials file, and explicitly paste/copy to test code). Generated and used new keys without special chars (just in case). Use EksClient fron java to confirn that credentials are fine (see code below in V2). Problem is really the generate token, likely the signature as per the message . Using "sts.amazonaws.com" vs String.format("sts.%s.amazonaws.com", region); (with region=us-east-1) did not make any difference. Tried with V1 API e V2 API. Fails both ways the same. With V2 made sure UTC is used.

Trying to generated the hash manually is a nightmare..so hoping to be able the use th SDK signer. Trying in V1 versions 1.11.509 and 1.11.832.

I've the felling could be something minor..but three days of work lost, many experiments and variation, and no progress.. :o( Below I show the code used for V1 and V2 for reference. (Cluster is just a descriptor Java class with name, key, and secret).

Thanks in advance for any tip/help.. Cheers,

public class AwsProvider1 {
	protected final Log logger = LogFactory.getLog(getClass());

	public String generateStsToken(Cluster cluster, String region) {
		BasicAWSCredentials awsCreds = new BasicAWSCredentials(cluster.getKey(), cluster.getSecret());
		AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(awsCreds);
		if (region==null) {
			region = Regions.US_EAST_1.getName();
		}
		AWSSecurityTokenServiceClient tokenService = (AWSSecurityTokenServiceClient) AWSSecurityTokenServiceClientBuilder
				.standard()
				.withCredentials(credentialsProvider)
				.withRegion(region)
				.build();
		String host = "sts.amazonaws.com"; //String.format("sts.%s.amazonaws.com", region); 
		return generateToken(cluster.getName(), "sts", region, tokenService, credentialsProvider, "https", host);
	}

	public String generateStsToken(String clusterName, String region) {
		AWSCredentialsProvider credentialsProvider =  new DefaultAWSCredentialsProviderChain();
		if (region==null) {
			region = Regions.US_EAST_1.getName();
		}
		AWSSecurityTokenServiceClient  tokenService = (AWSSecurityTokenServiceClient) AWSSecurityTokenServiceClientBuilder
					.standard()
					.withCredentials(credentialsProvider)
					.withRegion(region)
					.build();
		String host = "sts.amazonaws.com"; //String.format("sts.%s.amazonaws.com", region); 
		return generateToken(clusterName, "sts", region, tokenService, credentialsProvider, "https", host);	
	}

	public String generateToken(String clusterName, String serviceName, String region,
		AWSSecurityTokenServiceClient tokenService, AWSCredentialsProvider credentialsProvider,
		String scheme, String host) {
		if (region==null) {
			region = Regions.US_EAST_1.getName();
		}
		try {
			DefaultRequest<GetCallerIdentityRequest> request = new DefaultRequest<>(new GetCallerIdentityRequest(), serviceName);
			URI uri = new URI(scheme, host, null, null);
			request.setResourcePath("/");
			request.setEndpoint(uri);
			request.setHttpMethod(HttpMethodName.GET);
			request.addParameter("Action", "GetCallerIdentity");
			request.addParameter("Version", "2011-06-15");
			request.addHeader("x-k8s-aws-id", clusterName);

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

			PresignerFacade presignerFacade = new PresignerFacade(presignerParams);
			AWSCredentials cred = presignerParams.credentialsProvider().getCredentials();

			Date edate = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(60));
			URL url = presignerFacade.presign(request, edate);
			String encodedUrl = Base64.getUrlEncoder().withoutPadding().encodeToString(url.toString().getBytes());
			logger.info(String.format("generateToken: %s %s", DateTimeFormatter.ISO_INSTANT.format(expirationDate.toInstant()), encodedUrl));			
			return "k8s-aws-v1." + encodedUrl;
		} catch (URISyntaxException e) {
			logger.error(String.format("generateToken: %s", e));
			return null;
		}
	}

For V2:

public class AwsProvider {
	protected final Log logger = LogFactory.getLog(getClass());

	public String generateStsToken(Cluster cluster, Region region, Date signDate) {
		AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(AwsBasicCredentials.create(cluster.getKey(), cluster.getSecret()));
		return generateToken(credentialsProvider, region, cluster.getName(), signDate);
	}


	public String generateToken(AwsCredentialsProvider awsAuth, Region region, String clusterName, Date signDate) {
		try {	
			if (region==null) {
				region = Region.US_EAST_1;
			}
			SdkHttpFullRequest requestToSign = SdkHttpFullRequest
					.builder()
					.method(SdkHttpMethod.GET)
					.uri(getStsRegionalEndpointUri(region))
					.encodedPath("/") 
					.appendHeader("x-k8s-aws-id", clusterName)
					.appendRawQueryParameter("Action", "GetCallerIdentity")
					.appendRawQueryParameter("Version", "2011-06-15")
					.build();

			ZoneId zoneId =  ZoneId.of("UTC");
			ZonedDateTime now = ZonedDateTime.ofInstant(Instant.now(), zoneId);
			//Instant signInstant = signDate!=null ? ZonedDateTime.ofInstant(signDate.toInstant(), zoneId).toInstant() : now.toInstant();
			ZonedDateTime zsignDate = now;
			ZonedDateTime expirationDate = ZonedDateTime.ofInstant(zsignDate.plusSeconds(60).toInstant(), zoneId);

		    Instant expiry = Instant.now().plusSeconds(20); // must be <= 60 seconds

			System.out.println("!now:" + now);
			System.out.println("!zsignDate:" + zsignDate);
			System.out.println("!expirationDate:" + expirationDate);

			Aws4PresignerParams presignerParams = Aws4PresignerParams.builder()
					.awsCredentials(awsAuth.resolveCredentials())
					.expirationTime(expiry)
					.signingRegion(region)
					.signingName("sts")
					//.timeOffset(timeOffset)
					//.signingClockOverride(Clock.systemUTC())
					//.signingClockOverride(Clock.fixed(signInstant, zoneId))
					.build();
			System.out.println("KeyId:" + awsAuth.resolveCredentials().accessKeyId()); //tmp debug
			System.out.println("Secret:" + awsAuth.resolveCredentials().secretAccessKey());  //tmp debug
			SdkHttpFullRequest signedRequest = org.einnovator.devops.manager.provider.Aws4Signer.create().presign(requestToSign, presignerParams);
			MapUtil.print(signedRequest.rawQueryParameters());	
			
			String encodedUrl = Base64.getUrlEncoder().withoutPadding().encodeToString(signedRequest.getUri().toString().getBytes());
			String token = ("k8s-aws-v1." + encodedUrl);
			System.out.println("!!" + signedRequest.getUri().toString()); //tmp debug
			logger.info(String.format("generateToken: %s %s", DateTimeFormatter.ISO_INSTANT.format(expirationDate.toInstant()), encodedUrl));			

                        //tmp code to get list of clusters and confirm that connection with EKS works fine
			EksClient eksClient =  EksClient.builder()
					.credentialsProvider(awsAuth)
					.build();
			ListClustersResponse response = eksClient.listClusters();
			for (String c: response.clusters()) {
				System.out.println(">>>" + c);
			}
			return token;
		} catch (Exception e) {
			logger.error(String.format("generateToken: %s", e));
			return null;
		}
	}
	
	public URI getStsRegionalEndpointUri(Region awsRegion) {
	    try {
	        return new URI("https", "sts.amazonaws.com", "/", null);
	        //return new URI("https", String.format("sts.%s.amazonaws.com", awsRegion.id()), "/", null);
	    } catch (URISyntaxException e) {
			logger.error(String.format("generateToken: %s", e));
			return null;
	    }
	}
}

jsimao71 avatar Aug 05 '20 11:08 jsimao71

I faced a similar issue. The generated token after Base64 decode appeared exactly the same as generated by "aws eks get-token --cluster-name <>" One thing I noticed was the presence of extra "==" characters at the end of the token generated from AWS SDK. After removing these characters it worked. I noticed that AWS CLI is also doing the same by calling rstrip('=') https://github.com/aws/aws-cli/blob/v2/awscli/customizations/eks/get_token.py

Well, the best would be that this is natively supported by the JAVA SDK.

rahulgupta999 avatar May 27 '21 13:05 rahulgupta999

这个可用:

public String getToken() {
        try {
            URI uri = new URI("https", String.format("sts.%s.amazonaws.com.cn", Region.CN_NORTHWEST_1.id()), "/", null);
            SdkHttpFullRequest requestToSign = SdkHttpFullRequest
                    .builder()
                    .method(SdkHttpMethod.GET)
                    // https://docs.amazonaws.cn/aws/latest/userguide/endpoints-Ningxia.html
                    .uri(uri)
                    .appendHeader("x-k8s-aws-id", clusterName)
                    .appendRawQueryParameter("Action", "GetCallerIdentity")
                    .appendRawQueryParameter("Version", "2011-06-15")
                    .build();

            ZoneId zoneId = ZoneId.of("UTC");
            ZonedDateTime now = ZonedDateTime.ofInstant(Instant.now(), zoneId);
            ZonedDateTime expirationDate = ZonedDateTime.ofInstant(now.plusSeconds(60).toInstant(), zoneId);
            Aws4PresignerParams presignerParams = Aws4PresignerParams.builder()
                    .awsCredentials(AwsBasicCredentials.create(appId, appSecret))
                    .signingRegion(Region.CN_NORTHWEST_1)
                    .signingName("sts")
                    .expirationTime(expirationDate.toInstant())
                    .build();
            SdkHttpFullRequest signedRequest = Aws4Signer.create().presign(requestToSign, presignerParams);
            String encodedUrl = Base64.getUrlEncoder().withoutPadding().encodeToString(signedRequest.getUri().toString().getBytes(StandardCharsets.UTF_8));
            return ("k8s-aws-v1." + encodedUrl);
        } catch (Exception e) {
            String errorMessage = "A problem occurred generating an Eks authentication token for cluster: " + clusterName;
            throw new RuntimeException(errorMessage, e);
        }
    }

izerui avatar Apr 10 '22 05:04 izerui

Hi, I checked the above java examples. Unless I add the aws_session_token in the request parameter, the generated token is throwing an error like the below. image

aws_session_token in the request query parameter code - (X-Amz-Security-Token) image

My use case is to use this token for invoking helm and kubectl commands for eks cluster from my dev machine.

srinivasev avatar Apr 21 '22 11:04 srinivasev

eter code - (X-Amz-Se

Please refer to mine, don't forget to change the address and region!

izerui avatar Apr 21 '22 11:04 izerui

Hi @izerui , I tried your code, I am facing the same issue. Unless I pass the aws_session_token, have the same Unauthorized error.

srinivasev avatar Apr 22 '22 10:04 srinivasev

HI @srinivasev ,

You should use the sts token request, which may be in two directions with the eks oauthToken, perhaps consider using the sts apito obtain aws_session_token:

StsClient.builder()
                    .region(properties.getRegion())
                    .credentialsProvider(() -> AwsBasicCredentials.create(properties.getAccessKeyId(), properties.getSecretAccessKey()))
                    .build().getSessionToken(builder -> builder.durationSeconds(durationSeconds));

Or use oauthToken You can refer to this: https://medium.com/@rschoening/eks-client-authentication-f17f39228dc

izerui avatar Apr 23 '22 04:04 izerui