Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

/**
* <p>Getter for the field <code>dateFormat</code>.</p>
*
Expand Down Expand Up @@ -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");
Expand Down
19 changes: 19 additions & 0 deletions util/src/main/java/io/kubernetes/client/util/ClientBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Protocol> protocols = Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1);
// default to unlimited read timeout
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions util/src/main/java/io/kubernetes/client/util/KubeConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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();
}
}
31 changes: 31 additions & 0 deletions util/src/test/java/io/kubernetes/client/util/KubeConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
17 changes: 17 additions & 0 deletions util/src/test/resources/kubeconfig-tls-server-name
Original file line number Diff line number Diff line change
@@ -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
Loading