From c3608a4c2b1f046f6ffd8effeca4cafeead70d5b Mon Sep 17 00:00:00 2001 From: Jmacek <8560946+Jmacek@users.noreply.github.com> Date: Thu, 21 May 2026 10:06:13 -0700 Subject: [PATCH 1/2] config: write api_key['BearerToken'] so v36+ SDK auth works v36 rewrote Configuration.auth_settings() to look up the bearer-token credential under api_key['BearerToken'], matching the OpenAPI security scheme name. The in-cluster and kubeconfig loaders were not updated - they still write api_key['authorization'], the v35 lookup key. The same gap exists in kubernetes_asyncio/config/incluster_config.py. As a result, on v36 every call to load_incluster_config() (and load_kube_config() with a static token, including the async equivalents) produces a Configuration whose auth_settings() yields no bearer credential, so outgoing API requests are sent without an Authorization header and the apiserver treats them as system:anonymous. Write the token under both 'authorization' (v35) and 'BearerToken' (v36+) in all three affected loaders so requests carry the expected header. The old key is preserved as a backward-compatibility hedge for any third-party code introspecting api_key['authorization'] directly. Add a regression test in incluster_config_test that drives a real ApiClient.update_params_for_auth() against a freshly-loaded Configuration and asserts the resulting headers contain an Authorization entry - the end-to-end invariant that v36 quietly broke. --- kubernetes/base/config/incluster_config.py | 1 + kubernetes/base/config/incluster_config_test.py | 17 +++++++++++++++++ kubernetes/base/config/kube_config.py | 1 + kubernetes/base/config/kube_config_test.py | 13 +++++++++---- kubernetes_asyncio/config/incluster_config.py | 1 + 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/kubernetes/base/config/incluster_config.py b/kubernetes/base/config/incluster_config.py index fca6d6a1de..9d61fa3157 100644 --- a/kubernetes/base/config/incluster_config.py +++ b/kubernetes/base/config/incluster_config.py @@ -89,6 +89,7 @@ def _set_config(self, client_configuration): client_configuration.ssl_ca_cert = self.ssl_ca_cert if self.token is not None: client_configuration.api_key['authorization'] = self.token + client_configuration.api_key['BearerToken'] = self.token if not self._try_refresh_token: return diff --git a/kubernetes/base/config/incluster_config_test.py b/kubernetes/base/config/incluster_config_test.py index 856752be1e..c44ce5df04 100644 --- a/kubernetes/base/config/incluster_config_test.py +++ b/kubernetes/base/config/incluster_config_test.py @@ -108,6 +108,23 @@ def test_refresh_token(self): self.assertEqual('bearer ' + _TEST_NEW_TOKEN, loader.token) self.assertGreater(loader.token_expires_at, old_token_expires_at) + def test_load_incluster_sets_request_authorization_header(self): + from kubernetes.client import ApiClient + cert_filename = self._create_file_with_temp_content(_TEST_CERT) + loader = self.get_test_loader(cert_filename=cert_filename) + config = Configuration() + loader.load_and_set(config) + + api_client = ApiClient(config) + headers = {} + api_client.update_params_for_auth(headers, [], ['BearerToken']) + + self.assertIn('authorization', headers) + self.assertTrue( + headers['authorization'].lower().startswith('bearer '), + "Expected a Bearer authorization header, got: %r" + % headers['authorization']) + def _should_fail_load(self, config_loader, reason): try: config_loader.load_and_set() diff --git a/kubernetes/base/config/kube_config.py b/kubernetes/base/config/kube_config.py index 44b275b3c4..76d34c23b6 100644 --- a/kubernetes/base/config/kube_config.py +++ b/kubernetes/base/config/kube_config.py @@ -528,6 +528,7 @@ def _load_cluster_info(self): def _set_config(self, client_configuration): if 'token' in self.__dict__: client_configuration.api_key['authorization'] = self.token + client_configuration.api_key['BearerToken'] = self.token def _refresh_api_key(client_configuration): if ('expiry' in self.__dict__ and _is_expired(self.expiry)): diff --git a/kubernetes/base/config/kube_config_test.py b/kubernetes/base/config/kube_config_test.py index f860c685b5..0510a264ee 100644 --- a/kubernetes/base/config/kube_config_test.py +++ b/kubernetes/base/config/kube_config_test.py @@ -370,6 +370,7 @@ def __init__(self, token=None, **kwargs): self.refresh_api_key_hook = None if token: self.api_key['authorization'] = token + self.api_key['BearerToken'] = token self.__dict__.update(kwargs) @@ -1317,7 +1318,8 @@ def test_user_exec_auth(self, mock): "token": token } expected = FakeConfig(host=TEST_HOST, api_key={ - "authorization": BEARER_TOKEN_FORMAT % token}) + "authorization": BEARER_TOKEN_FORMAT % token, + "BearerToken": BEARER_TOKEN_FORMAT % token}) actual = FakeConfig() KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, @@ -1395,7 +1397,8 @@ def test_user_cmd_path(self): return_value = A(token, parse_rfc3339(datetime.datetime.now())) CommandTokenSource.token = mock.Mock(return_value=return_value) expected = FakeConfig(api_key={ - "authorization": BEARER_TOKEN_FORMAT % token}) + "authorization": BEARER_TOKEN_FORMAT % token, + "BearerToken": BEARER_TOKEN_FORMAT % token}) actual = FakeConfig() KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, @@ -1408,7 +1411,8 @@ def test_user_cmd_path_empty(self): return_value = A(token, parse_rfc3339(datetime.datetime.now())) CommandTokenSource.token = mock.Mock(return_value=return_value) expected = FakeConfig(api_key={ - "authorization": BEARER_TOKEN_FORMAT % token}) + "authorization": BEARER_TOKEN_FORMAT % token, + "BearerToken": BEARER_TOKEN_FORMAT % token}) actual = FakeConfig() self.expect_exception(lambda: KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, @@ -1422,7 +1426,8 @@ def test_user_cmd_path_with_scope(self): return_value = A(token, parse_rfc3339(datetime.datetime.now())) CommandTokenSource.token = mock.Mock(return_value=return_value) expected = FakeConfig(api_key={ - "authorization": BEARER_TOKEN_FORMAT % token}) + "authorization": BEARER_TOKEN_FORMAT % token, + "BearerToken": BEARER_TOKEN_FORMAT % token}) actual = FakeConfig() self.expect_exception(lambda: KubeConfigLoader( config_dict=self.TEST_KUBE_CONFIG, diff --git a/kubernetes_asyncio/config/incluster_config.py b/kubernetes_asyncio/config/incluster_config.py index 4a14038ebe..09197c35e7 100644 --- a/kubernetes_asyncio/config/incluster_config.py +++ b/kubernetes_asyncio/config/incluster_config.py @@ -89,6 +89,7 @@ def _set_config(self, client_configuration): client_configuration.ssl_ca_cert = self.ssl_ca_cert if self.token is not None: client_configuration.api_key['authorization'] = self.token + client_configuration.api_key['BearerToken'] = self.token if not self._try_refresh_token: return From 7cc2c579516839d373065f1ec7d19dcc692360f6 Mon Sep 17 00:00:00 2001 From: Jmacek <8560946+Jmacek@users.noreply.github.com> Date: Thu, 21 May 2026 15:05:43 -0700 Subject: [PATCH 2/2] config: drop dual-write, replace 'authorization' with 'BearerToken' Address review feedback: the openapi-generator v6.6.0 upgrade declared this rename as a breaking change in the project CHANGELOG, so the loaders should follow the rename rather than preserve the v35 key for backward compatibility. Removes the api_key['authorization'] write in all three affected loaders (sync incluster, sync kube_config, async incluster) and updates the corresponding test assertions to read 'BearerToken'. --- kubernetes/base/config/incluster_config.py | 1 - .../base/config/incluster_config_test.py | 6 +++--- kubernetes/base/config/kube_config.py | 1 - kubernetes/base/config/kube_config_test.py | 19 +++++++------------ kubernetes_asyncio/config/incluster_config.py | 1 - .../config/incluster_config_test.py | 6 +++--- 6 files changed, 13 insertions(+), 21 deletions(-) diff --git a/kubernetes/base/config/incluster_config.py b/kubernetes/base/config/incluster_config.py index 9d61fa3157..62b52970fb 100644 --- a/kubernetes/base/config/incluster_config.py +++ b/kubernetes/base/config/incluster_config.py @@ -88,7 +88,6 @@ def _set_config(self, client_configuration): client_configuration.host = self.host client_configuration.ssl_ca_cert = self.ssl_ca_cert if self.token is not None: - client_configuration.api_key['authorization'] = self.token client_configuration.api_key['BearerToken'] = self.token if not self._try_refresh_token: return diff --git a/kubernetes/base/config/incluster_config_test.py b/kubernetes/base/config/incluster_config_test.py index c44ce5df04..14286d66ce 100644 --- a/kubernetes/base/config/incluster_config_test.py +++ b/kubernetes/base/config/incluster_config_test.py @@ -91,7 +91,7 @@ def test_refresh_token(self): loader.load_and_set(config) self.assertEqual('bearer ' + _TEST_TOKEN, - config.get_api_key_with_prefix('authorization')) + config.get_api_key_with_prefix('BearerToken')) self.assertEqual('bearer ' + _TEST_TOKEN, loader.token) self.assertIsNotNone(loader.token_expires_at) @@ -100,11 +100,11 @@ def test_refresh_token(self): loader._token_filename = self._create_file_with_temp_content( _TEST_NEW_TOKEN) self.assertEqual('bearer ' + _TEST_TOKEN, - config.get_api_key_with_prefix('authorization')) + config.get_api_key_with_prefix('BearerToken')) loader.token_expires_at = datetime.datetime.now() self.assertEqual('bearer ' + _TEST_NEW_TOKEN, - config.get_api_key_with_prefix('authorization')) + config.get_api_key_with_prefix('BearerToken')) self.assertEqual('bearer ' + _TEST_NEW_TOKEN, loader.token) self.assertGreater(loader.token_expires_at, old_token_expires_at) diff --git a/kubernetes/base/config/kube_config.py b/kubernetes/base/config/kube_config.py index 76d34c23b6..13f4ba226c 100644 --- a/kubernetes/base/config/kube_config.py +++ b/kubernetes/base/config/kube_config.py @@ -527,7 +527,6 @@ def _load_cluster_info(self): def _set_config(self, client_configuration): if 'token' in self.__dict__: - client_configuration.api_key['authorization'] = self.token client_configuration.api_key['BearerToken'] = self.token def _refresh_api_key(client_configuration): diff --git a/kubernetes/base/config/kube_config_test.py b/kubernetes/base/config/kube_config_test.py index 0510a264ee..0a594b1ef2 100644 --- a/kubernetes/base/config/kube_config_test.py +++ b/kubernetes/base/config/kube_config_test.py @@ -369,7 +369,6 @@ def __init__(self, token=None, **kwargs): # Provided by the OpenAPI-generated Configuration class self.refresh_api_key_hook = None if token: - self.api_key['authorization'] = token self.api_key['BearerToken'] = token self.__dict__.update(kwargs) @@ -906,7 +905,7 @@ def test_gcp_no_refresh(self): self.assertIsNotNone(fake_config.refresh_api_key_hook) self.assertEqual(TEST_HOST, fake_config.host) self.assertEqual(BEARER_TOKEN_FORMAT % TEST_DATA_BASE64, - fake_config.api_key['authorization']) + fake_config.api_key['BearerToken']) def test_load_gcp_token_no_refresh(self): loader = KubeConfigLoader( @@ -1284,14 +1283,14 @@ def test_new_client_from_config(self): config_file=config_file, context="simple_token") self.assertEqual(TEST_HOST, client.configuration.host) self.assertEqual(BEARER_TOKEN_FORMAT % TEST_DATA_BASE64, - client.configuration.api_key['authorization']) + client.configuration.api_key['BearerToken']) def test_new_client_from_config_dict(self): client = new_client_from_config_dict( config_dict=self.TEST_KUBE_CONFIG, context="simple_token") self.assertEqual(TEST_HOST, client.configuration.host) self.assertEqual(BEARER_TOKEN_FORMAT % TEST_DATA_BASE64, - client.configuration.api_key['authorization']) + client.configuration.api_key['BearerToken']) def test_no_users_section(self): expected = FakeConfig(host=TEST_HOST) @@ -1318,7 +1317,6 @@ def test_user_exec_auth(self, mock): "token": token } expected = FakeConfig(host=TEST_HOST, api_key={ - "authorization": BEARER_TOKEN_FORMAT % token, "BearerToken": BEARER_TOKEN_FORMAT % token}) actual = FakeConfig() KubeConfigLoader( @@ -1349,13 +1347,13 @@ def test_user_exec_auth_with_expiry(self, mock): active_context="exec_cred_user").load_and_set(fake_config) # The kube config should use the first token returned from the # exec provider. - self.assertEqual(fake_config.api_key["authorization"], + self.assertEqual(fake_config.api_key["BearerToken"], BEARER_TOKEN_FORMAT % expired_token) # Should now be populated with a method to refresh expired tokens. self.assertIsNotNone(fake_config.refresh_api_key_hook) # Refresh the token; the kube config should be updated. fake_config.refresh_api_key_hook(fake_config) - self.assertEqual(fake_config.api_key["authorization"], + self.assertEqual(fake_config.api_key["BearerToken"], BEARER_TOKEN_FORMAT % current_token) @mock.patch('kubernetes.config.kube_config.ExecProvider.run') @@ -1397,7 +1395,6 @@ def test_user_cmd_path(self): return_value = A(token, parse_rfc3339(datetime.datetime.now())) CommandTokenSource.token = mock.Mock(return_value=return_value) expected = FakeConfig(api_key={ - "authorization": BEARER_TOKEN_FORMAT % token, "BearerToken": BEARER_TOKEN_FORMAT % token}) actual = FakeConfig() KubeConfigLoader( @@ -1411,7 +1408,6 @@ def test_user_cmd_path_empty(self): return_value = A(token, parse_rfc3339(datetime.datetime.now())) CommandTokenSource.token = mock.Mock(return_value=return_value) expected = FakeConfig(api_key={ - "authorization": BEARER_TOKEN_FORMAT % token, "BearerToken": BEARER_TOKEN_FORMAT % token}) actual = FakeConfig() self.expect_exception(lambda: KubeConfigLoader( @@ -1426,7 +1422,6 @@ def test_user_cmd_path_with_scope(self): return_value = A(token, parse_rfc3339(datetime.datetime.now())) CommandTokenSource.token = mock.Mock(return_value=return_value) expected = FakeConfig(api_key={ - "authorization": BEARER_TOKEN_FORMAT % token, "BearerToken": BEARER_TOKEN_FORMAT % token}) actual = FakeConfig() self.expect_exception(lambda: KubeConfigLoader( @@ -1728,7 +1723,7 @@ def test_new_client_from_config(self): config_file=kubeconfigs, context="simple_token") self.assertEqual(TEST_HOST, client.configuration.host) self.assertEqual(BEARER_TOKEN_FORMAT % TEST_DATA_BASE64, - client.configuration.api_key['authorization']) + client.configuration.api_key['BearerToken']) def test_merge_with_context_in_different_file(self): kubeconfigs = self._create_multi_config(self.TEST_KUBE_CONFIG_SET2) @@ -1744,7 +1739,7 @@ def test_merge_with_context_in_different_file(self): self.assertEqual(active_context, expected_contexts[0]) self.assertEqual(TEST_HOST, client.configuration.host) self.assertEqual(BEARER_TOKEN_FORMAT % TEST_DATA_BASE64, - client.configuration.api_key['authorization']) + client.configuration.api_key['BearerToken']) def test_save_changes(self): kubeconfigs = self._create_multi_config(self.TEST_KUBE_CONFIG_SET1) diff --git a/kubernetes_asyncio/config/incluster_config.py b/kubernetes_asyncio/config/incluster_config.py index 09197c35e7..30ebab5615 100644 --- a/kubernetes_asyncio/config/incluster_config.py +++ b/kubernetes_asyncio/config/incluster_config.py @@ -88,7 +88,6 @@ def _set_config(self, client_configuration): client_configuration.host = self.host client_configuration.ssl_ca_cert = self.ssl_ca_cert if self.token is not None: - client_configuration.api_key['authorization'] = self.token client_configuration.api_key['BearerToken'] = self.token if not self._try_refresh_token: return diff --git a/kubernetes_asyncio/config/incluster_config_test.py b/kubernetes_asyncio/config/incluster_config_test.py index 43cf7e03d1..b65bfdc81a 100644 --- a/kubernetes_asyncio/config/incluster_config_test.py +++ b/kubernetes_asyncio/config/incluster_config_test.py @@ -91,7 +91,7 @@ async def test_refresh_token(self): loader.load_and_set(config) self.assertEqual('bearer ' + _TEST_TOKEN, - await config.get_api_key_with_prefix('authorization')) + await config.get_api_key_with_prefix('BearerToken')) self.assertEqual('bearer ' + _TEST_TOKEN, loader.token) self.assertIsNotNone(loader.token_expires_at) @@ -100,11 +100,11 @@ async def test_refresh_token(self): loader._token_filename = self._create_file_with_temp_content( _TEST_NEW_TOKEN) self.assertEqual('bearer ' + _TEST_TOKEN, - await config.get_api_key_with_prefix('authorization')) + await config.get_api_key_with_prefix('BearerToken')) loader.token_expires_at = datetime.datetime.now() self.assertEqual('bearer ' + _TEST_NEW_TOKEN, - await config.get_api_key_with_prefix('authorization')) + await config.get_api_key_with_prefix('BearerToken')) self.assertEqual('bearer ' + _TEST_NEW_TOKEN, loader.token) self.assertGreater(loader.token_expires_at, old_token_expires_at)