Skip to content
This repository was archived by the owner on Mar 13, 2022. It is now read-only.

Commit b85aff2

Browse files
committed
Accept client certificates from an authn/authz plugin
(Plugin interface reference: https://kubernetes.io/docs/reference/access-authn-authz/authentication/#input-and-output-formats) When handling the response from the authn/authz plugin, `token` will be used if provided, which maintains current behaviour. Newly added is handling `clientCertificateData`: if it is present, that certificate (and its key) will be used as provided by the plugin. (And any certificate/key pair provided via the `users` section of the configuration file will be ignored.)
1 parent fb86b8a commit b85aff2

File tree

2 files changed

+70
-11
lines changed

2 files changed

+70
-11
lines changed

config/kube_config.py

+35-11
Original file line numberDiff line numberDiff line change
@@ -472,11 +472,31 @@ def _load_from_exec_plugin(self):
472472
return
473473
try:
474474
status = ExecProvider(self._user['exec']).run()
475-
if 'token' not in status:
476-
logging.error('exec: missing token field in plugin output')
477-
return None
478-
self.token = "Bearer %s" % status['token']
479-
return True
475+
if 'token' in status:
476+
self.token = "Bearer %s" % status['token']
477+
return True
478+
if 'clientCertificateData' in status:
479+
# https://kubernetes.io/docs/reference/access-authn-authz/authentication/#input-and-output-formats
480+
# Plugin has provided certificates instead of a token.
481+
if 'clientKeyData' not in status:
482+
logging.error('exec: missing clientKeyData field in '
483+
'plugin output')
484+
return None
485+
base_path = self._get_base_path(self._cluster.path)
486+
self.cert_file = FileOrData(
487+
status, None,
488+
data_key_name='clientCertificateData',
489+
file_base_path=base_path,
490+
base64_file_content=False).as_file()
491+
self.key_file = FileOrData(
492+
status, None,
493+
data_key_name='clientKeyData',
494+
file_base_path=base_path,
495+
base64_file_content=False).as_file()
496+
return True
497+
logging.error('exec: missing token or clientCertificateData field '
498+
'in plugin output')
499+
return None
480500
except Exception as e:
481501
logging.error(str(e))
482502

@@ -512,12 +532,16 @@ def _load_cluster_info(self):
512532
self.ssl_ca_cert = FileOrData(
513533
self._cluster, 'certificate-authority',
514534
file_base_path=base_path).as_file()
515-
self.cert_file = FileOrData(
516-
self._user, 'client-certificate',
517-
file_base_path=base_path).as_file()
518-
self.key_file = FileOrData(
519-
self._user, 'client-key',
520-
file_base_path=base_path).as_file()
535+
if 'cert_file' not in self.__dict__:
536+
# cert_file could have been provided by
537+
# _load_from_exec_plugin; only load from the _user
538+
# section if we need it.
539+
self.cert_file = FileOrData(
540+
self._user, 'client-certificate',
541+
file_base_path=base_path).as_file()
542+
self.key_file = FileOrData(
543+
self._user, 'client-key',
544+
file_base_path=base_path).as_file()
521545
if 'insecure-skip-tls-verify' in self._cluster:
522546
self.verify_ssl = not self._cluster['insecure-skip-tls-verify']
523547

config/kube_config_test.py

+35
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,13 @@ class TestKubeConfigLoader(BaseTestCase):
541541
"user": "exec_cred_user"
542542
}
543543
},
544+
{
545+
"name": "exec_cred_user_certificate",
546+
"context": {
547+
"cluster": "ssl",
548+
"user": "exec_cred_user_certificate"
549+
}
550+
},
544551
{
545552
"name": "contexttestcmdpath",
546553
"context": {
@@ -865,6 +872,16 @@ class TestKubeConfigLoader(BaseTestCase):
865872
}
866873
}
867874
},
875+
{
876+
"name": "exec_cred_user_certificate",
877+
"user": {
878+
"exec": {
879+
"apiVersion": "client.authentication.k8s.io/v1beta1",
880+
"command": "custom-certificate-authenticator",
881+
"args": []
882+
}
883+
}
884+
},
868885
{
869886
"name": "usertestcmdpath",
870887
"user": {
@@ -1295,6 +1312,24 @@ def test_user_exec_auth(self, mock):
12951312
active_context="exec_cred_user").load_and_set(actual)
12961313
self.assertEqual(expected, actual)
12971314

1315+
@mock.patch('kubernetes.config.kube_config.ExecProvider.run')
1316+
def test_user_exec_auth_certificates(self, mock):
1317+
mock.return_value = {
1318+
"clientCertificateData": TEST_CLIENT_CERT,
1319+
"clientKeyData": TEST_CLIENT_KEY,
1320+
}
1321+
expected = FakeConfig(
1322+
host=TEST_SSL_HOST,
1323+
cert_file=self._create_temp_file(TEST_CLIENT_CERT),
1324+
key_file=self._create_temp_file(TEST_CLIENT_KEY),
1325+
ssl_ca_cert=self._create_temp_file(TEST_CERTIFICATE_AUTH),
1326+
verify_ssl=True)
1327+
actual = FakeConfig()
1328+
KubeConfigLoader(
1329+
config_dict=self.TEST_KUBE_CONFIG,
1330+
active_context="exec_cred_user_certificate").load_and_set(actual)
1331+
self.assertEqual(expected, actual)
1332+
12981333
def test_user_cmd_path(self):
12991334
A = namedtuple('A', ['token', 'expiry'])
13001335
token = "dummy"

0 commit comments

Comments
 (0)