Obtenga token de autenticación de AWS EKS con AWS Java SDK v2

11

¿Cómo puedo obtener un token de autenticación de Kubernetes de AWS EKS con AWS Java SDK v2? Un token de autenticación que luego puede usarse para autenticarse con Kubernetes usando un SDK de Kubernetes. En otras palabras, quiero obtener un token de autenticación de EKS para usarlo para la autenticación con Kubernetes para no tener que crear una "configuración de kube".

De hecho, obtuve una solución trabajando con AWS Java SDK v1 (no v2) mirando los ejemplos de código en el siguiente número abierto . También hay un ejemplo de código Python aquí, PERO no estoy teniendo éxito con AWS Java SDK v2. Mi intento de hacerlo con 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);
    }
}

Genera un token, pero cuando uso el token en mi cliente Kubernetes (el SDK oficial de Java Kubernetes) obtengo una respuesta "no autorizada", así que me falta algo que no puedo señalar ...

La versión AWS Java SDK v1 se parece a esto: (Del tema abierto mencionado anteriormente)

Lo hice funcionar, pero estoy luchando por obtener algo similar al trabajo en 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;
        }
    }
NS du Toit
fuente
Como se indica en el problema AWS Java SDK v1, la implementación es sensible a la especificación de una fecha de vencimiento demasiado larga. Jugué un poco con la fecha de vencimiento, pero no resolvió el problema.
NS du Toit
¿
Intentaste
He usado el aws-iam-authenticator antes, pero necesito poder generar tokens desde el código fuente de Java, sin instalar nada. Y he conseguido que esto funcione con AWS Java SDK v1, solo que tengo problemas con la v2 del SDK.
NS du Toit
Actualmente estoy usando AWS Java SDK v1 para generar el token, pero ahora tengo que tenerlo en mi classpath :( Tan pronto como pueda resolver esto, puedo refactorizar y eliminar la v1 del SDK de mis dependencias :)
NS du Toit
¿Qué versión de Kubernetes estás ejecutando? ¿Dónde se debe ejecutar esta aplicación (fuera del clúster, dentro de él)?
Mewa

Respuestas:

2

Bien, finalmente lo conseguí funcionando.

La versión AWS Java SDK v2:

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);
    }
}

El problema estaba en mi punto final STS 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);
    }
}

Tenga /en cuenta el pathargumento en el (tercer) para el URIobjeto. La versión AWS Java SDK v1 no creó el URI de esa manera, pero especificó el /resto. Si ahora imprimo el URIcomo una cadena que obtengo https://sts.eu-west-1.amazonaws.com/, mientras que la versión original en la pregunta acaba de regresarhttps://sts.eu-west-1.amazonaws.com

Lo suficientemente interesante: la versión original también generó un token, pero el token fue rechazado por Kubernetes. Uno debería esperar un comportamiento similar si la fecha de vencimiento está demasiado lejos en el futuro: obtendrá un token, pero dará lugar a una Unauthorizedrespuesta del servicio de Kubernetes.

Después de cambiar el punto final STS, todo funcionó, pero hice un cambio más:

Agregué la siguiente línea a mi Aws4PresignerParams:

.signingClockOverride(Clock.systemUTC())

No era necesario, pero el AWS Java SDK v1 original hizo algo con un reloj cuando lo especificó SdkClock.STANDARD, y el ZonedDateTimeque uso en la versión AWS Java SDK v2 usa la zona horaria UTC.

NS du Toit
fuente
Entonces, sí, lo hice funcionar, pero no tengo demasiada información sobre el razonamiento detrás de esto. Incluso sin el /todavía obtuve un token, pero como se indicó, simplemente no funcionó cuando comencé a integrarme con Kubernetes.
NS du Toit
Otro aspecto interesante aparte: el problema original de AWS Java SDK v1 indicó que el token es de muy corta duración. Tan pronto como trato de establecer la fecha de vencimiento en más de 60 segundos, sucede lo mismo: obtengo un token, pero me da una Unauthorizedrespuesta.
NS du Toit