Vault based key manager
* Uses https://www.vaultproject.io/ to store/fetch secrets
* All we need is the URL and a Token to talk to the vault server
* tox target "functional-vault" sets up a server in development mode
and runs functional tests
* Supports both http:// and https:// url(s)
* the https support was tested by setting up a vault server by hand
(https://gist.github.com/dims/47674cf2c3b0a953df69246c2ea1ff78)
* create_key_pair is the only API that is not implemented
Change-Id: I6436e5841c8e77a7262b4d5aa39201b40a985255
Davanum Srinivas
5 years ago
24 | 24 | default='barbican', |
25 | 25 | deprecated_name='api_class', |
26 | 26 | deprecated_group='key_manager', |
27 | help='Specify the key manager implementation. Default is ' | |
28 | '"barbican".Will support the values earlier set using ' | |
27 | help='Specify the key manager implementation. Options are ' | |
28 | '"barbican" and "vault". Default is "barbican". Will ' | |
29 | 'support the values earlier set using ' | |
29 | 30 | '[key_manager]/api_class for some time.'), |
30 | 31 | ] |
31 | 32 |
0 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | |
1 | # not use this file except in compliance with the License. You may obtain | |
2 | # a copy of the License at | |
3 | # | |
4 | # http://www.apache.org/licenses/LICENSE-2.0 | |
5 | # | |
6 | # Unless required by applicable law or agreed to in writing, software | |
7 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
8 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
9 | # License for the specific language governing permissions and limitations | |
10 | # under the License. | |
11 | ||
12 | """ | |
13 | Key manager implementation for Vault | |
14 | """ | |
15 | ||
16 | import binascii | |
17 | import os | |
18 | import time | |
19 | import uuid | |
20 | ||
21 | from keystoneauth1 import loading | |
22 | from oslo_config import cfg | |
23 | from oslo_log import log as logging | |
24 | import requests | |
25 | import six | |
26 | ||
27 | from castellan.common import exception | |
28 | from castellan.common.objects import opaque_data as op_data | |
29 | from castellan.common.objects import passphrase | |
30 | from castellan.common.objects import private_key as pri_key | |
31 | from castellan.common.objects import public_key as pub_key | |
32 | from castellan.common.objects import symmetric_key as sym_key | |
33 | from castellan.common.objects import x_509 | |
34 | from castellan.i18n import _ | |
35 | from castellan.key_manager import key_manager | |
36 | ||
37 | DEFAULT_VAULT_URL = "http://127.0.0.1:8200" | |
38 | ||
39 | vault_opts = [ | |
40 | cfg.StrOpt('root_token_id', | |
41 | help='root token for vault'), | |
42 | cfg.StrOpt('vault_url', | |
43 | default=DEFAULT_VAULT_URL, | |
44 | help='Use this endpoint to connect to Vault, for example: ' | |
45 | '"%s"' % DEFAULT_VAULT_URL), | |
46 | cfg.StrOpt('ssl_ca_crt_file', | |
47 | help='Absolute path to ca cert file'), | |
48 | cfg.BoolOpt('use_ssl', | |
49 | default=False, | |
50 | help=_('SSL Enabled/Disabled')), | |
51 | ] | |
52 | ||
53 | VAULT_OPT_GROUP = 'vault' | |
54 | ||
55 | _EXCEPTIONS_BY_CODE = [ | |
56 | requests.codes['internal_server_error'], | |
57 | requests.codes['service_unavailable'], | |
58 | requests.codes['request_timeout'], | |
59 | requests.codes['gateway_timeout'], | |
60 | requests.codes['precondition_failed'], | |
61 | ] | |
62 | ||
63 | LOG = logging.getLogger(__name__) | |
64 | ||
65 | ||
66 | class VaultKeyManager(key_manager.KeyManager): | |
67 | """Key Manager Interface that wraps the Vault REST API.""" | |
68 | ||
69 | _secret_type_dict = { | |
70 | op_data.OpaqueData: 'opaque', | |
71 | passphrase.Passphrase: 'passphrase', | |
72 | pri_key.PrivateKey: 'private', | |
73 | pub_key.PublicKey: 'public', | |
74 | sym_key.SymmetricKey: 'symmetric', | |
75 | x_509.X509: 'certificate'} | |
76 | ||
77 | def __init__(self, configuration): | |
78 | self._conf = configuration | |
79 | self._conf.register_opts(vault_opts, group=VAULT_OPT_GROUP) | |
80 | loading.register_session_conf_options(self._conf, VAULT_OPT_GROUP) | |
81 | self._session = requests.Session() | |
82 | self._root_token_id = self._conf.vault.root_token_id | |
83 | self._vault_url = self._conf.vault.vault_url | |
84 | if self._vault_url.startswith("https://"): | |
85 | self._verify_server = self._conf.vault.ssl_ca_crt_file or True | |
86 | else: | |
87 | self._verify_server = False | |
88 | ||
89 | def _get_url(self): | |
90 | if not self._vault_url.endswith('/'): | |
91 | self._vault_url += '/' | |
92 | return self._vault_url | |
93 | ||
94 | def create_key_pair(self, context, algorithm, length, | |
95 | expiration=None, name=None): | |
96 | """Creates an asymmetric key pair.""" | |
97 | raise NotImplementedError( | |
98 | "VaultKeyManager does not support asymmetric keys") | |
99 | ||
100 | def _store_key_value(self, key_id, value): | |
101 | ||
102 | type_value = self._secret_type_dict.get(type(value)) | |
103 | if type_value is None: | |
104 | raise exception.KeyManagerError( | |
105 | "Unknown type for value : %r" % value) | |
106 | ||
107 | headers = {'X-Vault-Token': self._root_token_id} | |
108 | try: | |
109 | resource_url = self._get_url() + 'v1/secret/' + key_id | |
110 | record = { | |
111 | 'type': type_value, | |
112 | 'value': binascii.hexlify(value.get_encoded()).decode('utf-8'), | |
113 | 'algorithm': (value.algorithm if hasattr(value, 'algorithm') | |
114 | else None), | |
115 | 'bit_length': (value.bit_length if hasattr(value, 'bit_length') | |
116 | else None), | |
117 | 'name': value.name, | |
118 | 'created': value.created | |
119 | } | |
120 | resp = self._session.post(resource_url, | |
121 | verify=self._verify_server, | |
122 | json=record, | |
123 | headers=headers) | |
124 | except requests.exceptions.Timeout as ex: | |
125 | raise exception.KeyManagerError(six.text_type(ex)) | |
126 | except requests.exceptions.ConnectionError as ex: | |
127 | raise exception.KeyManagerError(six.text_type(ex)) | |
128 | except Exception as ex: | |
129 | raise exception.KeyManagerError(six.text_type(ex)) | |
130 | ||
131 | if resp.status_code in _EXCEPTIONS_BY_CODE: | |
132 | raise exception.KeyManagerError(resp.reason) | |
133 | if resp.status_code == requests.codes['forbidden']: | |
134 | raise exception.Forbidden() | |
135 | ||
136 | return key_id | |
137 | ||
138 | def create_key(self, context, algorithm, length, name=None, **kwargs): | |
139 | """Creates a symmetric key.""" | |
140 | ||
141 | # Confirm context is provided, if not raise forbidden | |
142 | if not context: | |
143 | msg = _("User is not authorized to use key manager.") | |
144 | raise exception.Forbidden(msg) | |
145 | ||
146 | key_id = uuid.uuid4().hex | |
147 | key_value = os.urandom(length or 32) | |
148 | key = sym_key.SymmetricKey(algorithm, | |
149 | length or 32, | |
150 | key_value, | |
151 | key_id, | |
152 | name or int(time.time())) | |
153 | return self._store_key_value(key_id, key) | |
154 | ||
155 | def store(self, context, key_value, **kwargs): | |
156 | """Stores (i.e., registers) a key with the key manager.""" | |
157 | ||
158 | # Confirm context is provided, if not raise forbidden | |
159 | if not context: | |
160 | msg = _("User is not authorized to use key manager.") | |
161 | raise exception.Forbidden(msg) | |
162 | ||
163 | key_id = uuid.uuid4().hex | |
164 | return self._store_key_value(key_id, key_value) | |
165 | ||
166 | def get(self, context, key_id, metadata_only=False): | |
167 | """Retrieves the key identified by the specified id.""" | |
168 | ||
169 | # Confirm context is provided, if not raise forbidden | |
170 | if not context: | |
171 | msg = _("User is not authorized to use key manager.") | |
172 | raise exception.Forbidden(msg) | |
173 | ||
174 | if not key_id: | |
175 | raise exception.KeyManagerError('key identifier not provided') | |
176 | ||
177 | headers = {'X-Vault-Token': self._root_token_id} | |
178 | try: | |
179 | resource_url = self._get_url() + 'v1/secret/' + key_id | |
180 | resp = self._session.get(resource_url, | |
181 | verify=self._verify_server, | |
182 | headers=headers) | |
183 | except requests.exceptions.Timeout as ex: | |
184 | raise exception.KeyManagerError(six.text_type(ex)) | |
185 | except requests.exceptions.ConnectionError as ex: | |
186 | raise exception.KeyManagerError(six.text_type(ex)) | |
187 | except Exception as ex: | |
188 | raise exception.KeyManagerError(six.text_type(ex)) | |
189 | ||
190 | if resp.status_code in _EXCEPTIONS_BY_CODE: | |
191 | raise exception.KeyManagerError(resp.reason) | |
192 | if resp.status_code == requests.codes['forbidden']: | |
193 | raise exception.Forbidden() | |
194 | if resp.status_code == requests.codes['not_found']: | |
195 | raise exception.ManagedObjectNotFoundError(uuid=key_id) | |
196 | ||
197 | record = resp.json()['data'] | |
198 | key = None if metadata_only else binascii.unhexlify(record['value']) | |
199 | ||
200 | clazz = None | |
201 | for type_clazz, type_name in self._secret_type_dict.items(): | |
202 | if type_name == record['type']: | |
203 | clazz = type_clazz | |
204 | ||
205 | if clazz is None: | |
206 | raise exception.KeyManagerError( | |
207 | "Unknown type : %r" % record['type']) | |
208 | ||
209 | if hasattr(clazz, 'algorithm') and hasattr(clazz, 'bit_length'): | |
210 | return clazz(record['algorithm'], | |
211 | record['bit_length'], | |
212 | key, | |
213 | record['name'], | |
214 | record['created'], | |
215 | key_id) | |
216 | else: | |
217 | return clazz(key, | |
218 | record['name'], | |
219 | record['created'], | |
220 | key_id) | |
221 | ||
222 | def delete(self, context, key_id): | |
223 | """Represents deleting the key.""" | |
224 | ||
225 | # Confirm context is provided, if not raise forbidden | |
226 | if not context: | |
227 | msg = _("User is not authorized to use key manager.") | |
228 | raise exception.Forbidden(msg) | |
229 | ||
230 | if not key_id: | |
231 | raise exception.KeyManagerError('key identifier not provided') | |
232 | ||
233 | headers = {'X-Vault-Token': self._root_token_id} | |
234 | try: | |
235 | resource_url = self._get_url() + 'v1/secret/' + key_id | |
236 | resp = self._session.delete(resource_url, | |
237 | verify=self._verify_server, | |
238 | headers=headers) | |
239 | except requests.exceptions.Timeout as ex: | |
240 | raise exception.KeyManagerError(six.text_type(ex)) | |
241 | except requests.exceptions.ConnectionError as ex: | |
242 | raise exception.KeyManagerError(six.text_type(ex)) | |
243 | except Exception as ex: | |
244 | raise exception.KeyManagerError(six.text_type(ex)) | |
245 | ||
246 | if resp.status_code in _EXCEPTIONS_BY_CODE: | |
247 | raise exception.KeyManagerError(resp.reason) | |
248 | if resp.status_code == requests.codes['forbidden']: | |
249 | raise exception.Forbidden() | |
250 | if resp.status_code == requests.codes['not_found']: | |
251 | raise exception.ManagedObjectNotFoundError(uuid=key_id) | |
252 | ||
253 | def list(self, context, object_type=None, metadata_only=False): | |
254 | """Lists the managed objects given the criteria.""" | |
255 | ||
256 | # Confirm context is provided, if not raise forbidden | |
257 | if not context: | |
258 | msg = _("User is not authorized to use key manager.") | |
259 | raise exception.Forbidden(msg) | |
260 | ||
261 | if object_type and object_type not in self._secret_type_dict: | |
262 | msg = _("Invalid secret type: %s") % object_type | |
263 | raise exception.KeyManagerError(reason=msg) | |
264 | ||
265 | headers = {'X-Vault-Token': self._root_token_id} | |
266 | try: | |
267 | resource_url = self._get_url() + 'v1/secret/?list=true' | |
268 | resp = self._session.get(resource_url, | |
269 | verify=self._verify_server, | |
270 | headers=headers) | |
271 | keys = resp.json()['data']['keys'] | |
272 | except requests.exceptions.Timeout as ex: | |
273 | raise exception.KeyManagerError(six.text_type(ex)) | |
274 | except requests.exceptions.ConnectionError as ex: | |
275 | raise exception.KeyManagerError(six.text_type(ex)) | |
276 | except Exception as ex: | |
277 | raise exception.KeyManagerError(six.text_type(ex)) | |
278 | ||
279 | if resp.status_code in _EXCEPTIONS_BY_CODE: | |
280 | raise exception.KeyManagerError(resp.reason) | |
281 | if resp.status_code == requests.codes['forbidden']: | |
282 | raise exception.Forbidden() | |
283 | if resp.status_code == requests.codes['not_found']: | |
284 | keys = [] | |
285 | ||
286 | objects = [] | |
287 | for obj_id in keys: | |
288 | try: | |
289 | obj = self.get(context, obj_id, metadata_only=metadata_only) | |
290 | if object_type is None or isinstance(obj, object_type): | |
291 | objects.append(obj) | |
292 | except exception.ManagedObjectNotFoundError as e: | |
293 | LOG.warning(_("Error occurred while retrieving object " | |
294 | "metadata, not adding it to the list: %s"), e) | |
295 | pass | |
296 | return objects |
19 | 19 | from castellan.key_manager import barbican_key_manager as bkm |
20 | 20 | except ImportError: |
21 | 21 | bkm = None |
22 | ||
23 | try: | |
24 | from castellan.key_manager import vault_key_manager as vkm | |
25 | except ImportError: | |
26 | vkm = None | |
27 | ||
22 | 28 | from castellan.common import utils |
23 | 29 | |
24 | 30 | _DEFAULT_LOG_LEVELS = ['castellan=WARN'] |
32 | 38 | def set_defaults(conf, backend=None, barbican_endpoint=None, |
33 | 39 | barbican_api_version=None, auth_endpoint=None, |
34 | 40 | retry_delay=None, number_of_retries=None, verify_ssl=None, |
35 | api_class=None): | |
41 | api_class=None, vault_root_token_id=None, vault_url=None, | |
42 | vault_ssl_ca_crt_file=None, vault_use_ssl=None): | |
36 | 43 | """Set defaults for configuration values. |
37 | 44 | |
38 | 45 | Overrides the default options values. |
44 | 51 | :param retry_delay: Use this attribute to set retry delay. |
45 | 52 | :param number_of_retries: Use this attribute to set number of retries. |
46 | 53 | :param verify_ssl: Use this to specify if ssl should be verified. |
54 | :param vault_root_token_id: Use this for the root token id for vault. | |
55 | :param vault_url: Use this for the url for vault. | |
56 | :param vault_use_ssl: Use this to force vault driver to use ssl. | |
57 | :param vault_ssl_ca_crt_file: Use this for the CA file for vault. | |
47 | 58 | """ |
48 | 59 | conf.register_opts(km.key_manager_opts, group='key_manager') |
49 | 60 | if bkm: |
50 | 61 | conf.register_opts(bkm.barbican_opts, group=bkm.BARBICAN_OPT_GROUP) |
62 | if vkm: | |
63 | conf.register_opts(vkm.vault_opts, group=vkm.VAULT_OPT_GROUP) | |
51 | 64 | |
52 | 65 | # Use the new backend option if set or fall back to the older api_class |
53 | 66 | default_backend = backend or api_class |
73 | 86 | if verify_ssl is not None: |
74 | 87 | conf.set_default('verify_ssl', verify_ssl, |
75 | 88 | group=bkm.BARBICAN_OPT_GROUP) |
89 | ||
90 | if vkm is not None: | |
91 | if vault_root_token_id is not None: | |
92 | conf.set_default('root_token_id', vault_root_token_id, | |
93 | group=vkm.VAULT_OPT_GROUP) | |
94 | if vault_url is not None: | |
95 | conf.set_default('vault_url', vault_url, | |
96 | group=vkm.VAULT_OPT_GROUP) | |
97 | if vault_ssl_ca_crt_file is not None: | |
98 | conf.set_default('ssl_ca_crt_file', vault_ssl_ca_crt_file, | |
99 | group=vkm.VAULT_OPT_GROUP) | |
100 | if vault_use_ssl is not None: | |
101 | conf.set_default('use_ssl', vault_use_ssl, | |
102 | group=vkm.VAULT_OPT_GROUP) | |
76 | 103 | |
77 | 104 | |
78 | 105 | def enable_logging(conf=None, app_name='castellan'): |
108 | 135 | |
109 | 136 | if bkm is not None: |
110 | 137 | opts.append((bkm.BARBICAN_OPT_GROUP, bkm.barbican_opts)) |
138 | if vkm is not None: | |
139 | opts.append((vkm.VAULT_OPT_GROUP, vkm.vault_opts)) | |
111 | 140 | return opts |
25 | 25 | from oslo_context import context |
26 | 26 | from oslo_utils import uuidutils |
27 | 27 | from oslotest import base |
28 | from testtools import testcase | |
28 | 29 | |
29 | 30 | from castellan.common.credentials import keystone_password |
30 | 31 | from castellan.common.credentials import keystone_token |
49 | 50 | |
50 | 51 | def setUp(self): |
51 | 52 | super(BarbicanKeyManagerTestCase, self).setUp() |
52 | self.ctxt = self.get_context() | |
53 | try: | |
54 | self.ctxt = self.get_context() | |
55 | self.key_mgr._get_barbican_client(self.ctxt) | |
56 | except Exception as e: | |
57 | # When we run functional-vault target, This test class needs | |
58 | # to be skipped as barbican is not running | |
59 | raise testcase.TestSkipped(str(e)) | |
53 | 60 | |
54 | 61 | def tearDown(self): |
55 | 62 | super(BarbicanKeyManagerTestCase, self).tearDown() |
0 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | |
1 | # not use this file except in compliance with the License. You may obtain | |
2 | # a copy of the License at | |
3 | # | |
4 | # http://www.apache.org/licenses/LICENSE-2.0 | |
5 | # | |
6 | # Unless required by applicable law or agreed to in writing, software | |
7 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
8 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
9 | # License for the specific language governing permissions and limitations | |
10 | # under the License. | |
11 | ||
12 | """ | |
13 | Functional test cases for the Vault key manager. | |
14 | ||
15 | Note: This requires local running instance of Vault. | |
16 | """ | |
17 | import abc | |
18 | import os | |
19 | ||
20 | from oslo_config import cfg | |
21 | from oslo_context import context | |
22 | from oslo_utils import uuidutils | |
23 | from oslotest import base | |
24 | from testtools import testcase | |
25 | ||
26 | from castellan.common import exception | |
27 | from castellan.key_manager import vault_key_manager | |
28 | from castellan.tests.functional import config | |
29 | from castellan.tests.functional.key_manager import test_key_manager | |
30 | ||
31 | CONF = config.get_config() | |
32 | ||
33 | ||
34 | class VaultKeyManagerTestCase(test_key_manager.KeyManagerTestCase): | |
35 | def _create_key_manager(self): | |
36 | key_mgr = vault_key_manager.VaultKeyManager(cfg.CONF) | |
37 | ||
38 | if ('VAULT_TEST_URL' not in os.environ or | |
39 | 'VAULT_TEST_ROOT_TOKEN' not in os.environ): | |
40 | raise testcase.TestSkipped('Missing Vault setup information') | |
41 | ||
42 | key_mgr._root_token_id = os.environ['VAULT_TEST_ROOT_TOKEN'] | |
43 | key_mgr._vault_url = os.environ['VAULT_TEST_URL'] | |
44 | return key_mgr | |
45 | ||
46 | @abc.abstractmethod | |
47 | def get_context(self): | |
48 | """Retrieves Context for Authentication""" | |
49 | return | |
50 | ||
51 | def setUp(self): | |
52 | super(VaultKeyManagerTestCase, self).setUp() | |
53 | self.ctxt = self.get_context() | |
54 | ||
55 | def tearDown(self): | |
56 | super(VaultKeyManagerTestCase, self).tearDown() | |
57 | ||
58 | def test_create_key_pair(self): | |
59 | self.assertRaises(NotImplementedError, | |
60 | self.key_mgr.create_key_pair, None, None, None) | |
61 | ||
62 | def test_create_null_context(self): | |
63 | self.assertRaises(exception.Forbidden, | |
64 | self.key_mgr.create_key, None, 'AES', 256) | |
65 | ||
66 | def test_create_key_pair_null_context(self): | |
67 | self.assertRaises(NotImplementedError, | |
68 | self.key_mgr.create_key_pair, None, 'RSA', 2048) | |
69 | ||
70 | def test_delete_null_context(self): | |
71 | key_uuid = self._get_valid_object_uuid( | |
72 | test_key_manager._get_test_symmetric_key()) | |
73 | self.addCleanup(self.key_mgr.delete, self.ctxt, key_uuid) | |
74 | self.assertRaises(exception.Forbidden, | |
75 | self.key_mgr.delete, None, key_uuid) | |
76 | ||
77 | def test_delete_null_object(self): | |
78 | self.assertRaises(exception.KeyManagerError, | |
79 | self.key_mgr.delete, self.ctxt, None) | |
80 | ||
81 | def test_get_null_context(self): | |
82 | key_uuid = self._get_valid_object_uuid( | |
83 | test_key_manager._get_test_symmetric_key()) | |
84 | self.addCleanup(self.key_mgr.delete, self.ctxt, key_uuid) | |
85 | self.assertRaises(exception.Forbidden, | |
86 | self.key_mgr.get, None, key_uuid) | |
87 | ||
88 | def test_get_null_object(self): | |
89 | self.assertRaises(exception.KeyManagerError, | |
90 | self.key_mgr.get, self.ctxt, None) | |
91 | ||
92 | def test_get_unknown_key(self): | |
93 | bad_key_uuid = uuidutils.generate_uuid() | |
94 | self.assertRaises(exception.ManagedObjectNotFoundError, | |
95 | self.key_mgr.get, self.ctxt, bad_key_uuid) | |
96 | ||
97 | def test_store_null_context(self): | |
98 | key = test_key_manager._get_test_symmetric_key() | |
99 | ||
100 | self.assertRaises(exception.Forbidden, | |
101 | self.key_mgr.store, None, key) | |
102 | ||
103 | ||
104 | class VaultKeyManagerOSLOContextTestCase(VaultKeyManagerTestCase, | |
105 | base.BaseTestCase): | |
106 | def get_context(self): | |
107 | return context.get_admin_context() |
28 | 28 | |
29 | 29 | castellan.drivers = |
30 | 30 | barbican = castellan.key_manager.barbican_key_manager:BarbicanKeyManager |
31 | vault = castellan.key_manager.vault_key_manager:VaultKeyManager | |
31 | 32 | |
32 | 33 | [build_sphinx] |
33 | 34 | source-dir = doc/source |
0 | #!/bin/bash | |
1 | set -eux | |
2 | if [ -z "$(which vault)" ]; then | |
3 | VAULT_VERSION=0.7.3 | |
4 | SUFFIX=zip | |
5 | case `uname -s` in | |
6 | Darwin) | |
7 | OS=darwin | |
8 | ;; | |
9 | Linux) | |
10 | OS=linux | |
11 | ;; | |
12 | *) | |
13 | echo "Unsupported OS" | |
14 | exit 1 | |
15 | esac | |
16 | case `uname -m` in | |
17 | x86_64) | |
18 | MACHINE=amd64 | |
19 | ;; | |
20 | *) | |
21 | echo "Unsupported machine" | |
22 | exit 1 | |
23 | esac | |
24 | TARBALL_NAME=vault_${VAULT_VERSION}_${OS}_${MACHINE} | |
25 | test ! -d "$TARBALL_NAME" && mkdir ${TARBALL_NAME} && wget https://releases.hashicorp.com/vault/${VAULT_VERSION}/${TARBALL_NAME}.${SUFFIX} && unzip -d ${TARBALL_NAME} ${TARBALL_NAME}.${SUFFIX} && rm ${TARBALL_NAME}.${SUFFIX} | |
26 | export VAULT_CONFIG_PATH=$(pwd)/$TARBALL_NAME/vault.json | |
27 | export PATH=$PATH:$(pwd)/$TARBALL_NAME | |
28 | fi | |
29 | ||
30 | $* |
56 | 56 | -r{toxinidir}/test-requirements.txt |
57 | 57 | commands = python setup.py testr --slowest --testr-args='{posargs}' |
58 | 58 | |
59 | [testenv:functional-vault] | |
60 | passenv = HOME | |
61 | usedevelop = True | |
62 | install_command = pip install -U {opts} {packages} | |
63 | setenv = | |
64 | VIRTUAL_ENV={envdir} | |
65 | OS_TEST_PATH=./castellan/tests/functional | |
66 | deps = -r{toxinidir}/requirements.txt | |
67 | -r{toxinidir}/test-requirements.txt | |
68 | commands = | |
69 | {toxinidir}/tools/setup-vault-env.sh pifpaf -e VAULT_TEST run vault -- python setup.py testr --slowest --testr-args='{posargs}' | |
70 | ||
59 | 71 | [testenv:genconfig] |
60 | 72 | commands = |
61 | 73 | oslo-config-generator --config-file=etc/castellan/functional-config-generator.conf |