From 647b56cae3baef94a12d21a61ee92b8d8e5021d8 Mon Sep 17 00:00:00 2001 From: arimu1 <19286898+arimu1@users.noreply.github.com> Date: Thu, 6 Aug 2026 06:44:45 +0700 Subject: [PATCH] fix: identify ECDSA algorithm for client keys from exec kubeconfig (#8022) When a kubeconfig exec credential plugin returns client certificate/key data, set clientKeyAlgo via getKeyAlgorithm (same as static client-key entries). Without this, ECDSA keys left the algorithm unset and CertUtils defaulted to RSA, failing key load. --- CHANGELOG.md | 1 + .../client/internal/KubeConfigUtils.java | 3 +++ .../fabric8/kubernetes/client/ConfigTest.java | 27 +++++++++++++++++++ .../src/test/resources/cert-generator-ec | 14 ++++++++++ .../test/resources/cert-generator-ec-win.bat | 11 ++++++++ .../test-kubeconfig-exec-cert-auth-ec | 20 ++++++++++++++ .../test-kubeconfig-exec-win-cert-auth-ec | 20 ++++++++++++++ 7 files changed, 96 insertions(+) create mode 100755 kubernetes-client-api/src/test/resources/cert-generator-ec create mode 100644 kubernetes-client-api/src/test/resources/cert-generator-ec-win.bat create mode 100644 kubernetes-client-api/src/test/resources/test-kubeconfig-exec-cert-auth-ec create mode 100644 kubernetes-client-api/src/test/resources/test-kubeconfig-exec-win-cert-auth-ec diff --git a/CHANGELOG.md b/CHANGELOG.md index 104e2db292d..a5fc7d5f09f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### 7.9-SNAPSHOT #### Bugs +* Fix #8022: (kubernetes-client-api) Client key algorithm is now detected from key material returned by kubeconfig exec credential plugins. Previously only static `client-key-data` / `client-key` entries ran `getKeyAlgorithm` (see #3535); exec-provided ECDSA keys left `clientKeyAlgo` unset so `CertUtils` defaulted to RSA and failed with `Invalid DER: object is not integer` * Fix #7807: (kube-api-test) The `KUBE_API_TEST_STARTUP_TIMEOUT` environment variable is now parsed instead of throwing `ClassCastException` on every use — the value (always a `String`) was passed to `Class.cast()` for an `Integer`/`Boolean` target, which only widens reference types and never parses, so the timeout could not be configured via environment at all. A new `startupTimeout` attribute on `@EnableKubeAPIServer` (e.g. `@EnableKubeAPIServer(startupTimeout = 180000)`) allows overriding it declaratively, and all three configuration paths (annotation, builder, env var) now reject non-positive values * Fix #7983: (mockwebserver) WebSocket upgrades are now performed synchronously from the Vert.x request handler instead of from the asynchronous `HttpServerRequest#body()` callback. Deferring the upgrade let the request end event be processed first, so `HttpServerRequest#toWebSocket()` intermittently threw `IllegalStateException: Request has already been read` and the upgrade was lost (surfacing as flaky `exec`/`attach` mock-server tests). Upgrade requests carry no body, so they are detected via the `Upgrade` header and upgraded before the request is read; the asynchronous path is unchanged for regular HTTP requests * Fix #7955: (java-generator) Malicious CRD schema values can no longer inject executable code into the generated Java sources. Schema-controlled values (enum values, CRD group/version/names, property names, descriptions and defaults) are emitted as fully escaped Java string literals, so a value carrying a Unicode-escaped quote cannot break out of its literal once `javac` decodes it. As a defense in depth, each generated class is also re-parsed and structurally validated before it is written (with Java Unicode escape preprocessing enabled to match `javac`), aborting generation on any residual structural mismatch diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/internal/KubeConfigUtils.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/internal/KubeConfigUtils.java index a8f1c92ad1b..74f3faa3d87 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/internal/KubeConfigUtils.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/internal/KubeConfigUtils.java @@ -325,6 +325,9 @@ private static void mergeKubeConfigExecCredential(io.fabric8.kubernetes.client.C && Utils.isNotNullOrEmpty(ec.status.clientKeyData)) { config.setClientCertData(ec.status.clientCertificateData); config.setClientKeyData(ec.status.clientKeyData); + // Detect key algorithm from exec-provided material (same as static kubeconfig keys). + // Without this, clientKeyAlgo stays unset and CertUtils defaults to RSA, which fails for ECDSA. + config.setClientKeyAlgo(getKeyAlgorithm(null, config.getClientKeyData())); } else { logger.warn("No token or certificate returned"); } diff --git a/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/ConfigTest.java b/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/ConfigTest.java index 08b6e108977..fb10f796d49 100644 --- a/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/ConfigTest.java +++ b/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/ConfigTest.java @@ -131,7 +131,13 @@ class ConfigTest { .filePath(ConfigTest.class.getResource("/test-kubeconfig-exec-cert-auth-ec-invalid")); private static final String TEST_KUBECONFIG_EXEC_WIN_FILE_CERT_AUTH_EC_INVALID = Utils .filePath(ConfigTest.class.getResource("/test-kubeconfig-exec-win-cert-auth-ec-invalid")); + private static final String TEST_KUBECONFIG_EXEC_FILE_CERT_AUTH_EC = Utils + .filePath(ConfigTest.class.getResource("/test-kubeconfig-exec-cert-auth-ec")); + private static final String TEST_KUBECONFIG_EXEC_WIN_FILE_CERT_AUTH_EC = Utils + .filePath(ConfigTest.class.getResource("/test-kubeconfig-exec-win-cert-auth-ec")); private static final String TEST_CERT_GENERATOR_FILE = Utils.filePath(ConfigTest.class.getResource("/cert-generator")); + private static final String TEST_CERT_GENERATOR_EC_FILE = Utils + .filePath(ConfigTest.class.getResource("/cert-generator-ec")); private static final String TEST_KUBECONFIG_TLS_SERVER_NAME_FILE = Utils .filePath(ConfigTest.class.getResource("/test-kubeconfig-tls-server-name")); @@ -797,6 +803,27 @@ void autoConfigure_whenInvalidArgsProvided_thenDoNotLoadClientCertData() throws .hasFieldOrPropertyWithValue("clientKeyData", null); } + @Test + @DisplayName("when exec returns ECDSA client key, then identify client key algorithm as EC (#8022)") + void autoConfigure_whenExecReturnsEcdsaClientKey_thenSetClientKeyAlgoToEc() throws Exception { + // Given + if (FileSystem.getCurrent() == FileSystem.WINDOWS) { + System.setProperty("kubeconfig", TEST_KUBECONFIG_EXEC_WIN_FILE_CERT_AUTH_EC); + } else { + Files.setPosixFilePermissions(Paths.get(TEST_CERT_GENERATOR_EC_FILE), PosixFilePermissions.fromString("rwxrwxr-x")); + System.setProperty("kubeconfig", TEST_KUBECONFIG_EXEC_FILE_CERT_AUTH_EC); + } + // When + Config config = Config.autoConfigure(null); + // Then + assertThat(config) + .isNotNull() + .hasFieldOrPropertyWithValue("clientKeyAlgo", "EC") + .extracting(Config::getClientKeyData, InstanceOfAssertFactories.STRING) + .contains("BEGIN EC PRIVATE KEY"); + assertThat(config.getClientCertData()).contains("BEGIN CERTIFICATE"); + } + @Test void should_accept_client_authentication_commands_args_with_spaces() throws Exception { // Given diff --git a/kubernetes-client-api/src/test/resources/cert-generator-ec b/kubernetes-client-api/src/test/resources/cert-generator-ec new file mode 100755 index 00000000000..1980e62a7f4 --- /dev/null +++ b/kubernetes-client-api/src/test/resources/cert-generator-ec @@ -0,0 +1,14 @@ +#!/bin/sh +# Fixed sample ECDSA client certificate/key for unit tests. +# clientKeyData is a SEC1 EC private key PEM so algorithm detection yields "EC". +cat <<'EOF' +{ + "kind": "ExecCredential", + "apiVersion": "client.authentication.k8s.io/v1", + "spec": {}, + "status": { + "clientCertificateData": "-----BEGIN CERTIFICATE-----\nMIICUDCCATigAwIBAgIRAP5Y0VEn43LepIIhGBgZCm4wDQYJKoZIhvcNAQELBQAw\nFTETMBEGA1UEAxMKbWluaWt1YmVDQTAeFw0yMTExMDEwOTIyMThaFw0yMjExMDEw\nOTIyMThaMCUxEzARBgNVBAoTCmRldmVsb3BlcnMxDjAMBgNVBAMTBXV6YWlyMFkw\nEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkAMD3WlBNsN0tiNCZSC8gK/5O4rxmkgj\n953kvWgv11/AorgxDFZpr2+azYJAN6ZNt8hXoBlsrBtDDzKnfPJszqNWMFQwDgYD\nVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAw\nHwYDVR0jBBgwFoAU4qycWT5q7c/4OgWIHdKILxBhjS8wDQYJKoZIhvcNAQELBQAD\nggEBANTomS4AL4z0cxd0KZQhts2ADlbgJQUmI7TU8+I70qoQhdvFC3xFuQM3KHVQ\nkt4amj+ahz74ZMGB8FH7SHvdLdb9yIsZQJttw6MVyrC3/GavRBQM3KwAB0CVMEQf\nRL/PYTXJSmPiicQH9BLUW9RzJwP4gnIlEj2yjLnAPyilH4LSiBvjE93nPBHdY0SF\n/v1/Jy3pnpcih05eHahFtMrE3FH1YaVCL2ncUGif//x9TNhR7WX2w0+X+fqY4w0Q\n4xmJCGW/DvYqBGqavdEYU1FMXvlhqQkRueYOGbU8P2VlhU5qd0Wdfdg8FokkdozL\nRUgkCbcwxOTea1Lit2iuDGzJezI=\n-----END CERTIFICATE-----\n", + "clientKeyData": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIICkeBChzRrBMifb5r9rzdq4FphWRzPz1eDmRTnqBugJoAoGCCqGSM49\nAwEHoUQDQgAEkAMD3WlBNsN0tiNCZSC8gK/5O4rxmkgj953kvWgv11/AorgxDFZp\nr2+azYJAN6ZNt8hXoBlsrBtDDzKnfPJszg==\n-----END EC PRIVATE KEY-----\n" + } +} +EOF diff --git a/kubernetes-client-api/src/test/resources/cert-generator-ec-win.bat b/kubernetes-client-api/src/test/resources/cert-generator-ec-win.bat new file mode 100644 index 00000000000..12e7ea5df41 --- /dev/null +++ b/kubernetes-client-api/src/test/resources/cert-generator-ec-win.bat @@ -0,0 +1,11 @@ +@REM Fixed sample ECDSA client certificate/key for unit tests. +@echo off +echo { +echo "kind": "ExecCredential", +echo "apiVersion": "client.authentication.k8s.io/v1", +echo "spec": {}, +echo "status": { +echo "clientCertificateData": "-----BEGIN CERTIFICATE-----\nMIICUDCCATigAwIBAgIRAP5Y0VEn43LepIIhGBgZCm4wDQYJKoZIhvcNAQELBQAw\nFTETMBEGA1UEAxMKbWluaWt1YmVDQTAeFw0yMTExMDEwOTIyMThaFw0yMjExMDEw\nOTIyMThaMCUxEzARBgNVBAoTCmRldmVsb3BlcnMxDjAMBgNVBAMTBXV6YWlyMFkw\nEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkAMD3WlBNsN0tiNCZSC8gK/5O4rxmkgj\n953kvWgv11/AorgxDFZpr2+azYJAN6ZNt8hXoBlsrBtDDzKnfPJszqNWMFQwDgYD\nVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAw\nHwYDVR0jBBgwFoAU4qycWT5q7c/4OgWIHdKILxBhjS8wDQYJKoZIhvcNAQELBQAD\nggEBANTomS4AL4z0cxd0KZQhts2ADlbgJQUmI7TU8+I70qoQhdvFC3xFuQM3KHVQ\nkt4amj+ahz74ZMGB8FH7SHvdLdb9yIsZQJttw6MVyrC3/GavRBQM3KwAB0CVMEQf\nRL/PYTXJSmPiicQH9BLUW9RzJwP4gnIlEj2yjLnAPyilH4LSiBvjE93nPBHdY0SF\n/v1/Jy3pnpcih05eHahFtMrE3FH1YaVCL2ncUGif//x9TNhR7WX2w0+X+fqY4w0Q\n4xmJCGW/DvYqBGqavdEYU1FMXvlhqQkRueYOGbU8P2VlhU5qd0Wdfdg8FokkdozL\nRUgkCbcwxOTea1Lit2iuDGzJezI=\n-----END CERTIFICATE-----\n", +echo "clientKeyData": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIICkeBChzRrBMifb5r9rzdq4FphWRzPz1eDmRTnqBugJoAoGCCqGSM49\nAwEHoUQDQgAEkAMD3WlBNsN0tiNCZSC8gK/5O4rxmkgj953kvWgv11/AorgxDFZp\nr2+azYJAN6ZNt8hXoBlsrBtDDzKnfPJszg==\n-----END EC PRIVATE KEY-----\n" +echo } +echo } diff --git a/kubernetes-client-api/src/test/resources/test-kubeconfig-exec-cert-auth-ec b/kubernetes-client-api/src/test/resources/test-kubeconfig-exec-cert-auth-ec new file mode 100644 index 00000000000..e39277d596d --- /dev/null +++ b/kubernetes-client-api/src/test/resources/test-kubeconfig-exec-cert-auth-ec @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Config +clusters: +- cluster: + server: https://wherever + name: test +contexts: +- context: + cluster: test + user: test + name: test +current-context: test +users: +- name: test + user: + exec: + apiVersion: client.authentication.k8s.io/v1 + args: [] + command: ./cert-generator-ec + env: [] diff --git a/kubernetes-client-api/src/test/resources/test-kubeconfig-exec-win-cert-auth-ec b/kubernetes-client-api/src/test/resources/test-kubeconfig-exec-win-cert-auth-ec new file mode 100644 index 00000000000..e57b3708d3a --- /dev/null +++ b/kubernetes-client-api/src/test/resources/test-kubeconfig-exec-win-cert-auth-ec @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Config +clusters: +- cluster: + server: https://wherever + name: test +contexts: +- context: + cluster: test + user: test + name: test +current-context: test +users: +- name: test + user: + exec: + apiVersion: client.authentication.k8s.io/v1 + args: [] + command: ".\cert-generator-ec-win.bat" + env: []