diff --git a/castellan/key_manager/vault_key_manager.py b/castellan/key_manager/vault_key_manager.py index b0f8edf2..0bc4ea5e 100644 --- a/castellan/key_manager/vault_key_manager.py +++ b/castellan/key_manager/vault_key_manager.py @@ -67,6 +67,10 @@ _vault_opts = [ cfg.BoolOpt('use_ssl', default=False, help=_('SSL Enabled/Disabled')), + cfg.StrOpt("namespace", + help=_("Vault Namespace to use for all requests to Vault. " + "Vault Namespaces feature is available only in " + "Vault Enterprise")), ] _VAULT_OPT_GROUP = 'vault' @@ -99,6 +103,7 @@ class VaultKeyManager(key_manager.KeyManager): self._kv_mountpoint = self._conf.vault.kv_mountpoint self._kv_version = self._conf.vault.kv_version self._vault_url = self._conf.vault.vault_url + self._namespace = self._conf.vault.namespace if self._vault_url.startswith("https://"): self._verify_server = self._conf.vault.ssl_ca_crt_file or True else: @@ -128,12 +133,19 @@ class VaultKeyManager(key_manager.KeyManager): self._cached_approle_token_id = None return self._cached_approle_token_id + def _set_namespace(self, headers): + if self._namespace: + headers["X-Vault-Namespace"] = self._namespace + return headers + def _build_auth_headers(self): if self._root_token_id: - return {'X-Vault-Token': self._root_token_id} + return self._set_namespace( + {'X-Vault-Token': self._root_token_id}) if self._approle_token_id: - return {'X-Vault-Token': self._approle_token_id} + return self._set_namespace( + {'X-Vault-Token': self._approle_token_id}) if self._approle_role_id: params = { @@ -145,9 +157,11 @@ class VaultKeyManager(key_manager.KeyManager): self._get_url() ) token_issue_utc = timeutils.utcnow() + headers = self._set_namespace({}) try: resp = self._session.post(url=approle_login_url, json=params, + headers=headers, verify=self._verify_server) except requests.exceptions.Timeout as ex: raise exception.KeyManagerError(str(ex)) @@ -169,7 +183,8 @@ class VaultKeyManager(key_manager.KeyManager): self._cached_approle_token_id = resp_data['auth']['client_token'] self._approle_token_issue = token_issue_utc self._approle_token_ttl = resp_data['auth']['lease_duration'] - return {'X-Vault-Token': self._approle_token_id} + return self._set_namespace( + {'X-Vault-Token': self._approle_token_id}) return {} diff --git a/castellan/options.py b/castellan/options.py index 213afc15..7a5410d1 100644 --- a/castellan/options.py +++ b/castellan/options.py @@ -46,6 +46,7 @@ def set_defaults(conf, backend=None, barbican_endpoint=None, vault_approle_role_id=None, vault_approle_secret_id=None, vault_kv_mountpoint=None, vault_url=None, vault_ssl_ca_crt_file=None, vault_use_ssl=None, + vault_namespace=None, barbican_endpoint_type=None): """Set defaults for configuration values. @@ -67,6 +68,7 @@ def set_defaults(conf, backend=None, barbican_endpoint=None, :param vault_url: Use this for the url for vault. :param vault_use_ssl: Use this to force vault driver to use ssl. :param vault_ssl_ca_crt_file: Use this for the CA file for vault. + :param vault_namespace: Namespace to use for all requests to Vault. :param barbican_endpoint_type: Use this to specify the type of URL. : Valid values are: public, internal or admin. """ @@ -134,6 +136,9 @@ def set_defaults(conf, backend=None, barbican_endpoint=None, if vault_use_ssl is not None: conf.set_default('use_ssl', vault_use_ssl, group=vkm._VAULT_OPT_GROUP) + if vault_namespace is not None: + conf.set_default('namespace', vault_namespace, + group=vkm._VAULT_OPT_GROUP) def enable_logging(conf=None, app_name='castellan'): diff --git a/castellan/tests/unit/key_manager/test_vault_key_manager.py b/castellan/tests/unit/key_manager/test_vault_key_manager.py new file mode 100644 index 00000000..8f511314 --- /dev/null +++ b/castellan/tests/unit/key_manager/test_vault_key_manager.py @@ -0,0 +1,66 @@ +# Copyright (c) 2021 Mirantis Inc +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Test cases for Vault key manager. +""" +import requests_mock + +from castellan.key_manager import vault_key_manager +from castellan.tests.unit.key_manager import test_key_manager + + +class VaultKeyManagerTestCase(test_key_manager.KeyManagerTestCase): + + def _create_key_manager(self): + return vault_key_manager.VaultKeyManager(self.conf) + + def test_auth_headers_root_token(self): + self.key_mgr._root_token_id = "spam" + expected_headers = {"X-Vault-Token": "spam"} + self.assertEqual(expected_headers, + self.key_mgr._build_auth_headers()) + + def test_auth_headers_root_token_with_namespace(self): + self.key_mgr._root_token_id = "spam" + self.key_mgr._namespace = "ham" + expected_headers = {"X-Vault-Token": "spam", + "X-Vault-Namespace": "ham"} + self.assertEqual(expected_headers, + self.key_mgr._build_auth_headers()) + + @requests_mock.Mocker() + def test_auth_headers_app_role(self, m): + self.key_mgr._approle_role_id = "spam" + self.key_mgr._approle_secret_id = "secret" + m.post( + "http://127.0.0.1:8200/v1/auth/approle/login", + json={"auth": {"client_token": "token", "lease_duration": 3600}} + ) + expected_headers = {"X-Vault-Token": "token"} + self.assertEqual(expected_headers, self.key_mgr._build_auth_headers()) + + @requests_mock.Mocker() + def test_auth_headers_app_role_with_namespace(self, m): + self.key_mgr._approle_role_id = "spam" + self.key_mgr._approle_secret_id = "secret" + self.key_mgr._namespace = "ham" + m.post( + "http://127.0.0.1:8200/v1/auth/approle/login", + json={"auth": {"client_token": "token", "lease_duration": 3600}} + ) + expected_headers = {"X-Vault-Token": "token", + "X-Vault-Namespace": "ham"} + self.assertEqual(expected_headers, self.key_mgr._build_auth_headers()) diff --git a/releasenotes/notes/vault-namespaces-7d334e7407396df9.yaml b/releasenotes/notes/vault-namespaces-7d334e7407396df9.yaml new file mode 100644 index 00000000..54a816b2 --- /dev/null +++ b/releasenotes/notes/vault-namespaces-7d334e7407396df9.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Added support for Vault Namespaces, which is a `feature of Vault Enterprise + `_. + A new config option ``namespace`` is added to the configuration of Vault + key manager to support this feature. diff --git a/test-requirements.txt b/test-requirements.txt index eedc2f87..9d949b0b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,6 +9,7 @@ pyflakes>=2.1.1 coverage!=4.4,>=4.0 # Apache-2.0 python-barbicanclient>=4.5.2 # Apache-2.0 +requests-mock>=1.2.0 # Apache-2.0 python-subunit>=1.0.0 # Apache-2.0/BSD oslotest>=3.2.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0