From d8da94337534b63d31667635f6c2b9d082568f8b Mon Sep 17 00:00:00 2001
From: cb <chriswbell@gmail.com>
Date: Fri, 28 Apr 2023 16:50:16 -0700
Subject: [PATCH] Support TLS Server Name overrides in kubeconfig (#1282)

The client should support tls-server-name just like client-go and kubectl.  See https://github.com/kubernetes/kubernetes/pull/88769
---
 .../KubeConfigModels/ClusterEndpoint.cs       |  6 +++++
 src/KubernetesClient/Kubernetes.ConfigInit.cs |  3 +++
 src/KubernetesClient/Kubernetes.cs            |  5 +++++
 ...ubernetesClientConfiguration.ConfigFile.cs |  1 +
 .../KubernetesClientConfiguration.cs          |  5 +++++
 .../KubernetesClientConfigurationTests.cs     | 11 ++++++++++
 .../assets/kubeconfig.tls-servername.yml      | 22 +++++++++++++++++++
 7 files changed, 53 insertions(+)
 create mode 100644 tests/KubernetesClient.Tests/assets/kubeconfig.tls-servername.yml

diff --git a/src/KubernetesClient.Models/KubeConfigModels/ClusterEndpoint.cs b/src/KubernetesClient.Models/KubeConfigModels/ClusterEndpoint.cs
index 4838d0083..cdd791b4a 100644
--- a/src/KubernetesClient.Models/KubeConfigModels/ClusterEndpoint.cs
+++ b/src/KubernetesClient.Models/KubeConfigModels/ClusterEndpoint.cs
@@ -25,6 +25,12 @@ public class ClusterEndpoint
         [YamlMember(Alias = "server")]
         public string Server { get; set; }
 
+        /// <summary>
+        /// Gets or sets a value to override the TLS server name.
+        /// </summary>
+        [YamlMember(Alias = "tls-server-name", ApplyNamingConventions = false)]
+        public string TlsServerName { get; set; }
+
         /// <summary>
         /// Gets or sets a value indicating whether to skip the validity check for the server's certificate.
         /// This will make your HTTPS connections insecure.
diff --git a/src/KubernetesClient/Kubernetes.ConfigInit.cs b/src/KubernetesClient/Kubernetes.ConfigInit.cs
index 7fc808b9f..ac81fbd93 100644
--- a/src/KubernetesClient/Kubernetes.ConfigInit.cs
+++ b/src/KubernetesClient/Kubernetes.ConfigInit.cs
@@ -26,6 +26,7 @@ public Kubernetes(KubernetesClientConfiguration config, params DelegatingHandler
             ValidateConfig(config);
             CaCerts = config.SslCaCerts;
             SkipTlsVerify = config.SkipTlsVerify;
+            TlsServerName = config.TlsServerName;
             CreateHttpClient(handlers, config);
             InitializeFromConfig(config);
             HttpClientTimeout = config.HttpClientTimeout;
@@ -115,6 +116,8 @@ private void InitializeFromConfig(KubernetesClientConfiguration config)
 
         private bool SkipTlsVerify { get; }
 
+        private string TlsServerName { get; }
+
         // NOTE: this method replicates the logic that the base ServiceClient uses except that it doesn't insert the RetryDelegatingHandler
         // and it does insert the WatcherDelegatingHandler. we don't want the RetryDelegatingHandler because it has a very broad definition
         // of what requests have failed. it considers everything outside 2xx to be failed, including 1xx (e.g. 101 Switching Protocols) and
diff --git a/src/KubernetesClient/Kubernetes.cs b/src/KubernetesClient/Kubernetes.cs
index bbbe70ee8..6b9972485 100644
--- a/src/KubernetesClient/Kubernetes.cs
+++ b/src/KubernetesClient/Kubernetes.cs
@@ -149,6 +149,11 @@ protected virtual async Task<HttpResponseMessage> SendRequestRaw(string requestC
                 await Credentials.ProcessHttpRequestAsync(httpRequest, cancellationToken).ConfigureAwait(false);
             }
 
+            if (!string.IsNullOrWhiteSpace(TlsServerName))
+            {
+                httpRequest.Headers.Host = TlsServerName;
+            }
+
             // Send Request
             cancellationToken.ThrowIfCancellationRequested();
             var httpResponse = await HttpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
diff --git a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs
index a1cf39740..9d439ad78 100644
--- a/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs
+++ b/src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs
@@ -267,6 +267,7 @@ private void SetClusterDetails(K8SConfiguration k8SConfig, Context activeContext
 
             Host = clusterDetails.ClusterEndpoint.Server;
             SkipTlsVerify = clusterDetails.ClusterEndpoint.SkipTlsVerify;
+            TlsServerName = clusterDetails.ClusterEndpoint.TlsServerName;
 
             if (!Uri.TryCreate(Host, UriKind.Absolute, out var uri))
             {
diff --git a/src/KubernetesClient/KubernetesClientConfiguration.cs b/src/KubernetesClient/KubernetesClientConfiguration.cs
index 61e0d061b..ac4a66719 100644
--- a/src/KubernetesClient/KubernetesClientConfiguration.cs
+++ b/src/KubernetesClient/KubernetesClientConfiguration.cs
@@ -56,6 +56,11 @@ public partial class KubernetesClientConfiguration
         /// </summary>
         public bool SkipTlsVerify { get; set; }
 
+        /// <summary>
+        ///     Option to override the TLS server name
+        /// </summary>
+        public string TlsServerName { get; set; }
+
         /// <summary>
         ///     Gets or sets the HTTP user agent.
         /// </summary>
diff --git a/tests/KubernetesClient.Tests/KubernetesClientConfigurationTests.cs b/tests/KubernetesClient.Tests/KubernetesClientConfigurationTests.cs
index 976844836..8f491a8ed 100644
--- a/tests/KubernetesClient.Tests/KubernetesClientConfigurationTests.cs
+++ b/tests/KubernetesClient.Tests/KubernetesClientConfigurationTests.cs
@@ -341,6 +341,17 @@ public void SmartSkipTlsVerify()
             Assert.Equal("http://horse.org", cfg.Host);
         }
 
+        /// <summary>
+        ///     Make sure that TlsServerName is present
+        /// </summary>
+        [Fact]
+        public void TlsServerName()
+        {
+            var fi = new FileInfo("assets/kubeconfig.tls-servername.yml");
+            var cfg = KubernetesClientConfiguration.BuildConfigFromConfigFile(fi);
+            Assert.Equal("pony", cfg.TlsServerName);
+        }
+
         /// <summary>
         ///     Checks config could work well when current-context is not set but masterUrl is set. #issue 24
         /// </summary>
diff --git a/tests/KubernetesClient.Tests/assets/kubeconfig.tls-servername.yml b/tests/KubernetesClient.Tests/assets/kubeconfig.tls-servername.yml
new file mode 100644
index 000000000..bf5ea1d3c
--- /dev/null
+++ b/tests/KubernetesClient.Tests/assets/kubeconfig.tls-servername.yml
@@ -0,0 +1,22 @@
+# Sample file based on https://kubernetes.io/docs/tasks/access-application-cluster/authenticate-across-clusters-kubeconfig/
+# WARNING: File includes minor fixes
+---
+current-context: federal-context
+apiVersion: v1
+clusters:
+- cluster:
+    server: https://horse.org:443
+    tls-server-name: pony
+  name: horse-cluster
+contexts:
+- context:
+    cluster: horse-cluster
+    namespace: chisel-ns
+    user: green-user
+  name: federal-context
+kind: Config
+users:
+- name: green-user
+  user:
+    password: secret
+    username: admin