Merge tag '3.5.0' into debian/victoria
castellan 3.5.0 release
meta:version: 3.5.0
meta:diff-start: -
meta:series: victoria
meta:release-type: release
meta:pypi: no
meta:first: no
meta:release:Author: Hervé Beraud <hberaud@redhat.com>
meta:release:Commit: Hervé Beraud <hberaud@redhat.com>
meta:release:Change-Id: I634e7d331d55fbbd83b1bb7e5059d4be6ec86aaf
meta:release:Code-Review+1: Ghanshyam Mann <gmann@ghanshyammann.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
0 | 0 | - job: |
1 | 1 | name: castellan-functional-vault |
2 | parent: openstack-tox-py27 | |
2 | parent: openstack-tox-py36 | |
3 | 3 | description: | |
4 | 4 | Run tox functional-vault target |
5 | 5 | required-projects: |
50 | 50 | jobs: |
51 | 51 | - castellan-functional-vault |
52 | 52 | - castellan-functional-devstack |
53 | - barbican-simple-crypto-devstack-tempest-castellan-from-git | |
53 | - barbican-tempest-plugin-simple-crypto-castellan-src | |
54 | 54 | gate: |
55 | 55 | jobs: |
56 | 56 | - castellan-functional-vault |
57 | 57 | - castellan-functional-devstack |
58 | - barbican-simple-crypto-devstack-tempest-castellan-from-git | |
58 | - barbican-tempest-plugin-simple-crypto-castellan-src | |
59 | 59 | templates: |
60 | 60 | - check-requirements |
61 | 61 | - openstack-lower-constraints-jobs |
62 | - openstack-python3-ussuri-jobs | |
62 | - openstack-python3-victoria-jobs | |
63 | 63 | - periodic-stable-jobs |
64 | 64 | - publish-openstack-docs-pti |
65 | 65 | - release-notes-jobs-python3 |
63 | 63 | message = _("Key not found, uuid: %(uuid)s") |
64 | 64 | |
65 | 65 | |
66 | class InvalidManagedObjectDictError(CastellanException): | |
67 | message = _("Dict has no field '%(field)s'.") | |
68 | ||
69 | ||
70 | class UnknownManagedObjectTypeError(CastellanException): | |
71 | message = _("Type not found, type: %(type)s") | |
72 | ||
73 | ||
66 | 74 | class AuthTypeInvalidError(CastellanException): |
67 | 75 | message = _("Invalid auth_type was specified, auth_type: %(type)s") |
68 | 76 |
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 | from castellan.common import exception | |
13 | from castellan.common.objects import opaque_data | |
14 | from castellan.common.objects import passphrase | |
15 | from castellan.common.objects import private_key | |
16 | from castellan.common.objects import public_key | |
17 | from castellan.common.objects import symmetric_key | |
18 | from castellan.common.objects import x_509 | |
19 | ||
20 | _managed_objects_by_type = { | |
21 | cls.managed_type(): cls for cls in [ | |
22 | opaque_data.OpaqueData, | |
23 | passphrase.Passphrase, | |
24 | private_key.PrivateKey, | |
25 | public_key.PublicKey, | |
26 | symmetric_key.SymmetricKey, | |
27 | x_509.X509, | |
28 | ] | |
29 | } | |
30 | ||
31 | ||
32 | def from_dict(obj, id=None): | |
33 | try: | |
34 | managed_object_type = obj["type"] | |
35 | except KeyError: | |
36 | raise exception.InvalidManagedObjectDictError(field="type") | |
37 | ||
38 | try: | |
39 | cls = _managed_objects_by_type[managed_object_type] | |
40 | except KeyError: | |
41 | raise exception.UnknownManagedObjectTypeError(type=managed_object_type) | |
42 | ||
43 | try: | |
44 | managed_object = cls.from_dict(obj, id) | |
45 | except KeyError as e: | |
46 | raise exception.InvalidManagedObjectDictError(field=str(e)) | |
47 | ||
48 | return managed_object |
21 | 21 | """ |
22 | 22 | |
23 | 23 | import abc |
24 | import binascii | |
24 | 25 | |
26 | from castellan.common.objects import exception | |
25 | 27 | from castellan.common.objects import managed_object |
26 | 28 | |
27 | 29 | |
28 | 30 | class Key(managed_object.ManagedObject): |
29 | 31 | """Base class to represent all keys.""" |
30 | 32 | |
31 | @abc.abstractproperty | |
33 | @property | |
34 | @abc.abstractmethod | |
32 | 35 | def algorithm(self): |
33 | 36 | """Returns the key's algorithm. |
34 | 37 | |
37 | 40 | """ |
38 | 41 | pass |
39 | 42 | |
40 | @abc.abstractproperty | |
43 | @property | |
44 | @abc.abstractmethod | |
41 | 45 | def bit_length(self): |
42 | 46 | """Returns the key's bit length. |
43 | 47 | |
46 | 50 | the length of the modulus. |
47 | 51 | """ |
48 | 52 | pass |
53 | ||
54 | def to_dict(self): | |
55 | dict_fields = super().to_dict() | |
56 | ||
57 | dict_fields["algorithm"] = self.algorithm | |
58 | dict_fields["bit_length"] = self.bit_length | |
59 | ||
60 | return dict_fields | |
61 | ||
62 | @classmethod | |
63 | def from_dict(cls, dict_fields, id=None, metadata_only=False): | |
64 | try: | |
65 | value = None | |
66 | ||
67 | # NOTE(moguimar): the managed object's value is exported as | |
68 | # a hex string. For now, this is a compatibility thing with | |
69 | # the already existent vault_key_manager backend. | |
70 | if not metadata_only and dict_fields["value"] is not None: | |
71 | value = binascii.unhexlify(dict_fields["value"]) | |
72 | ||
73 | return cls( | |
74 | algorithm=dict_fields["algorithm"], | |
75 | bit_length=dict_fields["bit_length"], | |
76 | key=value, | |
77 | name=dict_fields["name"], | |
78 | created=dict_fields["created"], | |
79 | id=id, | |
80 | ) | |
81 | except KeyError as e: | |
82 | raise exception.InvalidManagedObjectDictError(field=str(e)) |
18 | 18 | This module defines the ManagedObject class. The ManagedObject class |
19 | 19 | is the base class to represent all objects managed by the key manager. |
20 | 20 | """ |
21 | ||
21 | 22 | import abc |
23 | import binascii | |
24 | ||
25 | from castellan.common import exception | |
22 | 26 | |
23 | 27 | |
24 | 28 | class ManagedObject(object, metaclass=abc.ABCMeta): |
68 | 72 | """ |
69 | 73 | return self._created |
70 | 74 | |
71 | @abc.abstractproperty | |
75 | @property | |
76 | @abc.abstractmethod | |
72 | 77 | def format(self): |
73 | 78 | """Returns the encoding format. |
74 | 79 | |
76 | 81 | encoded. |
77 | 82 | """ |
78 | 83 | pass |
84 | ||
85 | @property | |
86 | def value(self): | |
87 | """Returns the managed object value.""" | |
88 | return self.get_encoded() | |
79 | 89 | |
80 | 90 | @abc.abstractmethod |
81 | 91 | def get_encoded(self): |
89 | 99 | def is_metadata_only(self): |
90 | 100 | """Returns if the associated object is only metadata or not.""" |
91 | 101 | return self.get_encoded() is None |
102 | ||
103 | @classmethod | |
104 | @abc.abstractmethod | |
105 | def managed_type(cls): | |
106 | """Returns the managed object type identifier. | |
107 | ||
108 | Returns the object's type identifier for serialization purpose. | |
109 | """ | |
110 | pass | |
111 | ||
112 | @classmethod | |
113 | def from_dict(cls, dict_fields, id=None, metadata_only=False): | |
114 | """Returns an instance of this class based on a dict object. | |
115 | ||
116 | :param dict_fields: The dictionary containing all necessary params | |
117 | to create one instance. | |
118 | :param id: The optional param 'id' to be passed to the constructor. | |
119 | :param metadata_only: A switch to create an instance with metadata | |
120 | only, without the secret itself. | |
121 | """ | |
122 | try: | |
123 | value = None | |
124 | ||
125 | # NOTE(moguimar): the managed object's value is exported as | |
126 | # a hex string. For now, this is a compatibility thing with | |
127 | # the already existent vault_key_manager backend. | |
128 | if not metadata_only and dict_fields["value"] is not None: | |
129 | value = binascii.unhexlify(dict_fields["value"]) | |
130 | ||
131 | return cls( | |
132 | value, | |
133 | name=dict_fields["name"], | |
134 | created=dict_fields["created"], | |
135 | id=id, | |
136 | ) | |
137 | except KeyError as e: | |
138 | raise exception.InvalidManagedObjectDictError(field=str(e)) | |
139 | ||
140 | def to_dict(self, metadata_only=False): | |
141 | """Returns a dict that can be used with the from_dict() method. | |
142 | ||
143 | :param metadata_only: A switch to create an dictionary with metadata | |
144 | only, without the secret itself. | |
145 | ||
146 | :rtype: dict | |
147 | """ | |
148 | value = None | |
149 | ||
150 | # NOTE(moguimar): the managed object's value is exported as | |
151 | # a hex string. For now, this is a compatibility thing with | |
152 | # the already existent vault_key_manager backend. | |
153 | if not metadata_only and self.value is not None: | |
154 | value = binascii.hexlify(self.value).decode("utf-8") | |
155 | ||
156 | return { | |
157 | "type": self.managed_type(), | |
158 | "name": self.name, | |
159 | "created": self.created, | |
160 | "value": value, | |
161 | } |
30 | 30 | Expected type for data is a bytestring. |
31 | 31 | """ |
32 | 32 | self._data = data |
33 | super(OpaqueData, self).__init__(name=name, created=created, id=id) | |
33 | super().__init__(name=name, created=created, id=id) | |
34 | ||
35 | @classmethod | |
36 | def managed_type(cls): | |
37 | return "opaque" | |
34 | 38 | |
35 | 39 | @property |
36 | 40 | def format(self): |
37 | """This method returns 'Opaque'.""" | |
38 | 41 | return "Opaque" |
39 | 42 | |
40 | 43 | def get_encoded(self): |
41 | """Returns the data in its original format.""" | |
42 | 44 | return self._data |
43 | 45 | |
44 | 46 | def __eq__(self, other): |
30 | 30 | The expected type for the passphrase is a bytestring. |
31 | 31 | """ |
32 | 32 | self._passphrase = passphrase |
33 | super(Passphrase, self).__init__(name=name, created=created, id=id) | |
33 | super().__init__(name=name, created=created, id=id) | |
34 | ||
35 | @classmethod | |
36 | def managed_type(cls): | |
37 | return "passphrase" | |
34 | 38 | |
35 | 39 | @property |
36 | 40 | def format(self): |
37 | """This method returns 'RAW'.""" | |
38 | 41 | return "RAW" |
39 | 42 | |
40 | 43 | def get_encoded(self): |
41 | """Returns the data in a bytestring.""" | |
42 | 44 | return self._passphrase |
43 | 45 | |
44 | 46 | def __eq__(self, other): |
34 | 34 | self._alg = algorithm |
35 | 35 | self._bit_length = bit_length |
36 | 36 | self._key = key |
37 | super(PrivateKey, self).__init__(name=name, created=created, id=id) | |
37 | super().__init__(name=name, created=created, id=id) | |
38 | ||
39 | @classmethod | |
40 | def managed_type(cls): | |
41 | return "private" | |
38 | 42 | |
39 | 43 | @property |
40 | 44 | def algorithm(self): |
41 | """Returns the algorithm for asymmetric encryption.""" | |
42 | 45 | return self._alg |
43 | 46 | |
44 | 47 | @property |
45 | 48 | def format(self): |
46 | """This method returns 'PKCS8'.""" | |
47 | 49 | return "PKCS8" |
48 | 50 | |
49 | 51 | @property |
50 | 52 | def bit_length(self): |
51 | """Returns the key length.""" | |
52 | 53 | return self._bit_length |
53 | 54 | |
54 | 55 | def get_encoded(self): |
55 | """Returns the key in DER encoded format.""" | |
56 | 56 | return self._key |
57 | 57 | |
58 | 58 | def __eq__(self, other): |
35 | 35 | self._alg = algorithm |
36 | 36 | self._bit_length = bit_length |
37 | 37 | self._key = key |
38 | super(PublicKey, self).__init__(name=name, created=created, id=id) | |
38 | super().__init__(name=name, created=created, id=id) | |
39 | ||
40 | @classmethod | |
41 | def managed_type(cls): | |
42 | return "public" | |
39 | 43 | |
40 | 44 | @property |
41 | 45 | def algorithm(self): |
42 | """Returns the algorithm for asymmetric encryption.""" | |
43 | 46 | return self._alg |
44 | 47 | |
45 | 48 | @property |
46 | 49 | def format(self): |
47 | """This method returns 'SubjectPublicKeyInfo'.""" | |
48 | 50 | return "SubjectPublicKeyInfo" |
49 | 51 | |
50 | 52 | def get_encoded(self): |
51 | """Returns the key in its encoded format.""" | |
52 | 53 | return self._key |
53 | 54 | |
54 | 55 | @property |
55 | 56 | def bit_length(self): |
56 | """Returns the key length.""" | |
57 | 57 | return self._bit_length |
58 | 58 | |
59 | 59 | def __eq__(self, other): |
34 | 34 | self._alg = algorithm |
35 | 35 | self._bit_length = bit_length |
36 | 36 | self._key = key |
37 | super(SymmetricKey, self).__init__(name=name, created=created, id=id) | |
37 | super().__init__(name=name, created=created, id=id) | |
38 | ||
39 | @classmethod | |
40 | def managed_type(cls): | |
41 | return "symmetric" | |
38 | 42 | |
39 | 43 | @property |
40 | 44 | def algorithm(self): |
41 | """Returns the algorithm for symmetric encryption.""" | |
42 | 45 | return self._alg |
43 | 46 | |
44 | 47 | @property |
45 | 48 | def format(self): |
46 | """This method returns 'RAW'.""" | |
47 | 49 | return "RAW" |
48 | 50 | |
49 | 51 | def get_encoded(self): |
50 | """Returns the key in its encoded format.""" | |
51 | 52 | return self._key |
52 | 53 | |
53 | 54 | @property |
54 | 55 | def bit_length(self): |
55 | """Returns the key length.""" | |
56 | 56 | return self._bit_length |
57 | 57 | |
58 | 58 | def __eq__(self, other): |
30 | 30 | The data should be in a bytestring. |
31 | 31 | """ |
32 | 32 | self._data = data |
33 | super(X509, self).__init__(name=name, created=created, id=id) | |
33 | super().__init__(name=name, created=created, id=id) | |
34 | ||
35 | @classmethod | |
36 | def managed_type(cls): | |
37 | return "certificate" | |
34 | 38 | |
35 | 39 | @property |
36 | 40 | def format(self): |
37 | """This method returns 'X.509'.""" | |
38 | 41 | return "X.509" |
39 | 42 | |
40 | 43 | def get_encoded(self): |
41 | """Returns the data in its encoded format.""" | |
42 | 44 | return self._data |
43 | 45 | |
44 | 46 | def __eq__(self, other): |
192 | 192 | return barbican.barbican_endpoint |
193 | 193 | elif getattr(auth, 'service_catalog', None): |
194 | 194 | endpoint_data = auth.service_catalog.endpoint_data_for( |
195 | service_type='key-manager') | |
195 | service_type='key-manager', | |
196 | interface=barbican.barbican_endpoint_type) | |
196 | 197 | return endpoint_data.url |
197 | 198 | else: |
198 | 199 | service_parameters = {'service_type': 'key-manager', |
40 | 40 | |
41 | 41 | _DEFAULT_VAULT_URL = "http://127.0.0.1:8200" |
42 | 42 | _DEFAULT_MOUNTPOINT = "secret" |
43 | _DEFAULT_VERSION = 2 | |
43 | 44 | |
44 | 45 | _vault_opts = [ |
45 | 46 | cfg.StrOpt('root_token_id', |
52 | 53 | default=_DEFAULT_MOUNTPOINT, |
53 | 54 | help='Mountpoint of KV store in Vault to use, for example: ' |
54 | 55 | '{}'.format(_DEFAULT_MOUNTPOINT)), |
56 | cfg.IntOpt('kv_version', | |
57 | default=_DEFAULT_VERSION, | |
58 | help='Version of KV store in Vault to use, for example: ' | |
59 | '{}'.format(_DEFAULT_VERSION)), | |
55 | 60 | cfg.StrOpt('vault_url', |
56 | 61 | default=_DEFAULT_VAULT_URL, |
57 | 62 | help='Use this endpoint to connect to Vault, for example: ' |
91 | 96 | self._approle_token_ttl = None |
92 | 97 | self._approle_token_issue = None |
93 | 98 | self._kv_mountpoint = self._conf.vault.kv_mountpoint |
99 | self._kv_version = self._conf.vault.kv_version | |
94 | 100 | self._vault_url = self._conf.vault.vault_url |
95 | 101 | if self._vault_url.startswith("https://"): |
96 | 102 | self._verify_server = self._conf.vault.ssl_ca_crt_file or True |
97 | 103 | else: |
98 | 104 | self._verify_server = False |
99 | self._vault_kv_version = None | |
100 | 105 | |
101 | 106 | def _get_url(self): |
102 | 107 | if not self._vault_url.endswith('/'): |
103 | 108 | self._vault_url += '/' |
104 | 109 | return self._vault_url |
105 | 110 | |
106 | def _get_api_version(self): | |
107 | if self._vault_kv_version: | |
108 | return self._vault_kv_version | |
109 | ||
110 | resource_url = '{}v1/sys/internal/ui/mounts/{}'.format( | |
111 | self._get_url(), | |
112 | self._kv_mountpoint | |
113 | ) | |
114 | resp = self._do_http_request(self._session.get, resource_url) | |
115 | ||
116 | if resp.status_code == requests.codes['not_found']: | |
117 | self._vault_kv_version = '1' | |
118 | else: | |
119 | self._vault_kv_version = resp.json()['data']['options']['version'] | |
120 | ||
121 | return self._vault_kv_version | |
122 | ||
123 | 111 | def _get_resource_url(self, key_id=None): |
124 | 112 | return '{}v1/{}/{}{}'.format( |
125 | 113 | self._get_url(), |
126 | 114 | self._kv_mountpoint, |
127 | 115 | |
128 | '' if self._get_api_version() == '1' else | |
116 | '' if self._kv_version == 1 else | |
129 | 117 | 'data/' if key_id else |
130 | 118 | 'metadata/', # no key_id is for listing and 'data/' doesn't works |
131 | 119 | |
172 | 160 | if resp.status_code == requests.codes['forbidden']: |
173 | 161 | raise exception.Forbidden() |
174 | 162 | |
175 | resp = resp.json() | |
176 | self._cached_approle_token_id = resp['auth']['client_token'] | |
163 | resp_data = resp.json() | |
164 | ||
165 | if resp.status_code == requests.codes['bad_request']: | |
166 | raise exception.KeyManagerError(', '.join(resp_data['errors'])) | |
167 | ||
168 | self._cached_approle_token_id = resp_data['auth']['client_token'] | |
177 | 169 | self._approle_token_issue = token_issue_utc |
178 | self._approle_token_ttl = resp['auth']['lease_duration'] | |
170 | self._approle_token_ttl = resp_data['auth']['lease_duration'] | |
179 | 171 | return {'X-Vault-Token': self._approle_token_id} |
180 | 172 | |
181 | 173 | return {} |
263 | 255 | 'name': value.name, |
264 | 256 | 'created': value.created |
265 | 257 | } |
266 | if self._get_api_version() != '1': | |
258 | if self._kv_version > 1: | |
267 | 259 | record = {'data': record} |
268 | 260 | |
269 | 261 | self._do_http_request(self._session.post, |
308 | 300 | raise exception.ManagedObjectNotFoundError(uuid=key_id) |
309 | 301 | |
310 | 302 | record = resp.json()['data'] |
311 | if self._get_api_version() != '1': | |
303 | if self._kv_version > 1: | |
312 | 304 | record = record['data'] |
313 | 305 | |
314 | 306 | key = None if metadata_only else binascii.unhexlify(record['value']) |
116 | 116 | self.ks_password_credential) |
117 | 117 | |
118 | 118 | self.assertFalse(self.ks_password_credential is None) |
119 | self.assertFalse(None == self.ks_password_credential) | |
119 | self.assertFalse(None == self.ks_password_credential) # noqa: E711 | |
120 | 120 | |
121 | 121 | other_ks_password_credential = keystone_password.KeystonePassword( |
122 | 122 | self.password, |
139 | 139 | |
140 | 140 | def test___ne___none(self): |
141 | 141 | self.assertTrue(self.ks_password_credential is not None) |
142 | self.assertTrue(None != self.ks_password_credential) | |
142 | self.assertTrue(None != self.ks_password_credential) # noqa: E711 | |
143 | 143 | |
144 | 144 | def test___ne___password(self): |
145 | 145 | other_password = "wheresmyCat??" |
92 | 92 | self.ks_token_credential) |
93 | 93 | |
94 | 94 | self.assertFalse(self.ks_token_credential is None) |
95 | self.assertFalse(None == self.ks_token_credential) | |
95 | self.assertFalse(None == self.ks_token_credential) # noqa: E711 | |
96 | 96 | |
97 | 97 | other_ks_token_credential = keystone_token.KeystoneToken( |
98 | 98 | self.token, |
111 | 111 | |
112 | 112 | def test___ne___none(self): |
113 | 113 | self.assertTrue(self.ks_token_credential is not None) |
114 | self.assertTrue(None != self.ks_token_credential) | |
114 | self.assertTrue(None != self.ks_token_credential) # noqa: E711 | |
115 | 115 | |
116 | 116 | def test___ne___token(self): |
117 | 117 | other_token = "5c59e3217d3d4dd297589b297aee2a6f" |
45 | 45 | self.assertTrue(self.password_credential is self.password_credential) |
46 | 46 | |
47 | 47 | self.assertFalse(self.password_credential is None) |
48 | self.assertFalse(None == self.password_credential) | |
48 | self.assertFalse(None == self.password_credential) # noqa: E711 | |
49 | 49 | |
50 | 50 | other_password_credential = password.Password(self.username, |
51 | 51 | self.password) |
54 | 54 | |
55 | 55 | def test___ne___none(self): |
56 | 56 | self.assertTrue(self.password_credential is not None) |
57 | self.assertTrue(None != self.password_credential) | |
57 | self.assertTrue(None != self.password_credential) # noqa: E711 | |
58 | 58 | |
59 | 59 | def test___ne___username(self): |
60 | 60 | other_username = "service" |
38 | 38 | self.assertTrue(self.token_credential is self.token_credential) |
39 | 39 | |
40 | 40 | self.assertFalse(self.token_credential is None) |
41 | self.assertFalse(None == self.token_credential) | |
41 | self.assertFalse(None == self.token_credential) # noqa: E711 | |
42 | 42 | |
43 | 43 | other_token_credential = token.Token(self.token) |
44 | 44 | self.assertTrue(self.token_credential == other_token_credential) |
46 | 46 | |
47 | 47 | def test___ne___none(self): |
48 | 48 | self.assertTrue(self.token_credential is not None) |
49 | self.assertTrue(None != self.token_credential) | |
49 | self.assertTrue(None != self.token_credential) # noqa: E711 | |
50 | 50 | |
51 | 51 | def test___ne___token(self): |
52 | 52 | other_token = "fe32af1fe47e4744a48254e60ae80012" |
16 | 16 | Test cases for the barbican key manager. |
17 | 17 | """ |
18 | 18 | import calendar |
19 | from unittest import mock | |
19 | 20 | |
20 | 21 | from barbicanclient import exceptions as barbican_exceptions |
21 | import mock | |
22 | 22 | from oslo_utils import timeutils |
23 | 23 | |
24 | 24 | from castellan.common import exception |
17 | 17 | """ |
18 | 18 | |
19 | 19 | import binascii |
20 | import mock | |
20 | from unittest import mock | |
21 | 21 | |
22 | 22 | from oslo_config import cfg |
23 | 23 |
0 | # Copyright 2020 Red Hat, 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 Managed Objects. | |
17 | """ | |
18 | from castellan.common import exception | |
19 | from castellan.common import objects | |
20 | from castellan.tests import base | |
21 | ||
22 | ||
23 | class ManagedObjectFromDictTestCase(base.TestCase): | |
24 | def test_invalid_dict(self): | |
25 | self.assertRaises( | |
26 | exception.InvalidManagedObjectDictError, | |
27 | objects.from_dict, | |
28 | {}, | |
29 | ) | |
30 | ||
31 | def test_unknown_type(self): | |
32 | self.assertRaises( | |
33 | exception.UnknownManagedObjectTypeError, | |
34 | objects.from_dict, | |
35 | {"type": "non-existing-managed-object-type"}, | |
36 | ) |
15 | 15 | """ |
16 | 16 | Test cases for the opaque data class. |
17 | 17 | """ |
18 | from castellan.common import objects | |
18 | 19 | from castellan.common.objects import opaque_data |
19 | 20 | from castellan.tests import base |
20 | 21 | |
66 | 67 | self.assertTrue(self.opaque_data is self.opaque_data) |
67 | 68 | |
68 | 69 | self.assertFalse(self.opaque_data is None) |
69 | self.assertFalse(None == self.opaque_data) | |
70 | self.assertFalse(None == self.opaque_data) # noqa: E711 | |
70 | 71 | |
71 | 72 | other_opaque_data = opaque_data.OpaqueData(self.data) |
72 | 73 | self.assertTrue(self.opaque_data == other_opaque_data) |
74 | 75 | |
75 | 76 | def test___ne___none(self): |
76 | 77 | self.assertTrue(self.opaque_data is not None) |
77 | self.assertTrue(None != self.opaque_data) | |
78 | self.assertTrue(None != self.opaque_data) # noqa: E711 | |
78 | 79 | |
79 | 80 | def test___ne___data(self): |
80 | 81 | other_opaque = opaque_data.OpaqueData(b'other data', self.name) |
81 | 82 | self.assertTrue(self.opaque_data != other_opaque) |
83 | ||
84 | def test_to_and_from_dict(self): | |
85 | other = objects.from_dict(self.opaque_data.to_dict()) | |
86 | self.assertEqual(self.opaque_data, other) |
15 | 15 | """ |
16 | 16 | Test cases for the passphrase class. |
17 | 17 | """ |
18 | from castellan.common import objects | |
18 | 19 | from castellan.common.objects import passphrase |
19 | 20 | from castellan.tests import base |
20 | 21 | |
66 | 67 | self.assertTrue(self.passphrase is self.passphrase) |
67 | 68 | |
68 | 69 | self.assertFalse(self.passphrase is None) |
69 | self.assertFalse(None == self.passphrase) | |
70 | self.assertFalse(None == self.passphrase) # noqa: E711 | |
70 | 71 | |
71 | 72 | other_passphrase = passphrase.Passphrase(self.passphrase_data) |
72 | 73 | self.assertTrue(self.passphrase == other_passphrase) |
74 | 75 | |
75 | 76 | def test___ne___none(self): |
76 | 77 | self.assertTrue(self.passphrase is not None) |
77 | self.assertTrue(None != self.passphrase) | |
78 | self.assertTrue(None != self.passphrase) # noqa: E711 | |
78 | 79 | |
79 | 80 | def test___ne___data(self): |
80 | 81 | other_phrase = passphrase.Passphrase(b"other passphrase", self.name) |
81 | 82 | self.assertTrue(self.passphrase != other_phrase) |
83 | ||
84 | def test_to_and_from_dict(self): | |
85 | other = objects.from_dict(self.passphrase.to_dict()) | |
86 | self.assertEqual(self.passphrase, other) |
15 | 15 | """ |
16 | 16 | Test cases for the private key class. |
17 | 17 | """ |
18 | from castellan.common import objects | |
18 | 19 | from castellan.common.objects import private_key |
19 | 20 | from castellan.tests import base |
20 | 21 | from castellan.tests import utils |
82 | 83 | self.assertTrue(self.key is self.key) |
83 | 84 | |
84 | 85 | self.assertFalse(self.key is None) |
85 | self.assertFalse(None == self.key) | |
86 | self.assertFalse(None == self.key) # noqa: E711 | |
86 | 87 | |
87 | 88 | other_key = private_key.PrivateKey(self.algorithm, |
88 | 89 | self.bit_length, |
92 | 93 | |
93 | 94 | def test___ne___none(self): |
94 | 95 | self.assertTrue(self.key is not None) |
95 | self.assertTrue(None != self.key) | |
96 | self.assertTrue(None != self.key) # noqa: E711 | |
96 | 97 | |
97 | 98 | def test___ne___algorithm(self): |
98 | 99 | other_key = private_key.PrivateKey('DSA', |
115 | 116 | different_encoded, |
116 | 117 | self.name) |
117 | 118 | self.assertTrue(self.key != other_key) |
119 | ||
120 | def test_to_and_from_dict(self): | |
121 | other = objects.from_dict(self.key.to_dict()) | |
122 | self.assertEqual(self.key, other) |
15 | 15 | """ |
16 | 16 | Test cases for the public key class. |
17 | 17 | """ |
18 | from castellan.common import objects | |
18 | 19 | from castellan.common.objects import public_key |
19 | 20 | from castellan.tests import base |
20 | 21 | from castellan.tests import utils |
82 | 83 | self.assertTrue(self.key is self.key) |
83 | 84 | |
84 | 85 | self.assertFalse(self.key is None) |
85 | self.assertFalse(None == self.key) | |
86 | self.assertFalse(None == self.key) # noqa: E711 | |
86 | 87 | |
87 | 88 | other_key = public_key.PublicKey(self.algorithm, |
88 | 89 | self.bit_length, |
92 | 93 | |
93 | 94 | def test___ne___none(self): |
94 | 95 | self.assertTrue(self.key is not None) |
95 | self.assertTrue(None != self.key) | |
96 | self.assertTrue(None != self.key) # noqa: E711 | |
96 | 97 | |
97 | 98 | def test___ne___algorithm(self): |
98 | 99 | other_key = public_key.PublicKey('DSA', |
115 | 116 | different_encoded, |
116 | 117 | self.name) |
117 | 118 | self.assertTrue(self.key != other_key) |
119 | ||
120 | def test_to_and_from_dict(self): | |
121 | other = objects.from_dict(self.key.to_dict()) | |
122 | self.assertEqual(self.key, other) |
15 | 15 | """ |
16 | 16 | Test cases for the symmetric key class. |
17 | 17 | """ |
18 | from castellan.common import objects | |
18 | 19 | from castellan.common.objects import symmetric_key as sym_key |
19 | 20 | from castellan.tests import base |
20 | 21 | |
81 | 82 | self.assertTrue(self.key is self.key) |
82 | 83 | |
83 | 84 | self.assertFalse(self.key is None) |
84 | self.assertFalse(None == self.key) | |
85 | self.assertFalse(None == self.key) # noqa: E711 | |
85 | 86 | |
86 | 87 | other_key = sym_key.SymmetricKey(self.algorithm, |
87 | 88 | self.bit_length, |
91 | 92 | |
92 | 93 | def test___ne___none(self): |
93 | 94 | self.assertTrue(self.key is not None) |
94 | self.assertTrue(None != self.key) | |
95 | self.assertTrue(None != self.key) # noqa: E711 | |
95 | 96 | |
96 | 97 | def test___ne___algorithm(self): |
97 | 98 | other_key = sym_key.SymmetricKey('DES', |
114 | 115 | different_encoded, |
115 | 116 | self.name) |
116 | 117 | self.assertTrue(self.key != other_key) |
118 | ||
119 | def test_to_and_from_dict(self): | |
120 | other = objects.from_dict(self.key.to_dict()) | |
121 | self.assertEqual(self.key, other) |
15 | 15 | """ |
16 | 16 | Test cases for the X.509 certificate class. |
17 | 17 | """ |
18 | from castellan.common import objects | |
18 | 19 | from castellan.common.objects import x_509 |
19 | 20 | from castellan.tests import base |
20 | 21 | from castellan.tests import utils |
66 | 67 | self.assertTrue(self.cert is self.cert) |
67 | 68 | |
68 | 69 | self.assertFalse(self.cert is None) |
69 | self.assertFalse(None == self.cert) | |
70 | self.assertFalse(None == self.cert) # noqa: E711 | |
70 | 71 | |
71 | 72 | other_x_509 = x_509.X509(self.data) |
72 | 73 | self.assertTrue(self.cert == other_x_509) |
74 | 75 | |
75 | 76 | def test___ne___none(self): |
76 | 77 | self.assertTrue(self.cert is not None) |
77 | self.assertTrue(None != self.cert) | |
78 | self.assertTrue(None != self.cert) # noqa: E711 | |
78 | 79 | |
79 | 80 | def test___ne___data(self): |
80 | 81 | other_x509 = x_509.X509(b'\x00\x00\x00', self.name) |
81 | 82 | self.assertTrue(self.cert != other_x509) |
83 | ||
84 | def test_to_and_from_dict(self): | |
85 | other = objects.from_dict(self.cert.to_dict()) | |
86 | self.assertEqual(self.cert, other) |
0 | sphinx>=1.8.0,!=2.1.0 # BSD | |
0 | sphinx>=2.0.0,!=2.1.0 # BSD | |
1 | 1 | sphinxcontrib-svg2pdfconverter>=0.1.0 # BSD |
2 | reno>=2.5.0 # Apache-2.0 | |
3 | openstackdocstheme>=1.18.1 # Apache-2.0 | |
2 | reno>=3.1.0 # Apache-2.0 | |
3 | openstackdocstheme>=2.2.1 # Apache-2.0 |
47 | 47 | add_module_names = True |
48 | 48 | |
49 | 49 | # The name of the Pygments (syntax highlighting) style to use. |
50 | pygments_style = 'sphinx' | |
50 | pygments_style = 'native' | |
51 | 51 | |
52 | 52 | # -- Options for HTML output -------------------------------------------------- |
53 | 53 | |
58 | 58 | |
59 | 59 | # Output file base name for HTML help builder. |
60 | 60 | htmlhelp_basename = '%sdoc' % project |
61 | ||
62 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, | |
63 | # using the given strftime format. | |
64 | # html_last_updated_fmt = '%b %d, %Y' | |
65 | html_last_updated_fmt = '%Y-%m-%d %H:%M' | |
66 | 61 | |
67 | 62 | # Add any paths that contain "extra" files, such as .htaccess or |
68 | 63 | # robots.txt. |
89 | 84 | #intersphinx_mapping = {'https://docs.python.org/3/': None} |
90 | 85 | |
91 | 86 | # -- Options for openstackdocstheme ------------------------------------------- |
92 | repository_name = 'openstack/castellan' | |
93 | bug_project = 'castellan' | |
94 | bug_tag = '' | |
87 | openstackdocs_repo_name = 'openstack/castellan' | |
88 | openstackdocs_pdf_link = True | |
89 | openstackdocs_auto_name = False | |
90 | openstackdocs_bug_project = 'castellan' | |
91 | openstackdocs_bug_tag = '' |
0 | alabaster==0.7.10 | |
1 | 0 | appdirs==1.3.0 |
2 | 1 | asn1crypto==0.23.0 |
3 | Babel==2.3.4 | |
4 | bandit==1.1.0 | |
5 | cffi==1.7.0 | |
2 | certifi==2020.4.5.2 | |
3 | cffi==1.13.2 | |
4 | chardet==3.0.4 | |
6 | 5 | cliff==2.8.0 |
7 | 6 | cmd2==0.8.0 |
8 | 7 | coverage==4.0 |
9 | 8 | cryptography==2.1 |
10 | 9 | debtcollector==1.2.0 |
11 | docutils==0.11 | |
12 | dulwich==0.15.0 | |
10 | entrypoints==0.3 | |
13 | 11 | extras==1.0.0 |
14 | 12 | fixtures==3.0.0 |
15 | flake8==2.5.5 | |
13 | future==0.18.2 | |
16 | 14 | gitdb==0.6.4 |
17 | 15 | GitPython==1.0.1 |
18 | hacking==0.12.0 | |
19 | 16 | idna==2.5 |
20 | imagesize==0.7.1 | |
21 | 17 | iso8601==0.1.11 |
22 | Jinja2==2.10 | |
23 | 18 | keystoneauth1==3.4.0 |
24 | 19 | linecache2==1.0.0 |
25 | MarkupSafe==1.0 | |
26 | mccabe==0.2.1 | |
27 | mock==2.0.0 | |
28 | 20 | monotonic==0.6 |
29 | 21 | mox3==0.20.0 |
30 | 22 | msgpack-python==0.4.0 |
39 | 31 | oslo.utils==3.33.0 |
40 | 32 | oslotest==3.2.0 |
41 | 33 | pbr==2.0.0 |
42 | pep8==1.5.7 | |
43 | 34 | pifpaf==0.10.0 |
44 | 35 | prettytable==0.7.2 |
45 | 36 | pycparser==2.18 |
46 | pyflakes==0.8.1 | |
47 | Pygments==2.2.0 | |
48 | 37 | pyinotify==0.9.6 |
49 | 38 | pyparsing==2.1.0 |
50 | 39 | pyperclip==1.5.27 |
57 | 46 | requests==2.18.0 |
58 | 47 | requestsexceptions==1.2.0 |
59 | 48 | rfc3986==0.3.1 |
49 | six==1.15.0 | |
60 | 50 | smmap==0.9.0 |
61 | snowballstemmer==1.2.1 | |
51 | stestr==2.0.0 | |
62 | 52 | stevedore==1.20.0 |
63 | stestr==2.0.0 | |
53 | testrepository==0.0.20 | |
64 | 54 | testscenarios==0.4 |
65 | 55 | testtools==2.2.0 |
66 | 56 | traceback2==1.4.0 |
67 | 57 | unittest2==1.1.0 |
58 | urllib3==1.21.1 | |
59 | voluptuous==0.11.7 | |
68 | 60 | wrapt==1.7.0 |
69 | 61 | xattr==0.9.2 |
+10
-0
0 | --- | |
1 | features: | |
2 | - | | |
3 | Historically, the vault key manager backend converts its managed objects | |
4 | to dictionaries in order to send them as a json object. To promote | |
5 | cross-backend compatibility, suck feature should be migrated to managed | |
6 | objects. Methods from_dict() and to_dict() added to class ManagedObject. | |
7 | The Method from_dict() is a class method to create instances based on a | |
8 | dictionary while the method to_dict() is an instance method to translate | |
9 | an instance to a dictionary. |
0 | --- | |
1 | fixes: | |
2 | - | | |
3 | In some situations, vault will not provide KV API version in the options | |
4 | structure. Vault documentation [1] doesn't cover cases when KV API version | |
5 | is not provided. A new configuration option, with default value equivalent | |
6 | to the latest KV API version available (kv_version=2) was added to allow | |
7 | precise configuration of the KV API being used. | |
8 | ||
9 | [1] https://learn.hashicorp.com/vault/secrets-management/sm-versioned-kv |
0 | --- | |
1 | fixes: | |
2 | - | | |
3 | ``barbican_endpoint_type`` is now used to retrieve Barbican endpoint URL | |
4 | from service catalog. This config option is set to 'public' by default so | |
5 | it will not change the current behaviour. |
51 | 51 | master_doc = 'index' |
52 | 52 | |
53 | 53 | # General information about the project. |
54 | repository_name = 'openstack/castellan' | |
55 | bug_project = 'castellan' | |
56 | bug_tag = 'doc' | |
54 | openstackdocs_repo_name = 'openstack/castellan' | |
55 | openstackdocs_auto_name = False | |
56 | openstackdocs_bug_project = 'castellan' | |
57 | openstackdocs_bug_tag = 'doc' | |
57 | 58 | project = u'Castellan Release Notes' |
58 | 59 | copyright = u'2017, Castellan Developers' |
59 | 60 | |
94 | 95 | # show_authors = False |
95 | 96 | |
96 | 97 | # The name of the Pygments (syntax highlighting) style to use. |
97 | pygments_style = 'sphinx' | |
98 | pygments_style = 'native' | |
98 | 99 | |
99 | 100 | # A list of ignored prefixes for module index sorting. |
100 | 101 | # modindex_common_prefix = [] |
142 | 143 | # .htaccess) here, relative to this directory. These files are copied |
143 | 144 | # directly to the root of the documentation. |
144 | 145 | # html_extra_path = [] |
145 | ||
146 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, | |
147 | # using the given strftime format. | |
148 | html_last_updated_fmt = '%Y-%m-%d %H:%M' | |
149 | 146 | |
150 | 147 | # If true, SmartyPants will be used to convert quotes and dashes to |
151 | 148 | # typographically correct entities. |
0 | =========================== | |
1 | Ussuri Series Release Notes | |
2 | =========================== | |
3 | ||
4 | .. release-notes:: | |
5 | :branch: stable/ussuri |
2 | 2 | # process, which may cause wedges in the gate later. |
3 | 3 | |
4 | 4 | pbr!=2.1.0,>=2.0.0 # Apache-2.0 |
5 | Babel!=2.4.0,>=2.3.4 # BSD | |
6 | 5 | cryptography>=2.1 # BSD/Apache-2.0 |
7 | 6 | python-barbicanclient>=4.5.2 # Apache-2.0 |
8 | 7 | oslo.config>=6.4.0 # Apache-2.0 |
16 | 16 | Programming Language :: Python :: 3 |
17 | 17 | Programming Language :: Python :: 3.6 |
18 | 18 | Programming Language :: Python :: 3.7 |
19 | Programming Language :: Python :: 3.8 | |
19 | 20 | Programming Language :: Python :: 3 :: Only |
20 | 21 | Programming Language :: Python :: Implementation :: CPython |
21 | 22 | |
34 | 35 | castellan.drivers = |
35 | 36 | barbican = castellan.key_manager.barbican_key_manager:BarbicanKeyManager |
36 | 37 | vault = castellan.key_manager.vault_key_manager:VaultKeyManager |
37 | ||
38 | [compile_catalog] | |
39 | directory = castellan/locale | |
40 | domain = castellan | |
41 | ||
42 | [update_catalog] | |
43 | domain = castellan | |
44 | output_dir = castellan/locale | |
45 | input_file = castellan/locale/castellan.pot | |
46 | ||
47 | [extract_messages] | |
48 | keywords = _ gettext ngettext l_ lazy_gettext | |
49 | mapping_file = babel.cfg | |
50 | output_file = castellan/locale/castellan.pot |
12 | 12 | # See the License for the specific language governing permissions and |
13 | 13 | # limitations under the License. |
14 | 14 | |
15 | # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT | |
16 | 15 | import setuptools |
17 | ||
18 | # In python < 2.7.4, a lazy loading of package `pbr` will break | |
19 | # setuptools if some other modules registered functions in `atexit`. | |
20 | # solution from: http://bugs.python.org/issue15881#msg170215 | |
21 | try: | |
22 | import multiprocessing # noqa | |
23 | except ImportError: | |
24 | pass | |
25 | 16 | |
26 | 17 | setuptools.setup( |
27 | 18 | setup_requires=['pbr>=2.0.0'], |
0 | 0 | # The order of packages is significant, because pip processes them in the order |
1 | 1 | # of appearance. Changing the order has an impact on the overall integration |
2 | 2 | # process, which may cause wedges in the gate later. |
3 | hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 | |
3 | hacking>=3.0.1,<3.1.0 # Apache-2.0 | |
4 | # remove this pyflakes from here once you bump the | |
5 | # hacking to 3.2.0 or above. hacking 3.2.0 takes | |
6 | # care of pyflakes version compatibilty. | |
7 | pyflakes>=2.1.1 | |
4 | 8 | |
5 | 9 | coverage!=4.4,>=4.0 # Apache-2.0 |
6 | 10 | python-barbicanclient>=4.5.2 # Apache-2.0 |
10 | 14 | fixtures>=3.0.0 # Apache-2.0/BSD |
11 | 15 | testscenarios>=0.4 # Apache-2.0/BSD |
12 | 16 | testtools>=2.2.0 # MIT |
13 | bandit>=1.1.0,<1.6.0 # Apache-2.0 | |
17 | bandit>=1.6.0,<1.7.0 # Apache-2.0 | |
14 | 18 | pifpaf>=0.10.0 # Apache-2.0 |
0 | 0 | #!/bin/bash |
1 | 1 | set -eux |
2 | 2 | if [ -z "$(which vault)" ]; then |
3 | VAULT_VERSION=0.10.4 | |
3 | VAULT_VERSION=1.4.2 | |
4 | 4 | SUFFIX=zip |
5 | 5 | case `uname -s` in |
6 | 6 | Darwin) |
0 | 0 | [tox] |
1 | 1 | minversion = 3.1.1 |
2 | envlist = py37,pep8 | |
2 | envlist = py38,pep8 | |
3 | 3 | ignore_basepython_conflict = True |
4 | 4 | skipsdist = True |
5 | 5 | |
10 | 10 | VIRTUAL_ENV={envdir} |
11 | 11 | OS_TEST_PATH=./castellan/tests/unit |
12 | 12 | deps = |
13 | -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/ussuri} | |
13 | -c{env:TOX_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt} | |
14 | 14 | -r{toxinidir}/requirements.txt |
15 | 15 | -r{toxinidir}/test-requirements.txt |
16 | 16 | commands = stestr run --slowest {posargs} |
77 | 77 | |
78 | 78 | [testenv:functional] |
79 | 79 | usedevelop = True |
80 | install_command = pip install -U {opts} {packages} | |
81 | 80 | setenv = |
82 | 81 | VIRTUAL_ENV={envdir} |
83 | 82 | OS_TEST_PATH=./castellan/tests/functional |
86 | 85 | [testenv:functional-vault] |
87 | 86 | passenv = HOME |
88 | 87 | usedevelop = True |
89 | install_command = pip install -U {opts} {packages} | |
90 | 88 | setenv = |
91 | 89 | VIRTUAL_ENV={envdir} |
92 | 90 | OS_TEST_PATH=./castellan/tests/functional |