diff --git a/kubernetes_asyncio/client/configuration.py b/kubernetes_asyncio/client/configuration.py index 1b403b0bf..81872a5cc 100644 --- a/kubernetes_asyncio/client/configuration.py +++ b/kubernetes_asyncio/client/configuration.py @@ -189,6 +189,10 @@ def __init__(self, host=None, self.assert_hostname = None """Set this to True/False to enable/disable SSL hostname verification. """ + self.tls_server_name = None + """SSL/TLS Server Name Indication (SNI) + Set this to the SNI value expected by Kubernetes API. + """ self.connection_pool_maxsize = 100 """This value is passed to the aiohttp to limit simultaneous connections. diff --git a/kubernetes_asyncio/client/rest.py b/kubernetes_asyncio/client/rest.py index 19ac9b849..0ec0505e4 100644 --- a/kubernetes_asyncio/client/rest.py +++ b/kubernetes_asyncio/client/rest.py @@ -56,6 +56,8 @@ def __init__(self, configuration, pools_size=4, maxsize=None): configuration.cert_file, keyfile=configuration.key_file ) + self.server_hostname = configuration.tls_server_name + if not configuration.verify_ssl: ssl_context.check_hostname = False ssl_context.verify_mode = ssl.CERT_NONE @@ -135,6 +137,9 @@ async def request(self, method, url, query_params=None, headers=None, if query_params: args["url"] += '?' + urlencode(query_params) + if self.server_hostname: + args["server_hostname"] = self.server_hostname + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: if ( diff --git a/kubernetes_asyncio/config/kube_config.py b/kubernetes_asyncio/config/kube_config.py index 2cb32142e..3ea1452ce 100644 --- a/kubernetes_asyncio/config/kube_config.py +++ b/kubernetes_asyncio/config/kube_config.py @@ -393,6 +393,8 @@ def _load_cluster_info(self): temp_file_path=self._temp_file_path).as_file() if 'insecure-skip-tls-verify' in self._cluster: self.verify_ssl = not self._cluster['insecure-skip-tls-verify'] + if 'tls-server-name' in self._cluster: + self.tls_server_name = self._cluster['tls-server-name'] def _set_config(self, client_configuration): @@ -400,7 +402,7 @@ def _set_config(self, client_configuration): client_configuration.api_key['BearerToken'] = self.token # copy these keys directly from self to configuration object - keys = ['host', 'ssl_ca_cert', 'cert_file', 'key_file', 'verify_ssl'] + keys = ['host', 'ssl_ca_cert', 'cert_file', 'key_file', 'verify_ssl', 'tls_server_name'] for key in keys: if key in self.__dict__: setattr(client_configuration, key, getattr(self, key)) diff --git a/kubernetes_asyncio/config/kube_config_test.py b/kubernetes_asyncio/config/kube_config_test.py index 9c489fc2b..0fca86945 100644 --- a/kubernetes_asyncio/config/kube_config_test.py +++ b/kubernetes_asyncio/config/kube_config_test.py @@ -72,6 +72,7 @@ def _raise_exception(st): TEST_CLIENT_KEY_BASE64 = _base64(TEST_CLIENT_KEY) TEST_CLIENT_CERT = "client-cert" TEST_CLIENT_CERT_BASE64 = _base64(TEST_CLIENT_CERT) +TEST_TLS_SERVER_NAME = "kubernetes.io" TEST_OIDC_TOKEN = "test-oidc-token" TEST_OIDC_INFO = "{\"name\": \"test\"}" @@ -443,6 +444,13 @@ class TestKubeConfigLoader(BaseTestCase): "user": "exec_cred_user_certificate" } }, + { + "name": "tls-server-name", + "context": { + "cluster": "tls-server-name", + "user": "ssl" + } + }, ], "clusters": [ { @@ -488,6 +496,16 @@ class TestKubeConfigLoader(BaseTestCase): "insecure-skip-tls-verify": False, } }, + { + "name": "tls-server-name", + "cluster": { + "server": TEST_SSL_HOST, + "certificate-authority-data": + TEST_CERTIFICATE_AUTH_BASE64, + "insecure-skip-tls-verify": False, + "tls-server-name": TEST_TLS_SERVER_NAME, + } + }, ], "users": [ { @@ -863,6 +881,22 @@ async def test_ssl_verification(self): active_context="ssl_verification").load_and_set(actual) self.assertEqual(expected, actual) + def test_tls_server_name(self): + expected = FakeConfig( + host=TEST_SSL_HOST, + token=BEARER_TOKEN_FORMAT % TEST_DATA_BASE64, + cert_file=self._create_temp_file(TEST_CLIENT_CERT), + key_file=self._create_temp_file(TEST_CLIENT_KEY), + ssl_ca_cert=self._create_temp_file(TEST_CERTIFICATE_AUTH), + verify_ssl=True, + tls_server_name=TEST_TLS_SERVER_NAME + ) + actual = FakeConfig() + KubeConfigLoader( + config_dict=self.TEST_KUBE_CONFIG, + active_context="tls-server-name").load_and_set(actual) + self.assertEqual(expected, actual) + def test_list_contexts(self): loader = KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, diff --git a/scripts/rest_client_server_hostname_patch.diff b/scripts/rest_client_server_hostname_patch.diff new file mode 100644 index 000000000..b034c20ab --- /dev/null +++ b/scripts/rest_client_server_hostname_patch.diff @@ -0,0 +1,23 @@ +diff --git a/kubernetes_asyncio/client/rest.py b/kubernetes_asyncio/client/rest.py +index c5be7990..9e0daab4 100644 +--- a/kubernetes_asyncio/client/rest.py ++++ b/kubernetes_asyncio/client/rest.py +@@ -56,6 +56,8 @@ class RESTClientObject(object): + configuration.cert_file, keyfile=configuration.key_file + ) + ++ self.server_hostname = configuration.tls_server_name ++ + if not configuration.verify_ssl: + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE +@@ -135,6 +137,9 @@ class RESTClientObject(object): + if query_params: + args["url"] += '?' + urlencode(query_params) + ++ if self.server_hostname: ++ args["server_hostname"] = self.server_hostname ++ + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + if re.search('json', headers['Content-Type'], re.IGNORECASE): diff --git a/scripts/update-client.sh b/scripts/update-client.sh index 5c48062d6..28a92add9 100755 --- a/scripts/update-client.sh +++ b/scripts/update-client.sh @@ -68,6 +68,8 @@ echo ">>> fix generated rest client for patching with strategic merge..." patch "${CLIENT_ROOT}/client/rest.py" "${SCRIPT_ROOT}/rest_client_patch.diff" echo ">>> fix generated rest client by increasing aiohttp read buffer to 2MiB..." patch "${CLIENT_ROOT}/client/rest.py" "${SCRIPT_ROOT}/rest_client_patch_read_bufsize.diff" +echo ">>> fix generated rest client to support customer server hostname TLS verification..." +patch "${CLIENT_ROOT}/client/rest.py" "${SCRIPT_ROOT}/rest_client_server_hostname_patch.diff" echo ">>> Remove invalid tests (workaround https://github.com/OpenAPITools/openapi-generator/issues/5377)"