Skip to content

Commit 242b9a8

Browse files
committed
Address review comments and improve auth
Auth now chooses between cert (#1) and bearer token (#2) instead of sending both. Also renamed ssl --> tls for correctness
1 parent e6c096d commit 242b9a8

File tree

4 files changed

+95
-87
lines changed

4 files changed

+95
-87
lines changed

checks.d/kubernetes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def _perform_kubelet_checks(self, url):
103103
service_check_base = NAMESPACE + '.kubelet.check'
104104
is_ok = True
105105
try:
106-
req = self.kubeutil.retrieve_kubelet_url(url)
106+
req = self.kubeutil.perform_kubelet_query(url)
107107
for line in req.iter_lines():
108108

109109
# avoid noise; this check is expected to fail since we override the container hostname

conf.d/kubernetes.yaml.example

+12-9
Original file line numberDiff line numberDiff line change
@@ -69,29 +69,32 @@ instances:
6969
# kubelet port. It needs to be set if you are not using a default one (10250 or 10255)
7070
# kubelet_port: 10255
7171
#
72-
# SSL configuration for querying kubelet.
72+
# TLS configuration for querying kubelet.
7373
#
7474
# Available options are:
75-
# - simple SSL validation (with readily available certificates)
76-
# - disabling SSL validation
75+
# (server auth)
76+
# - simple TLS validation (with readily available certificates)
77+
# - disabling TLS validation
7778
# - providing the server certificate
79+
# (client auth)
7880
# - providing a client cert/key pair
81+
# - using a service account bearer token
7982
#
80-
# The default is to try and use the read-only port that doesn't require SSL
81-
# And to fall back to the HTTPS API with simple SSL validation.
83+
# The default is to try and use the read-only port that doesn't require TLS
84+
# And to fall back to the HTTPS API with simple TLS validation.
8285
#
83-
# Explicitly disabling ssl_verify should be used with caution:
86+
# Explicitly disabling tls_verify should be used with caution:
8487
# if an attacker sniffs the agent requests they will see the agent's service account bearer token.
8588
#
86-
# Providing a server cert enables SSL validation.
89+
# Providing a server cert enables TLS validation.
8790
#
8891
# It is possible to providing a client cert/key pair for client auth.
89-
# The agent also uses its pod bearer token (if available) when querying kubelet and the apiserver.
92+
# If no such cert is passed, the agent authenticates using its pod bearer token (if available) when querying kubelet and the apiserver.
9093
#
9194
# The recommended way to pass certificates and keys to the agent is by using
9295
# "Secret" Kubernetes object (and to mount them as volumes).
9396
#
94-
# kubelet_ssl_verify: True
97+
# kubelet_tls_verify: True
9598
# kubelet_cert: /path/to/ca.pem
9699
# kubelet_client_crt: /path/to/client.crt
97100
# kubelet_client_key: /path/to/client.key

tests/checks/mock/test_kubernetes.py

+45-43
Original file line numberDiff line numberDiff line change
@@ -385,22 +385,31 @@ class TestKubeutil(unittest.TestCase):
385385
def setUp(self, _locate_kubelet):
386386
self.kubeutil = KubeUtil()
387387

388-
def test_init_ssl_settings(self):
388+
@mock.patch('os.path.exists', return_value=True)
389+
@mock.patch('utils.kubernetes.kubeutil.KubeUtil.get_auth_token', return_value='tkn')
390+
def test_init_tls_settings(self, *args):
391+
# kubelet
389392
instances = [
390393
# (instance, expected_result)
391-
({}, {'verify': True}),
392-
({'kubelet_ssl_verify': False}, {'verify': False}),
393-
({'kubelet_ssl_verify': True}, {'verify': True}),
394-
({'kubelet_ssl_verify': 'foo.pem'}, {'verify': 'foo.pem'}),
395-
({'kubelet_cert': 'foo.pem'}, {'verify': 'foo.pem'}),
394+
({}, {'kubelet_verify': True, 'bearer_token': 'tkn'}),
395+
({'kubelet_tls_verify': False}, {'kubelet_verify': False, 'bearer_token': 'tkn'}),
396+
({'kubelet_tls_verify': True}, {'kubelet_verify': True, 'bearer_token': 'tkn'}),
397+
({'kubelet_tls_verify': 'foo.pem'}, {'kubelet_verify': 'foo.pem', 'bearer_token': 'tkn'}),
398+
({'kubelet_cert': 'foo.pem'}, {'kubelet_verify': 'foo.pem', 'bearer_token': 'tkn'}),
396399
({'kubelet_client_crt': 'client.crt', 'kubelet_client_key': 'client.key'},
397-
{'verify': True, 'kubelet_client_cert': ('client.crt', 'client.key')}),
398-
({'kubelet_ssl_verify': True, 'kubelet_client_crt': 'client.crt'}, {'verify': True}),
399-
({'kubelet_client_crt': 'client.crt'}, {'verify': True})
400+
{'kubelet_verify': True, 'kubelet_client_cert': ('client.crt', 'client.key'), 'bearer_token': 'tkn'}),
401+
({'kubelet_tls_verify': True, 'kubelet_client_crt': 'client.crt'}, {'kubelet_verify': True, 'bearer_token': 'tkn'}),
402+
({'kubelet_client_crt': 'client.crt'}, {'kubelet_verify': True, 'bearer_token': 'tkn'})
400403
]
401404
for instance, result in instances:
402-
self.assertEqual(self.kubeutil._init_ssl_settings(instance), result)
405+
self.assertEqual(self.kubeutil._init_tls_settings(instance), result)
403406

