From e0ce4c49f3e441f4b412ad5e3a21bdab5d58820d Mon Sep 17 00:00:00 2001 From: liortamary Date: Mon, 21 Feb 2022 17:41:14 +0200 Subject: [PATCH 1/5] Add support for Kubernetes authentication method in Hashicorp Vault secret lookup Signed-off-by: liortamary --- awx/main/credential_plugins/hashivault.py | 37 ++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/awx/main/credential_plugins/hashivault.py b/awx/main/credential_plugins/hashivault.py index c179fcd1e7..dece2f777a 100644 --- a/awx/main/credential_plugins/hashivault.py +++ b/awx/main/credential_plugins/hashivault.py @@ -47,14 +47,21 @@ base_inputs = { 'multiline': False, 'help_text': _('Name of the namespace to use when authenticate and retrieve secrets'), }, + { + 'id': 'kubernetes_role', + 'label': _('Kubernetes role'), + 'type': 'string', + 'multiline': False, + 'help_text': _('The Role for Kubernetes Authentication'), + }, { 'id': 'default_auth_path', - 'label': _('Path to Approle Auth'), + 'label': _('Path to Auth'), 'type': 'string', 'multiline': False, 'default': 'approle', 'help_text': _( - 'The AppRole Authentication path to use if one isn\'t provided in the metadata when linking to an input field. Defaults to \'approle\'' + 'The Authentication path to use if one isn\'t provided in the metadata when linking to an input field. Defaults to \'approle\'' ), }, ], @@ -151,9 +158,11 @@ def handle_auth(**kwargs): if kwargs.get('token'): token = kwargs['token'] elif kwargs.get('role_id') and kwargs.get('secret_id'): - token = approle_auth(**kwargs) + token = method_auth(**kwargs, auth_param=approle_auth(**kwargs)) + elif kwargs.get('kubernetes_role'): + token = method_auth(**kwargs, auth_param=kubernetes_auth(**kwargs)) else: - raise Exception('Either token or AppRole parameters must be set') + raise Exception('Either token or AppRole/Kubernetes authentication parameters must be set') return token @@ -161,6 +170,23 @@ def handle_auth(**kwargs): def approle_auth(**kwargs): role_id = kwargs['role_id'] secret_id = kwargs['secret_id'] + # AppRole Login + return {'role_id': role_id, 'secret_id': secret_id} + + +def kubernetes_auth(**kwargs): + role = kwargs['kubernetes_role'] + jwt_file = pathlib.Path('/var/run/secrets/kubernetes.io/serviceaccount/token') + with jwt_file.open('r') as jwt_fo: + jwt = jwt_fo.read().rstrip() + # Kubernetes Login + return {'role': role, 'jwt': jwt} + + +def method_auth(**kwargs): + # get auth method specific params + request_kwargs = {'json': kwargs['auth_param'], 'timeout': 30} + # we first try to use the 'auth_path' from the metadata # if not found we try to fetch the 'default_auth_path' from inputs auth_path = kwargs.get('auth_path') or kwargs['default_auth_path'] @@ -168,9 +194,6 @@ def approle_auth(**kwargs): url = urljoin(kwargs['url'], 'v1') cacert = kwargs.get('cacert', None) - request_kwargs = {'timeout': 30} - # AppRole Login - request_kwargs['json'] = {'role_id': role_id, 'secret_id': secret_id} sess = requests.Session() # Namespace support if kwargs.get('namespace'): From 85791f730cfe7b9f05e42883259e106bc8ee8d16 Mon Sep 17 00:00:00 2001 From: liortamary Date: Tue, 22 Feb 2022 13:04:58 +0200 Subject: [PATCH 2/5] Add support for Kubernetes authentication method in Hashicorp Vault secret lookup Signed-off-by: liortamary --- awx/main/credential_plugins/hashivault.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/awx/main/credential_plugins/hashivault.py b/awx/main/credential_plugins/hashivault.py index dece2f777a..5593a81484 100644 --- a/awx/main/credential_plugins/hashivault.py +++ b/awx/main/credential_plugins/hashivault.py @@ -168,19 +168,14 @@ def handle_auth(**kwargs): def approle_auth(**kwargs): - role_id = kwargs['role_id'] - secret_id = kwargs['secret_id'] - # AppRole Login - return {'role_id': role_id, 'secret_id': secret_id} + return {'role_id': kwargs['role_id'], 'secret_id': kwargs['secret_id']} def kubernetes_auth(**kwargs): - role = kwargs['kubernetes_role'] jwt_file = pathlib.Path('/var/run/secrets/kubernetes.io/serviceaccount/token') with jwt_file.open('r') as jwt_fo: jwt = jwt_fo.read().rstrip() - # Kubernetes Login - return {'role': role, 'jwt': jwt} + return {'role': kwargs['kubernetes_role'], 'jwt': jwt} def method_auth(**kwargs): From dd99a25db037abf9e0ff618110112a581e713f9b Mon Sep 17 00:00:00 2001 From: liortamary Date: Tue, 22 Feb 2022 17:53:34 +0200 Subject: [PATCH 3/5] unittest: Add support for Kubernetes authentication method in Hashicorp Vault secret lookup Signed-off-by: liortamary --- .../functional/test_credential_plugins.py | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/awx/main/tests/functional/test_credential_plugins.py b/awx/main/tests/functional/test_credential_plugins.py index 8ff8093c34..6bd4f9cdb2 100644 --- a/awx/main/tests/functional/test_credential_plugins.py +++ b/awx/main/tests/functional/test_credential_plugins.py @@ -1,3 +1,8 @@ +import pytest +from unittest import mock +from awx.main.credential_plugins import hashivault + + def test_imported_azure_cloud_sdk_vars(): from awx.main.credential_plugins import azure_kv @@ -5,3 +10,69 @@ def test_imported_azure_cloud_sdk_vars(): assert all([hasattr(c, 'name') for c in azure_kv.clouds]) assert all([hasattr(c, 'suffixes') for c in azure_kv.clouds]) assert all([hasattr(c.suffixes, 'keyvault_dns') for c in azure_kv.clouds]) + + +def test_hashivault_approle_auth(): + kwargs = { + 'role_id': 'the_role_id', + 'secret_id': 'the_secret_id', + } + expected_res = { + 'role_id': 'the_role_id', + 'secret_id': 'the_secret_id', + } + res = hashivault.approle_auth(**kwargs) + assert res == expected_res + + +def test_hashivault_kubernetes_auth(): + kwargs = { + 'kubernetes_role': 'the_kubernetes_role', + } + expected_res = { + 'role': 'the_kubernetes_role', + 'jwt': 'the_jwt', + } + with mock.patch('pathlib.Path') as path_mock: + mock.mock_open(path_mock.return_value.open, read_data='the_jwt') + res = hashivault.kubernetes_auth(**kwargs) + path_mock.assert_called_with('/var/run/secrets/kubernetes.io/serviceaccount/token') + assert res == expected_res + + +def test_hashivault_handle_auth_token(): + kwargs = { + 'token': 'the_token', + } + token = hashivault.handle_auth(**kwargs) + assert token == kwargs['token'] + + +def test_hashivault_handle_auth_approle(): + kwargs = { + 'role_id': 'the_role_id', + 'secret_id': 'the_secret_id', + } + with mock.patch.object(hashivault, 'method_auth') as method_mock: + method_mock.return_value = 'the_token' + token = hashivault.handle_auth(**kwargs) + method_mock.assert_called_with(**kwargs, auth_param=kwargs) + assert token == 'the_token' + + +def test_hashivault_handle_auth_kubernetes(): + kwargs = { + 'kubernetes_role': 'the_kubernetes_role', + } + with mock.patch.object(hashivault, 'method_auth') as method_mock: + with mock.patch('pathlib.Path') as path_mock: + mock.mock_open(path_mock.return_value.open, read_data='the_jwt') + method_mock.return_value = 'the_token' + token = hashivault.handle_auth(**kwargs) + method_mock.assert_called_with(**kwargs, auth_param={'role': 'the_kubernetes_role', 'jwt': 'the_jwt'}) + assert token == 'the_token' + + +def test_hashivault_handle_auth_not_enough_args(): + with pytest.raises(Exception): + hashivault.handle_auth() From b870659fd968eb2a85558d6f5e25e95c768268be Mon Sep 17 00:00:00 2001 From: liortamary Date: Sat, 26 Feb 2022 18:33:36 +0200 Subject: [PATCH 4/5] run black: Add support for Kubernetes authentication method in Hashicorp Vault secret lookup --- awx/main/credential_plugins/hashivault.py | 4 +--- awx/main/tests/functional/test_credential_plugins.py | 12 ++++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/awx/main/credential_plugins/hashivault.py b/awx/main/credential_plugins/hashivault.py index 5593a81484..4ccc244330 100644 --- a/awx/main/credential_plugins/hashivault.py +++ b/awx/main/credential_plugins/hashivault.py @@ -60,9 +60,7 @@ base_inputs = { 'type': 'string', 'multiline': False, 'default': 'approle', - 'help_text': _( - 'The Authentication path to use if one isn\'t provided in the metadata when linking to an input field. Defaults to \'approle\'' - ), + 'help_text': _('The Authentication path to use if one isn\'t provided in the metadata when linking to an input field. Defaults to \'approle\''), }, ], 'metadata': [ diff --git a/awx/main/tests/functional/test_credential_plugins.py b/awx/main/tests/functional/test_credential_plugins.py index 6bd4f9cdb2..3ea31fce42 100644 --- a/awx/main/tests/functional/test_credential_plugins.py +++ b/awx/main/tests/functional/test_credential_plugins.py @@ -14,12 +14,12 @@ def test_imported_azure_cloud_sdk_vars(): def test_hashivault_approle_auth(): kwargs = { - 'role_id': 'the_role_id', - 'secret_id': 'the_secret_id', + 'role_id': 'the_role_id', + 'secret_id': 'the_secret_id', } expected_res = { - 'role_id': 'the_role_id', - 'secret_id': 'the_secret_id', + 'role_id': 'the_role_id', + 'secret_id': 'the_secret_id', } res = hashivault.approle_auth(**kwargs) assert res == expected_res @@ -27,7 +27,7 @@ def test_hashivault_approle_auth(): def test_hashivault_kubernetes_auth(): kwargs = { - 'kubernetes_role': 'the_kubernetes_role', + 'kubernetes_role': 'the_kubernetes_role', } expected_res = { 'role': 'the_kubernetes_role', @@ -42,7 +42,7 @@ def test_hashivault_kubernetes_auth(): def test_hashivault_handle_auth_token(): kwargs = { - 'token': 'the_token', + 'token': 'the_token', } token = hashivault.handle_auth(**kwargs) assert token == kwargs['token'] From bbf6484e891f4ee661c15f629ff0bc7267b0a736 Mon Sep 17 00:00:00 2001 From: liortamary Date: Tue, 1 Mar 2022 21:21:28 +0200 Subject: [PATCH 5/5] fix code review comments: Add support for Kubernetes authentication method in Hashicorp Vault secret lookup --- awx/main/credential_plugins/hashivault.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/awx/main/credential_plugins/hashivault.py b/awx/main/credential_plugins/hashivault.py index 4ccc244330..6a22efc21e 100644 --- a/awx/main/credential_plugins/hashivault.py +++ b/awx/main/credential_plugins/hashivault.py @@ -52,7 +52,11 @@ base_inputs = { 'label': _('Kubernetes role'), 'type': 'string', 'multiline': False, - 'help_text': _('The Role for Kubernetes Authentication'), + 'help_text': _( + 'The Role for Kubernetes Authentication.' + ' This is the named role, configured in Vault server, for AWX pod auth policies.' + ' see https://www.vaultproject.io/docs/auth/kubernetes#configuration' + ), }, { 'id': 'default_auth_path',