Add support for Vault Namespaces
Vault Namespaces [0] is a feature available in Vault Enterprise that
can be considered as a more advanced isolation feature on top of current
KV Mountpoint option in Castellan Vault plugin.
Passing a namespace in all request headers (including Auth) allows to organize
Vault-in-Vault style of isolation, with clients using the same simple URI path
but accessing separate sets of entities in Vault.
[0] https://www.vaultproject.io/docs/enterprise/namespaces
Change-Id: I627c20002bb2a0a1b346b57e824f87f856eca4c9
Pavlo Shchelokovskyy authored 2 years ago
Pavlo Shchelokovskyy committed 2 years ago
66 | 66 | cfg.BoolOpt('use_ssl', |
67 | 67 | default=False, |
68 | 68 | help=_('SSL Enabled/Disabled')), |
69 | cfg.StrOpt("namespace", | |
70 | help=_("Vault Namespace to use for all requests to Vault. " | |
71 | "Vault Namespaces feature is available only in " | |
72 | "Vault Enterprise")), | |
69 | 73 | ] |
70 | 74 | |
71 | 75 | _VAULT_OPT_GROUP = 'vault' |
98 | 102 | self._kv_mountpoint = self._conf.vault.kv_mountpoint |
99 | 103 | self._kv_version = self._conf.vault.kv_version |
100 | 104 | self._vault_url = self._conf.vault.vault_url |
105 | self._namespace = self._conf.vault.namespace | |
101 | 106 | if self._vault_url.startswith("https://"): |
102 | 107 | self._verify_server = self._conf.vault.ssl_ca_crt_file or True |
103 | 108 | else: |
127 | 132 | self._cached_approle_token_id = None |
128 | 133 | return self._cached_approle_token_id |
129 | 134 | |
135 | def _set_namespace(self, headers): | |
136 | if self._namespace: | |
137 | headers["X-Vault-Namespace"] = self._namespace | |
138 | return headers | |
139 | ||
130 | 140 | def _build_auth_headers(self): |
131 | 141 | if self._root_token_id: |
132 | return {'X-Vault-Token': self._root_token_id} | |
142 | return self._set_namespace( | |
143 | {'X-Vault-Token': self._root_token_id}) | |
133 | 144 | |
134 | 145 | if self._approle_token_id: |
135 | return {'X-Vault-Token': self._approle_token_id} | |
146 | return self._set_namespace( | |
147 | {'X-Vault-Token': self._approle_token_id}) | |
136 | 148 | |
137 | 149 | if self._approle_role_id: |
138 | 150 | params = { |
144 | 156 | self._get_url() |
145 | 157 | ) |
146 | 158 | token_issue_utc = timeutils.utcnow() |
159 | headers = self._set_namespace({}) | |
147 | 160 | try: |
148 | 161 | resp = self._session.post(url=approle_login_url, |
149 | 162 | json=params, |
163 | headers=headers, | |
150 | 164 | verify=self._verify_server) |
151 | 165 | except requests.exceptions.Timeout as ex: |
152 | 166 | raise exception.KeyManagerError(str(ex)) |
168 | 182 | self._cached_approle_token_id = resp_data['auth']['client_token'] |
169 | 183 | self._approle_token_issue = token_issue_utc |
170 | 184 | self._approle_token_ttl = resp_data['auth']['lease_duration'] |
171 | return {'X-Vault-Token': self._approle_token_id} | |
185 | return self._set_namespace( | |
186 | {'X-Vault-Token': self._approle_token_id}) | |
172 | 187 | |
173 | 188 | return {} |
174 | 189 |
45 | 45 | vault_approle_role_id=None, vault_approle_secret_id=None, |
46 | 46 | vault_kv_mountpoint=None, vault_url=None, |
47 | 47 | vault_ssl_ca_crt_file=None, vault_use_ssl=None, |
48 | vault_namespace=None, | |
48 | 49 | barbican_endpoint_type=None): |
49 | 50 | """Set defaults for configuration values. |
50 | 51 | |
66 | 67 | :param vault_url: Use this for the url for vault. |
67 | 68 | :param vault_use_ssl: Use this to force vault driver to use ssl. |
68 | 69 | :param vault_ssl_ca_crt_file: Use this for the CA file for vault. |
70 | :param vault_namespace: Namespace to use for all requests to Vault. | |
69 | 71 | :param barbican_endpoint_type: Use this to specify the type of URL. |
70 | 72 | : Valid values are: public, internal or admin. |
71 | 73 | """ |
133 | 135 | if vault_use_ssl is not None: |
134 | 136 | conf.set_default('use_ssl', vault_use_ssl, |
135 | 137 | group=vkm._VAULT_OPT_GROUP) |
138 | if vault_namespace is not None: | |
139 | conf.set_default('namespace', vault_namespace, | |
140 | group=vkm._VAULT_OPT_GROUP) | |
136 | 141 | |
137 | 142 | |
138 | 143 | def enable_logging(conf=None, app_name='castellan'): |
0 | # Copyright (c) 2021 Mirantis Inc | |
1 | # All Rights Reserved. | |
2 | # | |
3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | |
4 | # not use this file except in compliance with the License. You may obtain | |
5 | # a copy of the License at | |
6 | # | |
7 | # http://www.apache.org/licenses/LICENSE-2.0 | |
8 | # | |
9 | # Unless required by applicable law or agreed to in writing, software | |
10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
12 | # License for the specific language governing permissions and limitations | |
13 | # under the License. | |
14 | ||
15 | """ | |
16 | Test cases for Vault key manager. | |
17 | """ | |
18 | import requests_mock | |
19 | ||
20 | from castellan.key_manager import vault_key_manager | |
21 | from castellan.tests.unit.key_manager import test_key_manager | |
22 | ||
23 | ||
24 | class VaultKeyManagerTestCase(test_key_manager.KeyManagerTestCase): | |
25 | ||
26 | def _create_key_manager(self): | |
27 | return vault_key_manager.VaultKeyManager(self.conf) | |
28 | ||
29 | def test_auth_headers_root_token(self): | |
30 | self.key_mgr._root_token_id = "spam" | |
31 | expected_headers = {"X-Vault-Token": "spam"} | |
32 | self.assertEqual(expected_headers, | |
33 | self.key_mgr._build_auth_headers()) | |
34 | ||
35 | def test_auth_headers_root_token_with_namespace(self): | |
36 | self.key_mgr._root_token_id = "spam" | |
37 | self.key_mgr._namespace = "ham" | |
38 | expected_headers = {"X-Vault-Token": "spam", | |
39 | "X-Vault-Namespace": "ham"} | |
40 | self.assertEqual(expected_headers, | |
41 | self.key_mgr._build_auth_headers()) | |
42 | ||
43 | @requests_mock.Mocker() | |
44 | def test_auth_headers_app_role(self, m): | |
45 | self.key_mgr._approle_role_id = "spam" | |
46 | self.key_mgr._approle_secret_id = "secret" | |
47 | m.post( | |
48 | "http://127.0.0.1:8200/v1/auth/approle/login", | |
49 | json={"auth": {"client_token": "token", "lease_duration": 3600}} | |
50 | ) | |
51 | expected_headers = {"X-Vault-Token": "token"} | |
52 | self.assertEqual(expected_headers, self.key_mgr._build_auth_headers()) | |
53 | ||
54 | @requests_mock.Mocker() | |
55 | def test_auth_headers_app_role_with_namespace(self, m): | |
56 | self.key_mgr._approle_role_id = "spam" | |
57 | self.key_mgr._approle_secret_id = "secret" | |
58 | self.key_mgr._namespace = "ham" | |
59 | m.post( | |
60 | "http://127.0.0.1:8200/v1/auth/approle/login", | |
61 | json={"auth": {"client_token": "token", "lease_duration": 3600}} | |
62 | ) | |
63 | expected_headers = {"X-Vault-Token": "token", | |
64 | "X-Vault-Namespace": "ham"} | |
65 | self.assertEqual(expected_headers, self.key_mgr._build_auth_headers()) |