Merge tag '3.0.0' into debian/ussuri
castellan 3.0.0 release
meta:version: 3.0.0
meta:diff-start: -
meta:series: ussuri
meta:release-type: release
meta:pypi: no
meta:first: no
meta:release:Author: Ben Nemec <bnemec@redhat.com>
meta:release:Commit: Ben Nemec <bnemec@redhat.com>
meta:release:Change-Id: I9ad3b891c09dab47e38de4d77dcea6d3b1516bf8
meta:release:Code-Review+2: Hervé Beraud <hberaud@redhat.com>
meta:release:Code-Review+2: Sean McGinnis <sean.mcginnis@gmail.com>
meta:release:Workflow+1: Sean McGinnis <sean.mcginnis@gmail.com>
Thomas Goirand
3 years ago
59 | 59 | templates: |
60 | 60 | - check-requirements |
61 | 61 | - openstack-lower-constraints-jobs |
62 | - openstack-python-jobs | |
63 | - openstack-python3-train-jobs | |
62 | - openstack-python3-ussuri-jobs | |
64 | 63 | - periodic-stable-jobs |
65 | 64 | - publish-openstack-docs-pti |
66 | 65 | - release-notes-jobs-python3 |
8 | 8 | * Source: https://opendev.org/openstack/castellan |
9 | 9 | * Bugs: https://bugs.launchpad.net/castellan |
10 | 10 | * Release notes: https://docs.openstack.org/releasenotes/castellan |
11 | * Wiki: https://wiki.openstack.org/wiki/Castellan | |
11 | 12 | |
12 | 13 | Team and repository tags |
13 | 14 | ======================== |
22 | 22 | |
23 | 23 | import abc |
24 | 24 | |
25 | import six | |
26 | 25 | |
27 | ||
28 | @six.add_metaclass(abc.ABCMeta) | |
29 | class Credential(object): | |
26 | class Credential(object, metaclass=abc.ABCMeta): | |
30 | 27 | """Base class to represent all credentials.""" |
31 | 28 | |
32 | 29 | def __init__(self): |
16 | 16 | Castellan exception subclasses |
17 | 17 | """ |
18 | 18 | |
19 | import six.moves.urllib.parse as urlparse | |
19 | import urllib | |
20 | 20 | |
21 | 21 | from castellan.i18n import _ |
22 | 22 | |
25 | 25 | |
26 | 26 | class RedirectException(Exception): |
27 | 27 | def __init__(self, url): |
28 | self.url = urlparse.urlparse(url) | |
28 | self.url = urllib.parse.urlparse(url) | |
29 | 29 | |
30 | 30 | |
31 | 31 | class CastellanException(Exception): |
18 | 18 | This module defines the Certificate class. |
19 | 19 | """ |
20 | 20 | |
21 | import abc | |
22 | ||
23 | import six | |
24 | ||
25 | 21 | from castellan.common.objects import managed_object |
26 | 22 | |
27 | 23 | |
28 | @six.add_metaclass(abc.ABCMeta) | |
29 | 24 | class Certificate(managed_object.ManagedObject): |
30 | 25 | """Base class to represent all certificates.""" |
20 | 20 | from Java. |
21 | 21 | """ |
22 | 22 | |
23 | import abc | |
24 | ||
23 | 25 | from castellan.common.objects import managed_object |
24 | 26 | |
25 | import abc | |
26 | 27 | |
27 | import six | |
28 | ||
29 | ||
30 | @six.add_metaclass(abc.ABCMeta) | |
31 | 28 | class Key(managed_object.ManagedObject): |
32 | 29 | """Base class to represent all keys.""" |
33 | 30 |
20 | 20 | """ |
21 | 21 | import abc |
22 | 22 | |
23 | import six | |
24 | 23 | |
25 | ||
26 | @six.add_metaclass(abc.ABCMeta) | |
27 | class ManagedObject(object): | |
24 | class ManagedObject(object, metaclass=abc.ABCMeta): | |
28 | 25 | """Base class to represent all managed objects.""" |
29 | 26 | |
30 | 27 | def __init__(self, name=None, created=None, id=None): |
17 | 17 | """ |
18 | 18 | import calendar |
19 | 19 | import time |
20 | import urllib | |
20 | 21 | |
21 | 22 | from cryptography.hazmat import backends |
22 | 23 | from cryptography.hazmat.primitives import serialization |
31 | 32 | from castellan.common import exception |
32 | 33 | from castellan.common.objects import key as key_base_class |
33 | 34 | from castellan.common.objects import opaque_data as op_data |
34 | from castellan.common.objects import passphrase | |
35 | from castellan.common.objects import private_key as pri_key | |
36 | from castellan.common.objects import public_key as pub_key | |
37 | from castellan.common.objects import symmetric_key as sym_key | |
38 | from castellan.common.objects import x_509 | |
39 | 35 | from castellan.i18n import _ |
40 | 36 | from castellan.key_manager import key_manager |
41 | 37 | |
43 | 39 | from barbicanclient import client as barbican_client_import |
44 | 40 | from barbicanclient import exceptions as barbican_exceptions |
45 | 41 | from oslo_utils import timeutils |
46 | from six.moves import urllib | |
47 | ||
48 | ||
49 | barbican_opts = [ | |
42 | ||
43 | ||
44 | _barbican_opts = [ | |
50 | 45 | cfg.StrOpt('barbican_endpoint', |
51 | 46 | help='Use this endpoint to connect to Barbican, for example: ' |
52 | 47 | '"http://localhost:9311/"'), |
77 | 72 | |
78 | 73 | ] |
79 | 74 | |
80 | BARBICAN_OPT_GROUP = 'barbican' | |
75 | _BARBICAN_OPT_GROUP = 'barbican' | |
81 | 76 | |
82 | 77 | LOG = logging.getLogger(__name__) |
83 | 78 | |
84 | 79 | |
85 | 80 | class BarbicanKeyManager(key_manager.KeyManager): |
86 | 81 | """Key Manager Interface that wraps the Barbican client API.""" |
87 | ||
88 | _secret_type_dict = { | |
89 | op_data.OpaqueData: 'opaque', | |
90 | passphrase.Passphrase: 'passphrase', | |
91 | pri_key.PrivateKey: 'private', | |
92 | pub_key.PublicKey: 'public', | |
93 | sym_key.SymmetricKey: 'symmetric', | |
94 | x_509.X509: 'certificate'} | |
95 | 82 | |
96 | 83 | def __init__(self, configuration): |
97 | 84 | self._barbican_client = None |
98 | 85 | self._base_url = None |
99 | 86 | self.conf = configuration |
100 | self.conf.register_opts(barbican_opts, group=BARBICAN_OPT_GROUP) | |
101 | loading.register_session_conf_options(self.conf, BARBICAN_OPT_GROUP) | |
87 | self.conf.register_opts(_barbican_opts, group=_BARBICAN_OPT_GROUP) | |
88 | loading.register_session_conf_options(self.conf, _BARBICAN_OPT_GROUP) | |
102 | 89 | |
103 | 90 | def _get_barbican_client(self, context): |
104 | 91 | """Creates a client to connect to the Barbican service. |
143 | 130 | return self._barbican_client |
144 | 131 | |
145 | 132 | def _get_keystone_auth(self, context): |
146 | if context.__class__.__name__ is 'KeystonePassword': | |
133 | if context.__class__.__name__ == 'KeystonePassword': | |
147 | 134 | return identity.Password( |
148 | 135 | auth_url=context.auth_url, |
149 | 136 | username=context.username, |
159 | 146 | project_domain_id=context.project_domain_id, |
160 | 147 | project_domain_name=context.project_domain_name, |
161 | 148 | reauthenticate=context.reauthenticate) |
162 | elif context.__class__.__name__ is 'KeystoneToken': | |
149 | elif context.__class__.__name__ == 'KeystoneToken': | |
163 | 150 | return identity.Token( |
164 | 151 | auth_url=context.auth_url, |
165 | 152 | token=context.token, |
173 | 160 | reauthenticate=context.reauthenticate) |
174 | 161 | # this will be kept for oslo.context compatibility until |
175 | 162 | # projects begin to use utils.credential_factory |
176 | elif context.__class__.__name__ is 'RequestContext': | |
177 | return identity.Token( | |
178 | auth_url=self.conf.barbican.auth_endpoint, | |
179 | token=context.auth_token, | |
180 | project_id=context.project_id, | |
181 | project_name=context.project_name, | |
182 | project_domain_id=context.project_domain_id, | |
183 | project_domain_name=context.project_domain_name) | |
163 | elif context.__class__.__name__ == 'RequestContext': | |
164 | if getattr(context, 'get_auth_plugin', None): | |
165 | return context.get_auth_plugin() | |
166 | else: | |
167 | return identity.Token( | |
168 | auth_url=self.conf.barbican.auth_endpoint, | |
169 | token=context.auth_token, | |
170 | project_id=context.project_id, | |
171 | project_name=context.project_name, | |
172 | project_domain_id=context.project_domain_id, | |
173 | project_domain_name=context.project_domain_name) | |
184 | 174 | else: |
185 | 175 | msg = _("context must be of type KeystonePassword, " |
186 | 176 | "KeystoneToken, or RequestContext.") |
191 | 181 | barbican = self.conf.barbican |
192 | 182 | if barbican.barbican_endpoint: |
193 | 183 | return barbican.barbican_endpoint |
184 | elif getattr(auth, 'service_catalog', None): | |
185 | endpoint_data = auth.service_catalog.endpoint_data_for( | |
186 | service_type='key-manager') | |
187 | return endpoint_data.url | |
194 | 188 | else: |
195 | 189 | service_parameters = {'service_type': 'key-manager', |
196 | 190 | 'service_name': 'barbican', |
198 | 192 | return auth.get_endpoint(sess, **service_parameters) |
199 | 193 | |
200 | 194 | def _create_base_url(self, auth, sess, endpoint): |
195 | api_version = None | |
201 | 196 | if self.conf.barbican.barbican_api_version: |
202 | 197 | api_version = self.conf.barbican.barbican_api_version |
203 | else: | |
198 | elif getattr(auth, 'service_catalog', None): | |
199 | endpoint_data = auth.service_catalog.endpoint_data_for( | |
200 | service_type='key-manager') | |
201 | api_version = endpoint_data.api_version | |
202 | elif getattr(auth, 'get_discovery', None): | |
204 | 203 | discovery = auth.get_discovery(sess, url=endpoint) |
205 | 204 | raw_data = discovery.raw_version_data() |
206 | 205 | if len(raw_data) == 0: |
621 | 620 | except (barbican_exceptions.HTTPAuthError, |
622 | 621 | barbican_exceptions.HTTPClientError, |
623 | 622 | barbican_exceptions.HTTPServerError) as e: |
624 | LOG.error(_("Error listing objects: %s"), e) | |
623 | LOG.error("Error listing objects: %s", e) | |
625 | 624 | raise exception.KeyManagerError(reason=e) |
626 | 625 | |
627 | 626 | for secret in secrets: |
631 | 630 | except (barbican_exceptions.HTTPAuthError, |
632 | 631 | barbican_exceptions.HTTPClientError, |
633 | 632 | barbican_exceptions.HTTPServerError) as e: |
634 | LOG.warning(_("Error occurred while retrieving object " | |
635 | "metadata, not adding it to the list: %s"), e) | |
633 | LOG.warning("Error occurred while retrieving object " | |
634 | "metadata, not adding it to the list: %s", e) | |
636 | 635 | |
637 | 636 | return objects |
637 | ||
638 | def list_options_for_discovery(self): | |
639 | return [(_BARBICAN_OPT_GROUP, _barbican_opts)] |
18 | 18 | |
19 | 19 | import abc |
20 | 20 | |
21 | import six | |
21 | from castellan.common.objects import opaque_data as op_data | |
22 | from castellan.common.objects import passphrase | |
23 | from castellan.common.objects import private_key as pri_key | |
24 | from castellan.common.objects import public_key as pub_key | |
25 | from castellan.common.objects import symmetric_key as sym_key | |
26 | from castellan.common.objects import x_509 | |
22 | 27 | |
23 | 28 | |
24 | @six.add_metaclass(abc.ABCMeta) | |
25 | class KeyManager(object): | |
29 | class KeyManager(object, metaclass=abc.ABCMeta): | |
26 | 30 | """Base Key Manager Interface |
27 | 31 | |
28 | 32 | A Key Manager is responsible for managing encryption keys for volumes. A |
29 | 33 | Key Manager is responsible for creating, reading, and deleting keys. |
30 | 34 | """ |
35 | ||
36 | _secret_type_dict = { | |
37 | op_data.OpaqueData: "opaque", | |
38 | passphrase.Passphrase: "passphrase", | |
39 | pri_key.PrivateKey: "private", | |
40 | pub_key.PublicKey: "public", | |
41 | sym_key.SymmetricKey: "symmetric", | |
42 | x_509.X509: "certificate"} | |
31 | 43 | |
32 | 44 | @abc.abstractmethod |
33 | 45 | def __init__(self, configuration): |
122 | 134 | found, an empty list should be returned instead. |
123 | 135 | """ |
124 | 136 | return [] |
137 | ||
138 | def list_options_for_discovery(self): | |
139 | """Lists the KeyManager's configure options. | |
140 | ||
141 | KeyManagers should advertise all supported options through this | |
142 | method for the purpose of sample generation by oslo-config-generator. | |
143 | Each item in the advertised list should be tuple composed by the group | |
144 | name and a list of options belonging to that group. None should be used | |
145 | as the group name for the DEFAULT group. | |
146 | ||
147 | :returns: the list of supported options of a KeyManager. | |
148 | """ | |
149 | return [] |
30 | 30 | from oslo_log import log as logging |
31 | 31 | from oslo_utils import timeutils |
32 | 32 | import requests |
33 | import six | |
34 | 33 | |
35 | 34 | from castellan.common import exception |
36 | from castellan.common.objects import opaque_data as op_data | |
37 | from castellan.common.objects import passphrase | |
38 | 35 | from castellan.common.objects import private_key as pri_key |
39 | 36 | from castellan.common.objects import public_key as pub_key |
40 | 37 | from castellan.common.objects import symmetric_key as sym_key |
41 | from castellan.common.objects import x_509 | |
42 | 38 | from castellan.i18n import _ |
43 | 39 | from castellan.key_manager import key_manager |
44 | 40 | |
45 | DEFAULT_VAULT_URL = "http://127.0.0.1:8200" | |
46 | DEFAULT_MOUNTPOINT = "secret" | |
47 | ||
48 | vault_opts = [ | |
41 | _DEFAULT_VAULT_URL = "http://127.0.0.1:8200" | |
42 | _DEFAULT_MOUNTPOINT = "secret" | |
43 | ||
44 | _vault_opts = [ | |
49 | 45 | cfg.StrOpt('root_token_id', |
50 | 46 | help='root token for vault'), |
51 | 47 | cfg.StrOpt('approle_role_id', |
53 | 49 | cfg.StrOpt('approle_secret_id', |
54 | 50 | help='AppRole secret_id for authentication with vault'), |
55 | 51 | cfg.StrOpt('kv_mountpoint', |
56 | default=DEFAULT_MOUNTPOINT, | |
52 | default=_DEFAULT_MOUNTPOINT, | |
57 | 53 | help='Mountpoint of KV store in Vault to use, for example: ' |
58 | '{}'.format(DEFAULT_MOUNTPOINT)), | |
54 | '{}'.format(_DEFAULT_MOUNTPOINT)), | |
59 | 55 | cfg.StrOpt('vault_url', |
60 | default=DEFAULT_VAULT_URL, | |
56 | default=_DEFAULT_VAULT_URL, | |
61 | 57 | help='Use this endpoint to connect to Vault, for example: ' |
62 | '"%s"' % DEFAULT_VAULT_URL), | |
58 | '"%s"' % _DEFAULT_VAULT_URL), | |
63 | 59 | cfg.StrOpt('ssl_ca_crt_file', |
64 | 60 | help='Absolute path to ca cert file'), |
65 | 61 | cfg.BoolOpt('use_ssl', |
67 | 63 | help=_('SSL Enabled/Disabled')), |
68 | 64 | ] |
69 | 65 | |
70 | VAULT_OPT_GROUP = 'vault' | |
66 | _VAULT_OPT_GROUP = 'vault' | |
71 | 67 | |
72 | 68 | _EXCEPTIONS_BY_CODE = [ |
73 | 69 | requests.codes['internal_server_error'], |
83 | 79 | class VaultKeyManager(key_manager.KeyManager): |
84 | 80 | """Key Manager Interface that wraps the Vault REST API.""" |
85 | 81 | |
86 | _secret_type_dict = { | |
87 | op_data.OpaqueData: 'opaque', | |
88 | passphrase.Passphrase: 'passphrase', | |
89 | pri_key.PrivateKey: 'private', | |
90 | pub_key.PublicKey: 'public', | |
91 | sym_key.SymmetricKey: 'symmetric', | |
92 | x_509.X509: 'certificate'} | |
93 | ||
94 | 82 | def __init__(self, configuration): |
95 | 83 | self._conf = configuration |
96 | self._conf.register_opts(vault_opts, group=VAULT_OPT_GROUP) | |
97 | loading.register_session_conf_options(self._conf, VAULT_OPT_GROUP) | |
84 | self._conf.register_opts(_vault_opts, group=_VAULT_OPT_GROUP) | |
85 | loading.register_session_conf_options(self._conf, _VAULT_OPT_GROUP) | |
98 | 86 | self._session = requests.Session() |
99 | 87 | self._root_token_id = self._conf.vault.root_token_id |
100 | 88 | self._approle_role_id = self._conf.vault.approle_role_id |
173 | 161 | json=params, |
174 | 162 | verify=self._verify_server) |
175 | 163 | except requests.exceptions.Timeout as ex: |
176 | raise exception.KeyManagerError(six.text_type(ex)) | |
164 | raise exception.KeyManagerError(str(ex)) | |
177 | 165 | except requests.exceptions.ConnectionError as ex: |
178 | raise exception.KeyManagerError(six.text_type(ex)) | |
166 | raise exception.KeyManagerError(str(ex)) | |
179 | 167 | except Exception as ex: |
180 | raise exception.KeyManagerError(six.text_type(ex)) | |
168 | raise exception.KeyManagerError(str(ex)) | |
181 | 169 | |
182 | 170 | if resp.status_code in _EXCEPTIONS_BY_CODE: |
183 | 171 | raise exception.KeyManagerError(resp.reason) |
199 | 187 | try: |
200 | 188 | resp = method(resource, headers=headers, json=json, verify=verify) |
201 | 189 | except requests.exceptions.Timeout as ex: |
202 | raise exception.KeyManagerError(six.text_type(ex)) | |
190 | raise exception.KeyManagerError(str(ex)) | |
203 | 191 | except requests.exceptions.ConnectionError as ex: |
204 | raise exception.KeyManagerError(six.text_type(ex)) | |
192 | raise exception.KeyManagerError(str(ex)) | |
205 | 193 | except Exception as ex: |
206 | raise exception.KeyManagerError(six.text_type(ex)) | |
194 | raise exception.KeyManagerError(str(ex)) | |
207 | 195 | |
208 | 196 | if resp.status_code in _EXCEPTIONS_BY_CODE: |
209 | 197 | raise exception.KeyManagerError(resp.reason) |
215 | 203 | def create_key_pair(self, context, algorithm, length, |
216 | 204 | expiration=None, name=None): |
217 | 205 | """Creates an asymmetric key pair.""" |
218 | ||
219 | # Confirm context is provided, if not raise forbidden | |
220 | if not context: | |
221 | msg = _("User is not authorized to use key manager.") | |
222 | raise exception.Forbidden(msg) | |
223 | 206 | |
224 | 207 | if algorithm.lower() != 'rsa': |
225 | 208 | raise NotImplementedError( |
292 | 275 | def create_key(self, context, algorithm, length, name=None, **kwargs): |
293 | 276 | """Creates a symmetric key.""" |
294 | 277 | |
295 | # Confirm context is provided, if not raise forbidden | |
296 | if not context: | |
297 | msg = _("User is not authorized to use key manager.") | |
298 | raise exception.Forbidden(msg) | |
299 | ||
300 | 278 | if length % 8: |
301 | 279 | msg = _("Length must be multiple of 8.") |
302 | 280 | raise ValueError(msg) |
314 | 292 | def store(self, context, key_value, **kwargs): |
315 | 293 | """Stores (i.e., registers) a key with the key manager.""" |
316 | 294 | |
317 | # Confirm context is provided, if not raise forbidden | |
318 | if not context: | |
319 | msg = _("User is not authorized to use key manager.") | |
320 | raise exception.Forbidden(msg) | |
321 | ||
322 | 295 | key_id = uuid.uuid4().hex |
323 | 296 | return self._store_key_value(key_id, key_value) |
324 | 297 | |
325 | 298 | def get(self, context, key_id, metadata_only=False): |
326 | 299 | """Retrieves the key identified by the specified id.""" |
327 | ||
328 | # Confirm context is provided, if not raise forbidden | |
329 | if not context: | |
330 | msg = _("User is not authorized to use key manager.") | |
331 | raise exception.Forbidden(msg) | |
332 | 300 | |
333 | 301 | if not key_id: |
334 | 302 | raise exception.KeyManagerError('key identifier not provided') |
370 | 338 | def delete(self, context, key_id): |
371 | 339 | """Represents deleting the key.""" |
372 | 340 | |
373 | # Confirm context is provided, if not raise forbidden | |
374 | if not context: | |
375 | msg = _("User is not authorized to use key manager.") | |
376 | raise exception.Forbidden(msg) | |
377 | ||
378 | 341 | if not key_id: |
379 | 342 | raise exception.KeyManagerError('key identifier not provided') |
380 | 343 | |
386 | 349 | |
387 | 350 | def list(self, context, object_type=None, metadata_only=False): |
388 | 351 | """Lists the managed objects given the criteria.""" |
389 | ||
390 | # Confirm context is provided, if not raise forbidden | |
391 | if not context: | |
392 | msg = _("User is not authorized to use key manager.") | |
393 | raise exception.Forbidden(msg) | |
394 | 352 | |
395 | 353 | if object_type and object_type not in self._secret_type_dict: |
396 | 354 | msg = _("Invalid secret type: %s") % object_type |
411 | 369 | if object_type is None or isinstance(obj, object_type): |
412 | 370 | objects.append(obj) |
413 | 371 | except exception.ManagedObjectNotFoundError as e: |
414 | LOG.warning(_("Error occurred while retrieving object " | |
415 | "metadata, not adding it to the list: %s"), e) | |
372 | LOG.warning("Error occurred while retrieving object " | |
373 | "metadata, not adding it to the list: %s", e) | |
416 | 374 | pass |
417 | 375 | return objects |
376 | ||
377 | def list_options_for_discovery(self): | |
378 | return [(_VAULT_OPT_GROUP, _vault_opts)] |
11 | 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
12 | 12 | # License for the specific language governing permissions and limitations |
13 | 13 | # under the License. |
14 | from stevedore import ExtensionManager | |
15 | ||
14 | 16 | from oslo_config import cfg |
15 | 17 | from oslo_log import log |
16 | 18 | |
17 | from castellan import key_manager as km | |
19 | from castellan import key_manager | |
18 | 20 | try: |
19 | 21 | from castellan.key_manager import barbican_key_manager as bkm |
20 | 22 | except ImportError: |
65 | 67 | :param barbican_endpoint_type: Use this to specify the type of URL. |
66 | 68 | : Valid values are: public, internal or admin. |
67 | 69 | """ |
68 | conf.register_opts(km.key_manager_opts, group='key_manager') | |
69 | if bkm: | |
70 | conf.register_opts(bkm.barbican_opts, group=bkm.BARBICAN_OPT_GROUP) | |
71 | if vkm: | |
72 | conf.register_opts(vkm.vault_opts, group=vkm.VAULT_OPT_GROUP) | |
70 | conf.register_opts(key_manager.key_manager_opts, group='key_manager') | |
71 | ||
72 | ext_mgr = ExtensionManager( | |
73 | "castellan.drivers", | |
74 | invoke_on_load=True, | |
75 | invoke_args=[cfg.CONF]) | |
76 | ||
77 | for km in ext_mgr.names(): | |
78 | for group, opts in ext_mgr[km].obj.list_options_for_discovery(): | |
79 | conf.register_opts(opts, group=group) | |
73 | 80 | |
74 | 81 | # Use the new backend option if set or fall back to the older api_class |
75 | 82 | default_backend = backend or api_class |
79 | 86 | if bkm is not None: |
80 | 87 | if barbican_endpoint is not None: |
81 | 88 | conf.set_default('barbican_endpoint', barbican_endpoint, |
82 | group=bkm.BARBICAN_OPT_GROUP) | |
89 | group=bkm._BARBICAN_OPT_GROUP) | |
83 | 90 | if barbican_api_version is not None: |
84 | 91 | conf.set_default('barbican_api_version', barbican_api_version, |
85 | group=bkm.BARBICAN_OPT_GROUP) | |
92 | group=bkm._BARBICAN_OPT_GROUP) | |
86 | 93 | if auth_endpoint is not None: |
87 | 94 | conf.set_default('auth_endpoint', auth_endpoint, |
88 | group=bkm.BARBICAN_OPT_GROUP) | |
95 | group=bkm._BARBICAN_OPT_GROUP) | |
89 | 96 | if retry_delay is not None: |
90 | 97 | conf.set_default('retry_delay', retry_delay, |
91 | group=bkm.BARBICAN_OPT_GROUP) | |
98 | group=bkm._BARBICAN_OPT_GROUP) | |
92 | 99 | if number_of_retries is not None: |
93 | 100 | conf.set_default('number_of_retries', number_of_retries, |
94 | group=bkm.BARBICAN_OPT_GROUP) | |
101 | group=bkm._BARBICAN_OPT_GROUP) | |
95 | 102 | if verify_ssl is not None: |
96 | 103 | conf.set_default('verify_ssl', verify_ssl, |
97 | group=bkm.BARBICAN_OPT_GROUP) | |
104 | group=bkm._BARBICAN_OPT_GROUP) | |
98 | 105 | if barbican_endpoint_type is not None: |
99 | 106 | conf.set_default('barbican_endpoint_type', barbican_endpoint_type, |
100 | group=bkm.BARBICAN_OPT_GROUP) | |
107 | group=bkm._BARBICAN_OPT_GROUP) | |
101 | 108 | |
102 | 109 | if vkm is not None: |
103 | 110 | if vault_root_token_id is not None: |
150 | 157 | :returns: a list of (group_name, opts) tuples |
151 | 158 | """ |
152 | 159 | key_manager_opts = [] |
153 | key_manager_opts.extend(km.key_manager_opts) | |
160 | key_manager_opts.extend(key_manager.key_manager_opts) | |
154 | 161 | key_manager_opts.extend(utils.credential_opts) |
155 | 162 | opts = [('key_manager', key_manager_opts)] |
156 | 163 | |
157 | if bkm is not None: | |
158 | opts.append((bkm.BARBICAN_OPT_GROUP, bkm.barbican_opts)) | |
159 | if vkm is not None: | |
160 | opts.append((vkm.VAULT_OPT_GROUP, vkm.vault_opts)) | |
164 | ext_mgr = ExtensionManager( | |
165 | "castellan.drivers", | |
166 | invoke_on_load=True, | |
167 | invoke_args=[cfg.CONF]) | |
168 | ||
169 | for driver in ext_mgr.names(): | |
170 | opts.extend(ext_mgr[driver].obj.list_options_for_discovery()) | |
171 | ||
161 | 172 | return opts |
76 | 76 | def setUp(self): |
77 | 77 | super(KeyManagerTestCase, self).setUp() |
78 | 78 | self.key_mgr = self._create_key_manager() |
79 | self.ctxt = None | |
79 | 80 | |
80 | 81 | def _get_valid_object_uuid(self, managed_object): |
81 | 82 | object_uuid = self.key_mgr.store(self.ctxt, managed_object) |
14 | 14 | |
15 | 15 | Note: This requires local running instance of Vault. |
16 | 16 | """ |
17 | import abc | |
18 | 17 | import os |
19 | 18 | import uuid |
20 | 19 | |
21 | 20 | from oslo_config import cfg |
22 | from oslo_context import context | |
23 | 21 | from oslo_utils import uuidutils |
24 | 22 | from oslotest import base |
25 | 23 | import requests |
33 | 31 | CONF = config.get_config() |
34 | 32 | |
35 | 33 | |
36 | class VaultKeyManagerTestCase(test_key_manager.KeyManagerTestCase): | |
34 | class VaultKeyManagerTestCase(test_key_manager.KeyManagerTestCase, | |
35 | base.BaseTestCase): | |
37 | 36 | def _create_key_manager(self): |
38 | 37 | key_mgr = vault_key_manager.VaultKeyManager(cfg.CONF) |
39 | 38 | |
45 | 44 | key_mgr._vault_url = os.environ['VAULT_TEST_URL'] |
46 | 45 | return key_mgr |
47 | 46 | |
48 | @abc.abstractmethod | |
49 | def get_context(self): | |
50 | """Retrieves Context for Authentication""" | |
51 | return | |
52 | ||
53 | def setUp(self): | |
54 | super(VaultKeyManagerTestCase, self).setUp() | |
55 | self.ctxt = self.get_context() | |
56 | ||
57 | def tearDown(self): | |
58 | super(VaultKeyManagerTestCase, self).tearDown() | |
59 | ||
60 | def test_create_null_context(self): | |
61 | self.assertRaises(exception.Forbidden, | |
62 | self.key_mgr.create_key, None, 'AES', 256) | |
63 | ||
64 | def test_create_key_pair_null_context(self): | |
65 | self.assertRaises(exception.Forbidden, | |
66 | self.key_mgr.create_key_pair, None, 'RSA', 2048) | |
67 | ||
68 | 47 | def test_create_key_pair_bad_algorithm(self): |
69 | 48 | self.assertRaises( |
70 | 49 | NotImplementedError, |
72 | 51 | self.ctxt, 'DSA', 2048 |
73 | 52 | ) |
74 | 53 | |
75 | def test_delete_null_context(self): | |
76 | key_uuid = self._get_valid_object_uuid( | |
77 | test_key_manager._get_test_symmetric_key()) | |
78 | self.addCleanup(self.key_mgr.delete, self.ctxt, key_uuid) | |
79 | self.assertRaises(exception.Forbidden, | |
80 | self.key_mgr.delete, None, key_uuid) | |
81 | ||
82 | 54 | def test_delete_null_object(self): |
83 | 55 | self.assertRaises(exception.KeyManagerError, |
84 | 56 | self.key_mgr.delete, self.ctxt, None) |
85 | ||
86 | def test_get_null_context(self): | |
87 | key_uuid = self._get_valid_object_uuid( | |
88 | test_key_manager._get_test_symmetric_key()) | |
89 | self.addCleanup(self.key_mgr.delete, self.ctxt, key_uuid) | |
90 | self.assertRaises(exception.Forbidden, | |
91 | self.key_mgr.get, None, key_uuid) | |
92 | 57 | |
93 | 58 | def test_get_null_object(self): |
94 | 59 | self.assertRaises(exception.KeyManagerError, |
98 | 63 | bad_key_uuid = uuidutils.generate_uuid() |
99 | 64 | self.assertRaises(exception.ManagedObjectNotFoundError, |
100 | 65 | self.key_mgr.get, self.ctxt, bad_key_uuid) |
101 | ||
102 | def test_store_null_context(self): | |
103 | key = test_key_manager._get_test_symmetric_key() | |
104 | ||
105 | self.assertRaises(exception.Forbidden, | |
106 | self.key_mgr.store, None, key) | |
107 | ||
108 | ||
109 | class VaultKeyManagerOSLOContextTestCase(VaultKeyManagerTestCase, | |
110 | base.BaseTestCase): | |
111 | def get_context(self): | |
112 | return context.get_admin_context() | |
113 | 66 | |
114 | 67 | |
115 | 68 | TEST_POLICY = ''' |
127 | 80 | APPROLE_ENDPOINT = 'v1/auth/approle/role/{role_name}' |
128 | 81 | |
129 | 82 | |
130 | class VaultKeyManagerAppRoleTestCase(VaultKeyManagerOSLOContextTestCase): | |
83 | class VaultKeyManagerAppRoleTestCase(VaultKeyManagerTestCase): | |
131 | 84 | |
132 | 85 | mountpoint = 'secret' |
133 | 86 |
93 | 93 | endpoint) |
94 | 94 | self.assertEqual(endpoint + "/" + version, base_url) |
95 | 95 | |
96 | def test_base_url_service_catalog(self): | |
97 | endpoint_data = mock.Mock() | |
98 | endpoint_data.api_version = 'v321' | |
99 | ||
100 | auth = mock.Mock(spec=['service_catalog']) | |
101 | auth.service_catalog.endpoint_data_for.return_value = endpoint_data | |
102 | ||
103 | endpoint = "http://localhost/key_manager" | |
104 | ||
105 | base_url = self.key_mgr._create_base_url(auth, | |
106 | mock.Mock(), | |
107 | endpoint) | |
108 | self.assertEqual(endpoint + "/" + endpoint_data.api_version, base_url) | |
109 | auth.service_catalog.endpoint_data_for.assert_called_once_with( | |
110 | service_type='key-manager') | |
111 | ||
112 | def test_base_url_raise_exception(self): | |
113 | auth = mock.Mock(spec=['get_discovery']) | |
114 | sess = mock.Mock() | |
115 | discovery = mock.Mock() | |
116 | discovery.raw_version_data = mock.Mock(return_value=[]) | |
117 | auth.get_discovery = mock.Mock(return_value=discovery) | |
118 | ||
119 | endpoint = "http://localhost/key_manager" | |
120 | ||
121 | self.assertRaises(exception.KeyManagerError, | |
122 | self.key_mgr._create_base_url, | |
123 | auth, sess, endpoint) | |
124 | auth.get_discovery.asser_called_once_with(sess, url=endpoint) | |
125 | self.assertEqual(1, discovery.raw_version_data.call_count) | |
126 | ||
127 | def test_base_url_get_discovery(self): | |
128 | version = 'v100500' | |
129 | auth = mock.Mock(spec=['get_discovery']) | |
130 | sess = mock.Mock() | |
131 | discovery = mock.Mock() | |
132 | auth.get_discovery = mock.Mock(return_value=discovery) | |
133 | discovery.raw_version_data = mock.Mock(return_value=[{'id': version}]) | |
134 | ||
135 | endpoint = "http://localhost/key_manager" | |
136 | ||
137 | base_url = self.key_mgr._create_base_url(auth, | |
138 | mock.Mock(), | |
139 | endpoint) | |
140 | self.assertEqual(endpoint + "/" + version, base_url) | |
141 | auth.get_discovery.asser_called_once_with(sess, url=endpoint) | |
142 | self.assertEqual(1, discovery.raw_version_data.call_count) | |
143 | ||
96 | 144 | def test_create_key(self): |
97 | 145 | # Create order_ref_url and assign return value |
98 | 146 | order_ref_url = ("http://localhost:9311/v1/orders/" |
39 | 39 | barbican_endpoint = 'http://test-server.org:9311/' |
40 | 40 | options.set_defaults(conf, barbican_endpoint=barbican_endpoint) |
41 | 41 | self.assertEqual(barbican_endpoint, |
42 | conf.get(bkm.BARBICAN_OPT_GROUP).barbican_endpoint) | |
42 | conf.barbican.barbican_endpoint) | |
43 | 43 | |
44 | 44 | barbican_api_version = 'vSomething' |
45 | 45 | options.set_defaults(conf, barbican_api_version=barbican_api_version) |
46 | 46 | self.assertEqual(barbican_api_version, |
47 | conf.get(bkm.BARBICAN_OPT_GROUP).barbican_api_version) | |
47 | conf.barbican.barbican_api_version) | |
48 | 48 | |
49 | 49 | auth_endpoint = 'http://test-server.org/identity' |
50 | 50 | options.set_defaults(conf, auth_endpoint=auth_endpoint) |
51 | 51 | self.assertEqual(auth_endpoint, |
52 | conf.get(bkm.BARBICAN_OPT_GROUP).auth_endpoint) | |
52 | conf.barbican.auth_endpoint) | |
53 | 53 | |
54 | 54 | retry_delay = 3 |
55 | 55 | options.set_defaults(conf, retry_delay=retry_delay) |
56 | 56 | self.assertEqual(retry_delay, |
57 | conf.get(bkm.BARBICAN_OPT_GROUP).retry_delay) | |
57 | conf.barbican.retry_delay) | |
58 | 58 | |
59 | 59 | number_of_retries = 10 |
60 | 60 | options.set_defaults(conf, number_of_retries=number_of_retries) |
61 | 61 | self.assertEqual(number_of_retries, |
62 | conf.get(bkm.BARBICAN_OPT_GROUP).number_of_retries) | |
62 | conf.barbican.number_of_retries) | |
63 | 63 | |
64 | 64 | verify_ssl = True |
65 | 65 | options.set_defaults(conf, verify_ssl=True) |
66 | 66 | self.assertEqual(verify_ssl, |
67 | conf.get(bkm.BARBICAN_OPT_GROUP).verify_ssl) | |
67 | conf.barbican.verify_ssl) | |
68 | 68 | |
69 | 69 | barbican_endpoint_type = 'internal' |
70 | 70 | options.set_defaults(conf, barbican_endpoint_type='internal') |
71 | result_type = conf.get(bkm.BARBICAN_OPT_GROUP).barbican_endpoint_type | |
71 | result_type = conf.barbican.barbican_endpoint_type | |
72 | 72 | self.assertEqual(barbican_endpoint_type, result_type) |
18 | 18 | import functools |
19 | 19 | import types |
20 | 20 | |
21 | import six | |
22 | ||
23 | 21 | |
24 | 22 | def construct_new_test_function(original_func, name, build_params): |
25 | 23 | """Builds a new test function based on parameterized data. |
31 | 29 | :return: A new function object |
32 | 30 | """ |
33 | 31 | new_func = types.FunctionType( |
34 | six.get_function_code(original_func), | |
35 | six.get_function_globals(original_func), | |
32 | original_func.__code__, | |
33 | original_func.__globals__, | |
36 | 34 | name=name, |
37 | argdefs=six.get_function_defaults(original_func) | |
35 | argdefs=original_func.__defaults__, | |
38 | 36 | ) |
39 | 37 | |
40 | 38 | for key, val in original_func.__dict__.items(): |
0 | sphinx>=1.8.0,!=2.1.0 # BSD | |
1 | sphinxcontrib-svg2pdfconverter>=0.1.0 # BSD | |
2 | reno>=2.5.0 # Apache-2.0 | |
3 | openstackdocstheme>=1.18.1 # Apache-2.0 |
22 | 22 | 'sphinx.ext.autodoc', |
23 | 23 | #'sphinx.ext.intersphinx', |
24 | 24 | 'openstackdocstheme', |
25 | 'sphinxcontrib.rsvgconverter', | |
25 | 26 | ] |
26 | 27 | |
27 | 28 | # autodoc generation is a bit aggressive and a nuisance when doing heavy |
70 | 71 | # Grouping the document tree into LaTeX files. List of tuples |
71 | 72 | # (source start file, target name, title, author, documentclass |
72 | 73 | # [howto/manual]). |
74 | ||
73 | 75 | latex_documents = [ |
74 | 76 | ('index', |
75 | '%s.tex' % project, | |
77 | 'doc-castellan.tex', | |
76 | 78 | u'%s Documentation' % project, |
77 | 79 | u'OpenStack Foundation', 'manual'), |
78 | 80 | ] |
81 | ||
82 | latex_elements = { | |
83 | 'extraclassoptions': 'openany,oneside', | |
84 | } | |
85 | ||
86 | latex_use_xindy = False | |
79 | 87 | |
80 | 88 | # Example configuration for intersphinx: refer to the Python standard library. |
81 | 89 | #intersphinx_mapping = {'https://docs.python.org/3/': None} |
14 | 14 | user/index |
15 | 15 | contributor/index |
16 | 16 | |
17 | Indices and tables | |
18 | ================== | |
17 | .. only:: html | |
19 | 18 | |
20 | * :ref:`genindex` | |
21 | * :ref:`modindex` | |
22 | * :ref:`search` | |
19 | Indices and tables | |
20 | ================== | |
23 | 21 | |
22 | * :ref:`genindex` | |
23 | * :ref:`modindex` | |
24 | * :ref:`search` |
16 | 16 | gitdb==0.6.4 |
17 | 17 | GitPython==1.0.1 |
18 | 18 | hacking==0.12.0 |
19 | idna==2.6 | |
19 | idna==2.5 | |
20 | 20 | imagesize==0.7.1 |
21 | 21 | iso8601==0.1.11 |
22 | 22 | Jinja2==2.10 |
30 | 30 | msgpack-python==0.4.0 |
31 | 31 | netaddr==0.7.18 |
32 | 32 | netifaces==0.10.4 |
33 | openstackdocstheme==1.18.1 | |
34 | 33 | os-client-config==1.28.0 |
35 | 34 | oslo.config==6.4.0 |
36 | 35 | oslo.context==2.19.2 |
55 | 54 | python-subunit==1.0.0 |
56 | 55 | pytz==2013.6 |
57 | 56 | PyYAML==3.12 |
58 | reno==2.5.0 | |
59 | requests==2.14.2 | |
57 | requests==2.18.0 | |
60 | 58 | requestsexceptions==1.2.0 |
61 | 59 | rfc3986==0.3.1 |
62 | six==1.10.0 | |
63 | 60 | smmap==0.9.0 |
64 | 61 | snowballstemmer==1.2.1 |
65 | Sphinx==1.6.2 | |
66 | sphinxcontrib-websupport==1.0.1 | |
67 | 62 | stevedore==1.20.0 |
68 | 63 | stestr==2.0.0 |
69 | 64 | testscenarios==0.4 |
0 | --- | |
1 | upgrade: | |
2 | - | | |
3 | Python 2.7 support has been dropped. The minimum version of Python now | |
4 | supported by castellan is Python 3.6. |
0 | --- | |
1 | features: | |
2 | - | | |
3 | Enhance the global option listing to discover available key managers and | |
4 | their options. The purpose of this feature is to have a correct listing of | |
5 | the supported key managers, now each key manager is responsible for | |
6 | advertising the oslo.config groups/options they consume. | |
7 | other: | |
8 | - | | |
9 | The visibility of module variables and constants related to oslo.config | |
10 | options changed to private in both barbican and vault key managers. The | |
11 | key managers are only responsible for overloading the method | |
12 | list_options_for_discovery() in order to advertise their own options. | |
13 | This way, the global options doesn't need to know which variables to look | |
14 | for. |
0 | ========================== | |
1 | Train Series Release Notes | |
2 | ========================== | |
3 | ||
4 | .. release-notes:: | |
5 | :branch: stable/train |
12 | 12 | oslo.utils>=3.33.0 # Apache-2.0 |
13 | 13 | stevedore>=1.20.0 # Apache-2.0 |
14 | 14 | keystoneauth1>=3.4.0 # Apache-2.0 |
15 | requests>=2.14.2,!=2.20.0 # Apache-2.0 | |
15 | requests>=2.18.0,!=2.20.0 # Apache-2.0 |
5 | 5 | author = OpenStack |
6 | 6 | author-email = openstack-discuss@lists.openstack.org |
7 | 7 | home-page = https://docs.openstack.org/castellan/latest/ |
8 | python-requires = >=3.6 | |
8 | 9 | classifier = |
9 | 10 | Environment :: OpenStack |
10 | 11 | Intended Audience :: Information Technology |
12 | 13 | License :: OSI Approved :: Apache Software License |
13 | 14 | Operating System :: POSIX :: Linux |
14 | 15 | Programming Language :: Python |
15 | Programming Language :: Python :: 2 | |
16 | Programming Language :: Python :: 2.7 | |
17 | 16 | Programming Language :: Python :: 3 |
18 | 17 | Programming Language :: Python :: 3.6 |
19 | 18 | Programming Language :: Python :: 3.7 |
19 | Programming Language :: Python :: 3 :: Only | |
20 | Programming Language :: Python :: Implementation :: CPython | |
20 | 21 | |
21 | 22 | [files] |
22 | 23 | packages = |
34 | 35 | barbican = castellan.key_manager.barbican_key_manager:BarbicanKeyManager |
35 | 36 | vault = castellan.key_manager.vault_key_manager:VaultKeyManager |
36 | 37 | |
37 | [build_sphinx] | |
38 | source-dir = doc/source | |
39 | build-dir = doc/build | |
40 | all_files = 1 | |
41 | warning-is-error = 1 | |
42 | ||
43 | [upload_sphinx] | |
44 | upload-dir = doc/build/html | |
45 | ||
46 | 38 | [compile_catalog] |
47 | 39 | directory = castellan/locale |
48 | 40 | domain = castellan |
56 | 48 | keywords = _ gettext ngettext l_ lazy_gettext |
57 | 49 | mapping_file = babel.cfg |
58 | 50 | output_file = castellan/locale/castellan.pot |
59 | ||
60 | [wheel] | |
61 | universal = 1 |
5 | 5 | coverage!=4.4,>=4.0 # Apache-2.0 |
6 | 6 | python-barbicanclient>=4.5.2 # Apache-2.0 |
7 | 7 | python-subunit>=1.0.0 # Apache-2.0/BSD |
8 | sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD | |
9 | sphinx!=1.6.6,!=1.6.7,>=1.6.2;python_version>='3.4' # BSD | |
10 | openstackdocstheme>=1.18.1 # Apache-2.0 | |
11 | 8 | oslotest>=3.2.0 # Apache-2.0 |
12 | 9 | stestr>=2.0.0 # Apache-2.0 |
13 | 10 | fixtures>=3.0.0 # Apache-2.0/BSD |
14 | 11 | testscenarios>=0.4 # Apache-2.0/BSD |
15 | 12 | testtools>=2.2.0 # MIT |
16 | 13 | bandit>=1.1.0,<1.6.0 # Apache-2.0 |
17 | reno>=2.5.0 # Apache-2.0 | |
18 | 14 | pifpaf>=0.10.0 # Apache-2.0 |
0 | 0 | [tox] |
1 | minversion = 2.0 | |
2 | envlist = py27,py37,pep8 | |
1 | minversion = 3.1.1 | |
2 | envlist = py37,pep8 | |
3 | ignore_basepython_conflict = True | |
3 | 4 | skipsdist = True |
4 | 5 | |
5 | 6 | [testenv] |
6 | 7 | usedevelop = True |
7 | install_command = pip install {opts} {packages} | |
8 | basepython = python3 | |
8 | 9 | setenv = |
9 | 10 | VIRTUAL_ENV={envdir} |
10 | 11 | OS_TEST_PATH=./castellan/tests/unit |
14 | 15 | -r{toxinidir}/test-requirements.txt |
15 | 16 | commands = stestr run --slowest {posargs} |
16 | 17 | |
17 | [testenv:py27] | |
18 | basepython = python2.7 | |
19 | ||
20 | 18 | [testenv:pep8] |
21 | basepython = python3 | |
22 | 19 | commands = |
23 | 20 | flake8 |
24 | 21 | bandit -r castellan -x tests -s B105,B106,B107,B607 |
25 | 22 | |
26 | 23 | [testenv:bandit] |
27 | basepython = python3 | |
28 | 24 | # This command runs the bandit security linter against the castellan |
29 | 25 | # codebase minus the tests directory. Some tests are being excluded to |
30 | 26 | # reduce the number of positives before a team inspection, and to ensure a |
37 | 33 | bandit -r castellan -x tests -s B105,B106,B107,B607 |
38 | 34 | |
39 | 35 | [testenv:venv] |
40 | basepython = python3 | |
41 | 36 | commands = {posargs} |
42 | 37 | |
43 | 38 | [testenv:debug] |
44 | basepython = python3 | |
45 | 39 | commands = oslo_debug_helper {posargs} |
46 | 40 | |
47 | 41 | [testenv:cover] |
48 | basepython = python3 | |
49 | 42 | setenv = |
50 | PYTHON=coverage run --source $project --parallel-mode | |
43 | PYTHON=coverage run --source castellan --parallel-mode | |
51 | 44 | commands = |
52 | stestr run {posargs} | |
45 | coverage erase | |
46 | {[testenv]commands} | |
53 | 47 | coverage combine |
54 | 48 | coverage html -d cover |
55 | 49 | coverage xml -o cover/coverage.xml |
56 | coverage report | |
50 | coverage report --show-missing | |
57 | 51 | |
58 | 52 | [testenv:docs] |
59 | basepython = python3 | |
60 | commands = python setup.py build_sphinx | |
53 | # This environment is called from CI scripts to test and publish | |
54 | # the main docs to https://docs.openstack.org/castellan | |
55 | description = Build main documentation | |
56 | deps = -r{toxinidir}/doc/requirements.txt | |
57 | commands= | |
58 | rm -rf doc/build doc/build/doctrees | |
59 | sphinx-build -W -b html -d doc/build/doctrees doc/source doc/build/html | |
60 | whitelist_externals = rm | |
61 | ||
62 | [testenv:pdf-docs] | |
63 | deps = {[testenv:docs]deps} | |
64 | envdir = {toxworkdir}/docs | |
65 | whitelist_externals = | |
66 | rm | |
67 | make | |
68 | commands = | |
69 | rm -rf doc/build/pdf | |
70 | sphinx-build -W -b latex doc/source doc/build/pdf | |
71 | make -C doc/build/pdf | |
61 | 72 | |
62 | 73 | [testenv:releasenotes] |
63 | basepython = python3 | |
74 | deps = {[testenv:docs]deps} | |
75 | envdir = {toxworkdir}/docs | |
64 | 76 | commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html |
65 | 77 | |
66 | 78 | [testenv:functional] |
82 | 94 | {toxinidir}/tools/setup-vault-env.sh pifpaf -e VAULT_TEST run vault -- stestr run --slowest {posargs} |
83 | 95 | |
84 | 96 | [testenv:genconfig] |
85 | basepython = python3 | |
86 | 97 | commands = |
87 | 98 | oslo-config-generator --config-file=etc/castellan/functional-config-generator.conf |
88 | 99 | oslo-config-generator --config-file=etc/castellan/sample-config-generator.conf |
98 | 109 | import_exceptions = castellan.i18n |
99 | 110 | |
100 | 111 | [testenv:lower-constraints] |
101 | basepython = python3 | |
102 | 112 | deps = |
103 | 113 | -c{toxinidir}/lower-constraints.txt |
104 | 114 | -r{toxinidir}/test-requirements.txt |
105 | 115 | -r{toxinidir}/requirements.txt |
106 | 116 | |
107 | 117 | [testenv:bindep] |
108 | basepython = python3 | |
109 | 118 | # Do not install any requirements. We want this to be fast and work even if |
110 | 119 | # system dependencies are missing, since it's used to tell you what system |
111 | 120 | # dependencies are missing! This also means that bindep must be installed |