407+
# apiserver
408+
instance = {'apiserver_client_crt': 'foo.crt', 'apiserver_client_key': 'foo.key'}
409+
expected_res = {'apiserver_client_cert': ('foo.crt', 'foo.key'), 'kubelet_verify': True, 'bearer_token': 'tkn'}
410+
self.assertEqual(self.kubeutil._init_tls_settings(instance), expected_res)
411+
with mock.patch('utils.kubernetes.kubeutil.os.path.exists', return_value=False):
412+
self.assertEqual(self.kubeutil._init_tls_settings(instance), {'kubelet_verify': True, 'bearer_token': 'tkn'})
404413

405414
##### Test _locate_kubelet #####
406415

@@ -419,7 +428,7 @@ def test_locate_kubelet_no_auth_no_ssl(self, _get_hostname):
419428
({'kubelet_port': '1337'}, 'http://test_docker_host:1337'),
420429
({'host': 'test_explicit_host', 'kubelet_port': '1337'}, 'http://test_explicit_host:1337')
421430
]
422-
with mock.patch('utils.kubernetes.kubeutil.KubeUtil.retrieve_kubelet_url', return_value=True):
431+
with mock.patch('utils.kubernetes.kubeutil.KubeUtil.perform_kubelet_query', return_value=True):
423432
for instance, result in no_auth_no_ssl_instances:
424433
self.assertEqual(self.kubeutil._locate_kubelet(instance), result)
425434

@@ -434,13 +443,13 @@ def test_locate_kubelet_no_auth_no_verify(self, _get_hostname):
434443
]
435444

436445
def side_effect(url):
437-
"""Mock KubeUtil.retrieve_kubelet_url"""
446+
"""Mock KubeUtil.perform_kubelet_query"""
438447
if url.startswith('https://'):
439448
return True
440449
else:
441450
raise Exception()
442451

443-
with mock.patch('utils.kubernetes.kubeutil.KubeUtil.retrieve_kubelet_url', side_effect=side_effect):
452+
with mock.patch('utils.kubernetes.kubeutil.KubeUtil.perform_kubelet_query', side_effect=side_effect):
444453
for instance, result in no_auth_no_verify_instances:
445454
self.assertEqual(self.kubeutil._locate_kubelet(instance), result)
446455

@@ -449,65 +458,58 @@ def side_effect(url):
449458
@mock.patch('utils.kubernetes.kubeutil.KubeUtil.get_auth_token', return_value='foo')
450459
def test_locate_kubelet_verify_and_auth(self, *args):
451460
"""
452-
Test kubelet connection with SSL. Also look for auth token.
461+
Test kubelet connection with TLS. Also look for auth token.
453462
"""
454463
no_auth_instances = [
455-
# instance, ssl_settings, expected_result
456-
({}, {'verify': True}, 'https://test_k8s_host:10250'),
457-
({'kubelet_port': '1337'}, {'verify': 'test.pem'}, 'https://test_k8s_host:1337'),
464+
# instance, tls_settings, expected_result
465+
({}, {'kubelet_verify': True}, 'https://test_k8s_host:10250'),
466+
({'kubelet_port': '1337'}, {'kubelet_verify': 'test.pem'}, 'https://test_k8s_host:1337'),
458467
(
459468
{'host': 'test_explicit_host'},
460-
{'verify': True, 'kubelet_client_cert': ('client.crt', 'client.key')},
469+
{'kubelet_verify': True, 'kubelet_client_cert': ('client.crt', 'client.key')},
461470
'https://test_explicit_host:10250'
462471
),
463472
(
464473
{'host': 'test_explicit_host', 'kubelet_port': '1337'},
465-
{'verify': True},
474+
{'kubelet_verify': True},
466475
'https://test_explicit_host:1337'
467476
),
468477
]
469478

470479
def side_effect(url, **kwargs):
471-
"""Mock KubeUtil.retrieve_kubelet_url"""
480+
"""Mock KubeUtil.perform_kubelet_query"""
472481
if url.startswith('https://') and '10255' not in url:
473482
return True
474483
else:
475484
raise Exception()
476485

477-
# no auth / SSL with verify
478-
for instance, ssl_settings, result in no_auth_instances:
486+
# no auth / TLS with verify
487+
for instance, tls_settings, result in no_auth_instances:
479488
with mock.patch('utils.kubernetes.kubeutil.requests') as req:
480489
req.get = mock.MagicMock(side_effect=side_effect)
481-
self.kubeutil.ssl_settings = ssl_settings
490+
self.kubeutil.tls_settings = tls_settings
482491
self.assertEqual(self.kubeutil._locate_kubelet(instance), result)
483492
req.get.assert_called_with(result + '/healthz', # test endpoint
484493
timeout=10,
485-
verify=ssl_settings.get('verify', False),
486-
cert=ssl_settings.get('kubelet_client_cert'),
487-
headers={'Authorization': 'Bearer foo'}, # auth
494+
verify=tls_settings.get('kubelet_verify', True),
495+
headers={'Authorization': 'Bearer foo'} if 'kubelet_client_cert' not in tls_settings else None,
496+
cert=tls_settings.get('kubelet_client_cert'),
488497
params={'verbose': True}
489498
)
490499

