From f1e2e39b09c3fad3d888731214b1765e1befe375 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 17 Nov 2025 22:45:48 +0000
Subject: [PATCH 1/3] Initial plan
From 99bc5fac0b2797b0a8f2f6155dd5eab1dc0e26d4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 17 Nov 2025 22:55:54 +0000
Subject: [PATCH 2/3] Add support for tls-server-name field in kubeconfig
Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com>
---
.../kubernetes/client/openapi/ApiClient.java | 38 ++++++++++++++++++-
.../kubernetes/client/util/ClientBuilder.java | 19 ++++++++++
.../io/kubernetes/client/util/KubeConfig.java | 4 ++
3 files changed, 60 insertions(+), 1 deletion(-)
diff --git a/kubernetes/src/main/java/io/kubernetes/client/openapi/ApiClient.java b/kubernetes/src/main/java/io/kubernetes/client/openapi/ApiClient.java
index d5d3fd68c2..a64a91072a 100644
--- a/kubernetes/src/main/java/io/kubernetes/client/openapi/ApiClient.java
+++ b/kubernetes/src/main/java/io/kubernetes/client/openapi/ApiClient.java
@@ -85,6 +85,7 @@ public class ApiClient {
private InputStream sslCaCert;
private boolean verifyingSsl;
private KeyManager[] keyManagers;
+ private String tlsServerName;
private OkHttpClient httpClient;
private JSON json;
@@ -301,6 +302,29 @@ public ApiClient setKeyManagers(KeyManager[] managers) {
return this;
}
+ /**
+ * Get TLS server name for SNI (Server Name Indication).
+ *
+ * @return The TLS server name
+ */
+ public String getTlsServerName() {
+ return tlsServerName;
+ }
+
+ /**
+ * Set TLS server name for SNI (Server Name Indication).
+ * This is used to verify the server certificate against a specific hostname
+ * instead of the hostname in the URL.
+ *
+ * @param tlsServerName The TLS server name to use for certificate verification
+ * @return ApiClient
+ */
+ public ApiClient setTlsServerName(String tlsServerName) {
+ this.tlsServerName = tlsServerName;
+ applySslSettings();
+ return this;
+ }
+
/**
*
Getter for the field dateFormat.
*
@@ -1539,7 +1563,19 @@ public boolean verify(String hostname, SSLSession session) {
trustManagerFactory.init(caKeyStore);
}
trustManagers = trustManagerFactory.getTrustManagers();
- hostnameVerifier = OkHostnameVerifier.INSTANCE;
+ // If tlsServerName is set, use a custom hostname verifier that checks against
+ // the specified server name instead of the actual hostname in the URL
+ if (tlsServerName != null && !tlsServerName.isEmpty()) {
+ hostnameVerifier = new HostnameVerifier() {
+ @Override
+ public boolean verify(String hostname, SSLSession session) {
+ // Verify the certificate against tlsServerName instead of the actual hostname
+ return OkHostnameVerifier.INSTANCE.verify(tlsServerName, session);
+ }
+ };
+ } else {
+ hostnameVerifier = OkHostnameVerifier.INSTANCE;
+ }
}
SSLContext sslContext = SSLContext.getInstance("TLS");
diff --git a/util/src/main/java/io/kubernetes/client/util/ClientBuilder.java b/util/src/main/java/io/kubernetes/client/util/ClientBuilder.java
index 28c737a662..ae0bfb4011 100644
--- a/util/src/main/java/io/kubernetes/client/util/ClientBuilder.java
+++ b/util/src/main/java/io/kubernetes/client/util/ClientBuilder.java
@@ -64,6 +64,7 @@ public class ClientBuilder {
private boolean verifyingSsl = true;
private Authentication authentication;
private String keyStorePassphrase;
+ private String tlsServerName;
// defaulting client protocols to HTTP1.1 and HTTP 2
private List protocols = Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1);
// default to unlimited read timeout
@@ -312,6 +313,11 @@ public static ClientBuilder kubeconfig(KubeConfig config, Duration tokenRefreshP
}
builder.setVerifyingSsl(config.verifySSL());
+ String tlsServerName = config.getTlsServerName();
+ if (tlsServerName != null && !tlsServerName.isEmpty()) {
+ builder.setTlsServerName(tlsServerName);
+ }
+
builder.setBasePath(server);
builder.setAuthentication(new KubeconfigAuthentication(config, tokenRefreshPeriod));
return builder;
@@ -436,6 +442,15 @@ public ClientBuilder setKeyStorePassphrase(String keyStorePassphrase) {
return this;
}
+ public String getTlsServerName() {
+ return tlsServerName;
+ }
+
+ public ClientBuilder setTlsServerName(String tlsServerName) {
+ this.tlsServerName = tlsServerName;
+ return this;
+ }
+
public ApiClient getApiClient() {
return this.apiClient;
}
@@ -466,6 +481,10 @@ public ApiClient build() {
client.setVerifyingSsl(verifyingSsl);
+ if (tlsServerName != null && !tlsServerName.isEmpty()) {
+ client.setTlsServerName(tlsServerName);
+ }
+
if (authentication != null) {
if (StringUtils.isNotEmpty(keyStorePassphrase)) {
if (authentication instanceof KubeconfigAuthentication) {
diff --git a/util/src/main/java/io/kubernetes/client/util/KubeConfig.java b/util/src/main/java/io/kubernetes/client/util/KubeConfig.java
index ba4fb2a995..18f64f251b 100644
--- a/util/src/main/java/io/kubernetes/client/util/KubeConfig.java
+++ b/util/src/main/java/io/kubernetes/client/util/KubeConfig.java
@@ -176,6 +176,10 @@ public String getCertificateAuthorityFile() {
return getData(currentCluster, "certificate-authority");
}
+ public String getTlsServerName() {
+ return getData(currentCluster, "tls-server-name");
+ }
+
public String getClientCertificateFile() {
return getData(currentUser, "client-certificate");
}
From 2f9f511ee80471a36471991e6d7873226ea49961 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 17 Nov 2025 23:01:58 +0000
Subject: [PATCH 3/3] Add unit tests for tls-server-name functionality
Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com>
---
.../client/util/ClientBuilderTest.java | 20 ++++++++++++
.../client/util/KubeConfigTest.java | 31 +++++++++++++++++++
.../test/resources/kubeconfig-tls-server-name | 17 ++++++++++
3 files changed, 68 insertions(+)
create mode 100644 util/src/test/resources/kubeconfig-tls-server-name
diff --git a/util/src/test/java/io/kubernetes/client/util/ClientBuilderTest.java b/util/src/test/java/io/kubernetes/client/util/ClientBuilderTest.java
index cfc5f054d9..bbecb6068e 100644
--- a/util/src/test/java/io/kubernetes/client/util/ClientBuilderTest.java
+++ b/util/src/test/java/io/kubernetes/client/util/ClientBuilderTest.java
@@ -48,6 +48,8 @@ class ClientBuilderTest {
Resources.getResource("kubeconfig-https").getPath();
private static final String KUBECONFIG_HTTPS_X509_FILE_PATH =
Resources.getResource("kubeconfig-https-x509").getPath();
+ private static final String KUBECONFIG_TLS_SERVER_NAME_FILE_PATH =
+ Resources.getResource("kubeconfig-tls-server-name").getPath();
private static final String SSL_CA_CERT_PATH =
new File(Resources.getResource("ca-cert.pem").getPath()).toString();
private static final String INVALID_SSL_CA_CERT_PATH =
@@ -321,4 +323,22 @@ void detectsServerNotSet() {
ClientBuilder.kubeconfig(kubeConfigWithoutServer);
}).hasMessage("No server in kubeconfig").isInstanceOf(IllegalArgumentException.class);
}
+
+ @Test
+ void tlsServerNameSetFromKubeConfig() throws IOException {
+ ApiClient client =
+ ClientBuilder.kubeconfig(
+ KubeConfig.loadKubeConfig(new FileReader(KUBECONFIG_TLS_SERVER_NAME_FILE_PATH)))
+ .build();
+ assertThat(client.getTlsServerName()).isEqualTo("my-cluster.example.com");
+ }
+
+ @Test
+ void tlsServerNameNotSetWhenNotInKubeConfig() throws IOException {
+ ApiClient client =
+ ClientBuilder.kubeconfig(
+ KubeConfig.loadKubeConfig(new FileReader(KUBECONFIG_HTTPS_FILE_PATH)))
+ .build();
+ assertThat(client.getTlsServerName()).isNull();
+ }
}
diff --git a/util/src/test/java/io/kubernetes/client/util/KubeConfigTest.java b/util/src/test/java/io/kubernetes/client/util/KubeConfigTest.java
index d855283de0..6ac8ca57d1 100644
--- a/util/src/test/java/io/kubernetes/client/util/KubeConfigTest.java
+++ b/util/src/test/java/io/kubernetes/client/util/KubeConfigTest.java
@@ -425,4 +425,35 @@ void execCredentialsCertificate() {
assertThat(kc.getCredentials()).containsEntry(KubeConfig.CRED_CLIENT_KEY_DATA_KEY, "key");
assertThat(kc.getCredentials().get(KubeConfig.CRED_TOKEN_KEY)).isNull();
}
+
+ @Test
+ void tlsServerName() {
+ String configWithTlsServerName =
+ "apiVersion: v1\n"
+ + "clusters:\n"
+ + "- cluster:\n"
+ + " server: https://192.168.1.1:6443\n"
+ + " tls-server-name: my-cluster.example.com\n"
+ + " certificate-authority-data: dGVzdAo=\n"
+ + " name: test-cluster\n"
+ + "users:\n"
+ + "- user:\n"
+ + " token: test-token\n"
+ + " name: test-user\n"
+ + "contexts:\n"
+ + "- context:\n"
+ + " cluster: test-cluster\n"
+ + " user: test-user\n"
+ + " name: test-context\n"
+ + "current-context: test-context\n";
+
+ KubeConfig config = KubeConfig.loadKubeConfig(new StringReader(configWithTlsServerName));
+ assertThat("my-cluster.example.com").isEqualTo(config.getTlsServerName());
+ }
+
+ @Test
+ void tlsServerNameNotPresent() {
+ KubeConfig config = KubeConfig.loadKubeConfig(new StringReader(KUBECONFIG_TOKEN));
+ assertThat(config.getTlsServerName()).isNull();
+ }
}
diff --git a/util/src/test/resources/kubeconfig-tls-server-name b/util/src/test/resources/kubeconfig-tls-server-name
new file mode 100644
index 0000000000..41493a063f
--- /dev/null
+++ b/util/src/test/resources/kubeconfig-tls-server-name
@@ -0,0 +1,17 @@
+apiVersion: v1
+clusters:
+ - cluster:
+ server: https://192.168.1.1:6443
+ tls-server-name: my-cluster.example.com
+ certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkekNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUzTXpNMU1qYzFPVGN3SGhjTk1qUXhNREkzTVRnek1ETTNXaGNOTXpReE1ESTFNVGd6TURNMwpXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUzTXpNMU1qYzFPVGN3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFUMmY5dzdYZEJLRktDN2ptQUxYaDFlNVgyWjNYL0h5K2JYR0MxV1VoeHUKS3BZUUJ5Q0oxcU5RUjhQWHhOWEdvd3BJUFp0ekU0T2NQdjFRUlJyd0hvNkZvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVXdYam5rdWRSek55T1N2VmY2Mm1PCnFqc2FxREF3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUlnRGRYelNuYTRnS2x6akJ2MllLVDJqM0VlSzY1WndPa2cKNGMxaG1nK29CVHdDSVFEdTBGR2NjRVE3OXE2U0pGTkF1bUhsd0JaY3lmeE5sOWhDb3hSRUdWVy93Zz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
+ name: test-cluster
+users:
+ - user:
+ token: test-token
+ name: test-user
+contexts:
+ - context:
+ cluster: test-cluster
+ user: test-user
+ name: test-context
+current-context: test-context