491500
@mock.patch('utils.kubernetes.kubeutil.KubeUtil.get_auth_token', return_value='foo')
492501
def test_get_node_hostname(self, _get_auth_tkn):
493502
node_lists = [
494503
(json.loads(Fixtures.read_file('filtered_node_list_1_4.json', string_escape=False)), 'ip-10-0-0-179'),
495-
({'items': [{'foo': 'bar'}]}, None)
496-
]
497-
498-
exception_node_lists = [
499-
{'items': []},
500-
{'items': [{'foo': 'bar'}, {'bar': 'foo'}]}
504+
({'items': [{'foo': 'bar'}]}, None),
505+
({'items': []}, None),
506+
({'items': [{'foo': 'bar'}, {'bar': 'foo'}]}, None)
501507
]
502508

503509
for node_list, expected_result in node_lists:
504510
with mock.patch('utils.kubernetes.kubeutil.KubeUtil.retrieve_json_auth', return_value=node_list):
505511
self.assertEqual(self.kubeutil.get_node_hostname('ip-10-0-0-179'), expected_result)
506512

507-
for node_list in exception_node_lists:
508-
with mock.patch('utils.kubernetes.kubeutil.KubeUtil.retrieve_json_auth', return_value=node_list):
509-
self.assertRaises(Exception, self.kubeutil.get_node_hostname, 'ip-10-0-0-179')
510-
511513
@mock.patch('utils.kubernetes.KubeUtil.retrieve_pods_list', side_effect=['foo'])
512514
@mock.patch('utils.kubernetes.KubeUtil.extract_kube_labels')
513515
def test_get_kube_labels(self, extract_kube_labels, retrieve_pods_list):
@@ -551,7 +553,7 @@ def test_extract_kube_labels(self):
551553
labels = set(inn for out in res.values() for inn in out)
552554
self.assertEqual(len(labels), 3)
553555

554-
@mock.patch('utils.kubernetes.kubeutil.KubeUtil.retrieve_kubelet_url')
556+
@mock.patch('utils.kubernetes.kubeutil.KubeUtil.perform_kubelet_query')
555557
def test_retrieve_pods_list(self, retrieve_url):
556558
self.kubeutil.retrieve_pods_list()
557559
retrieve_url.assert_called_twice_with(self.kubeutil.pods_list_url, verbose=True, timeout=10)
@@ -568,7 +570,7 @@ def test_retrieve_metrics(self, retrieve_json):
568570

569571
@mock.patch('utils.kubernetes.kubeutil.KubeUtil.get_auth_token', return_value='foo')
570572
@mock.patch('utils.kubernetes.kubeutil.requests')
571-
def test_retrieve_kubelet_url(self, req, _get_auth_tkn):
573+
def test_perform_kubelet_query(self, req, _get_auth_tkn):
572574
base_params = {'timeout': 10, 'verify': False,
573575
'params': {'verbose': True}, 'cert': None, 'headers': None}
574576

@@ -580,15 +582,15 @@ def test_retrieve_kubelet_url(self, req, _get_auth_tkn):
580582
instances = [
581583
('http://test.com', {}, dict(base_params.items() + verify_true.items())),
582584
('https://test.com', {}, dict(base_params.items() + verify_true.items() + auth_token_header.items())),
583-
('https://test.com', {'verify': True}, dict(base_params.items() + verify_true.items() + auth_token_header.items())),
584-
('https://test.com', {'verify': 'kubelet.pem'}, dict(base_params.items() + verify_cert.items() + auth_token_header.items())),
585+
('https://test.com', {'kubelet_verify': True}, dict(base_params.items() + verify_true.items() + auth_token_header.items())),
586+
('https://test.com', {'kubelet_verify': 'kubelet.pem'}, dict(base_params.items() + verify_cert.items() + auth_token_header.items())),
585587
('https://test.com', {'kubelet_client_cert': ('client.crt', 'client.key')},
586-
dict(base_params.items() + verify_true.items() + client_cert.items() + auth_token_header.items())),
588+
dict(base_params.items() + verify_true.items() + client_cert.items())),
587589
]
588590
for url, ssl_context, expected_params in instances:
589591
req.get.reset_mock()
590-
self.kubeutil.ssl_settings = ssl_context
591-
self.kubeutil.retrieve_kubelet_url(url)
592+
self.kubeutil.tls_settings = ssl_context
593+
self.kubeutil.perform_kubelet_query(url)
592594
req.get.assert_called_with(url, **expected_params)
593595

594596
@mock.patch('utils.kubernetes.kubeutil.requests')

0 commit comments

Comments
 (0)