Merge tag '0.3.1' into debian/mitaka
castellan 0.3.1 release
Thomas Goirand
8 years ago
3 | 3 | omit = castellan/tests/*,castellan/openstack/* |
4 | 4 | |
5 | 5 | [report] |
6 | ignore-errors = True | |
6 | ignore_errors = True |
0 | If you would like to contribute to the development of OpenStack, | |
1 | you must follow the steps in this page: | |
0 | ============ | |
1 | Contributing | |
2 | ============ | |
2 | 3 | |
3 | http://docs.openstack.org/infra/manual/developers.html | |
4 | The best way to join the community and get involved is to talk with others | |
5 | online or at a meetup and offer contributions. Here are some of the many | |
6 | ways you can contribute to the Castellan project: | |
4 | 7 | |
5 | Once those steps have been completed, changes to OpenStack | |
6 | should be submitted for review via the Gerrit tool, following | |
7 | the workflow documented at: | |
8 | * Development and Code Reviews | |
9 | * Bug reporting/Bug fixes | |
10 | * Wiki and Documentation | |
11 | * Blueprints/Specifications | |
12 | * Testing | |
13 | * Deployment scripts | |
8 | 14 | |
9 | http://docs.openstack.org/infra/manual/developers.html#development-workflow | |
15 | Before you start contributing take a look at the `Openstack Developers Guide`_. | |
10 | 16 | |
11 | Pull requests submitted through GitHub will be ignored. | |
17 | .. _`Openstack Developers Guide`: http://docs.openstack.org/infra/manual/developers.html | |
12 | 18 | |
13 | Bugs should be filed on Launchpad, not GitHub: | |
19 | Freenode IRC (Chat) | |
20 | ------------------- | |
21 | You can find Castellaneers in our publicly accessible channel on `freenode`_ | |
22 | ``#openstack-barbican``. All conversations are logged and stored for your | |
23 | convenience at `eavesdrop.openstack.org`_. For more information regarding | |
24 | OpenStack IRC channels please visit the `OpenStack IRC Wiki`_. | |
14 | 25 | |
15 | https://bugs.launchpad.net/castellan | |
26 | .. _`freenode`: https://freenode.net | |
27 | .. _`OpenStack IRC Wiki`: https://wiki.openstack.org/wiki/IRC | |
28 | .. _`eavesdrop.openstack.org`: http://eavesdrop.openstack.org/irclogs/ | |
29 | %23openstack-barbican/ | |
30 | ||
31 | Launchpad | |
32 | --------- | |
33 | Like other OpenStack related projects, we utilize Launchpad for our bug | |
34 | and release tracking. | |
35 | ||
36 | * `Castellan Launchpad Project`_ | |
37 | ||
38 | .. _`Castellan Launchpad Project`: https://launchpad.net/castellan | |
39 | ||
40 | .. note:: | |
41 | ||
42 | Bugs should be filed on Launchpad, not Github. | |
43 | ||
44 | Source Repository | |
45 | ----------------- | |
46 | Like other OpenStack related projects, the official Git repository is | |
47 | available on `Castellan on GitHub`_. | |
48 | ||
49 | .. _`Castellan on GitHub`: https://github.com/openstack/castellan | |
50 | ||
51 | Gerrit | |
52 | ------ | |
53 | Like other OpenStack related projects, we utilize the OpenStack Gerrit | |
54 | review system for all code reviews. If you're unfamiliar with using | |
55 | the OpenStack Gerrit review system, please review the `Gerrit Workflow`_ | |
56 | wiki documentation. | |
57 | ||
58 | .. _`Gerrit Workflow`: http://docs.openstack.org/infra/manual/developers.html#development-workflow | |
59 | ||
60 | .. note:: | |
61 | ||
62 | Pull requests submitted through GitHub will be ignored. |
0 | =============================== | |
1 | castellan | |
2 | =============================== | |
0 | ========= | |
1 | Castellan | |
2 | ========= | |
3 | 3 | |
4 | Generic Key Manager interface for OpenStack | |
4 | Generic Key Manager interface for OpenStack. | |
5 | 5 | |
6 | * Free software: Apache license | |
6 | * License: Apache License, Version 2.0 | |
7 | 7 | * Documentation: http://docs.openstack.org/developer/castellan |
8 | 8 | * Source: http://git.openstack.org/cgit/openstack/castellan |
9 | 9 | * Bugs: http://bugs.launchpad.net/castellan |
10 | ||
11 | Features | |
12 | -------- | |
13 | ||
14 | * TODO |
41 | 41 | if not message_arg: |
42 | 42 | message_arg = self.message |
43 | 43 | try: |
44 | self.message = message_arg.format(**kwargs) | |
44 | self.message = message_arg % kwargs | |
45 | 45 | except Exception as e: |
46 | 46 | if _FATAL_EXCEPTION_FORMAT_ERRORS: |
47 | 47 | raise e |
57 | 57 | |
58 | 58 | class KeyManagerError(CastellanException): |
59 | 59 | message = u._("Key manager error: %(reason)s") |
60 | ||
61 | ||
62 | class ManagedObjectNotFoundError(CastellanException): | |
63 | message = u._("Key not found, uuid: %(uuid)s") |
28 | 28 | class ManagedObject(object): |
29 | 29 | """Base class to represent all managed objects.""" |
30 | 30 | |
31 | def __init__(self, name=None): | |
32 | """Managed Object has a name, defaulted to None.""" | |
33 | self._name = name | |
34 | ||
35 | @property | |
36 | def name(self): | |
37 | """Returns the name. | |
38 | ||
39 | Returns the object's name or None if this object does not have one. | |
40 | """ | |
41 | return self._name | |
42 | ||
31 | 43 | @abc.abstractproperty |
32 | 44 | def format(self): |
33 | 45 | """Returns the encoding format. |
24 | 24 | class OpaqueData(managed_object.ManagedObject): |
25 | 25 | """This class represents opaque data.""" |
26 | 26 | |
27 | def __init__(self, data): | |
27 | def __init__(self, data, name=None): | |
28 | 28 | """Create a new OpaqueData object. |
29 | 29 | |
30 | 30 | Expected type for data is a bytestring. |
31 | 31 | """ |
32 | 32 | self._data = data |
33 | super(OpaqueData, self).__init__(name=name) | |
33 | 34 | |
34 | 35 | @property |
35 | 36 | def format(self): |
42 | 43 | |
43 | 44 | def __eq__(self, other): |
44 | 45 | if isinstance(other, OpaqueData): |
45 | return self._data == other._data | |
46 | return (self._data == other._data and | |
47 | self._name == other._name) | |
46 | 48 | else: |
47 | 49 | return False |
48 | 50 |
24 | 24 | class Passphrase(managed_object.ManagedObject): |
25 | 25 | """This class represents a passphrase.""" |
26 | 26 | |
27 | def __init__(self, passphrase): | |
27 | def __init__(self, passphrase, name=None): | |
28 | 28 | """Create a new Passphrase object. |
29 | 29 | |
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) | |
33 | 34 | |
34 | 35 | @property |
35 | 36 | def format(self): |
42 | 43 | |
43 | 44 | def __eq__(self, other): |
44 | 45 | if isinstance(other, Passphrase): |
45 | return self._passphrase == other._passphrase | |
46 | return (self._passphrase == other._passphrase and | |
47 | self._name == other._name) | |
46 | 48 | else: |
47 | 49 | return False |
48 | 50 |
24 | 24 | class PrivateKey(key.Key): |
25 | 25 | """This class represents private keys.""" |
26 | 26 | |
27 | def __init__(self, algorithm, bit_length, key): | |
27 | def __init__(self, algorithm, bit_length, key, name=None): | |
28 | 28 | """Create a new PrivateKey object. |
29 | 29 | |
30 | 30 | The arguments specify the algorithm and bit length for the asymmetric |
33 | 33 | self._alg = algorithm |
34 | 34 | self._bit_length = bit_length |
35 | 35 | self._key = key |
36 | super(PrivateKey, self).__init__(name=name) | |
36 | 37 | |
37 | 38 | @property |
38 | 39 | def algorithm(self): |
56 | 57 | def __eq__(self, other): |
57 | 58 | if isinstance(other, PrivateKey): |
58 | 59 | return (self._alg == other._alg and |
59 | self._key == other._key) | |
60 | self._bit_length == other._bit_length and | |
61 | self._key == other._key and | |
62 | self._name == other._name) | |
60 | 63 | else: |
61 | 64 | return False |
62 | 65 |
24 | 24 | class PublicKey(key.Key): |
25 | 25 | """This class represents public keys.""" |
26 | 26 | |
27 | def __init__(self, algorithm, bit_length, key): | |
27 | def __init__(self, algorithm, bit_length, key, name=None): | |
28 | 28 | """Create a new PublicKey object. |
29 | 29 | |
30 | 30 | The arguments specify the algorithm and bit length for the asymmetric |
34 | 34 | self._alg = algorithm |
35 | 35 | self._bit_length = bit_length |
36 | 36 | self._key = key |
37 | super(PublicKey, self).__init__(name=name) | |
37 | 38 | |
38 | 39 | @property |
39 | 40 | def algorithm(self): |
57 | 58 | def __eq__(self, other): |
58 | 59 | if isinstance(other, PublicKey): |
59 | 60 | return (self._alg == other._alg and |
60 | self._key == other._key) | |
61 | self._bit_length == other._bit_length and | |
62 | self._key == other._key and | |
63 | self._name == other._name) | |
61 | 64 | else: |
62 | 65 | return False |
63 | 66 |
24 | 24 | class SymmetricKey(key.Key): |
25 | 25 | """This class represents symmetric keys.""" |
26 | 26 | |
27 | def __init__(self, algorithm, bit_length, key): | |
27 | def __init__(self, algorithm, bit_length, key, name=None): | |
28 | 28 | """Create a new SymmetricKey object. |
29 | 29 | |
30 | 30 | The arguments specify the algorithm and bit length for the symmetric |
33 | 33 | self._alg = algorithm |
34 | 34 | self._bit_length = bit_length |
35 | 35 | self._key = key |
36 | super(SymmetricKey, self).__init__(name=name) | |
36 | 37 | |
37 | 38 | @property |
38 | 39 | def algorithm(self): |
57 | 58 | if isinstance(other, SymmetricKey): |
58 | 59 | return (self._alg == other._alg and |
59 | 60 | self._bit_length == other._bit_length and |
60 | self._key == other._key) | |
61 | self._key == other._key and | |
62 | self._name == other._name) | |
61 | 63 | else: |
62 | 64 | return False |
63 | 65 |
24 | 24 | class X509(certificate.Certificate): |
25 | 25 | """This class represents X.509 certificates.""" |
26 | 26 | |
27 | def __init__(self, data): | |
27 | def __init__(self, data, name=None): | |
28 | 28 | """Create a new X509 object. |
29 | 29 | |
30 | 30 | The data should be in a bytestring. |
31 | 31 | """ |
32 | 32 | self._data = data |
33 | super(X509, self).__init__(name=name) | |
33 | 34 | |
34 | 35 | @property |
35 | 36 | def format(self): |
42 | 43 | |
43 | 44 | def __eq__(self, other): |
44 | 45 | if isinstance(other, X509): |
45 | return (self._data == other._data) | |
46 | return (self._data == other._data and | |
47 | self._name == other._name) | |
46 | 48 | else: |
47 | 49 | return False |
48 | 50 |
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 | ||
15 | 14 | from oslo_config import cfg |
16 | 15 | from oslo_utils import importutils |
17 | 16 | |
28 | 27 | conf.register_opts(key_manager_opts, group='key_manager') |
29 | 28 | |
30 | 29 | cls = importutils.import_class(conf.key_manager.api_class) |
31 | return cls(configuration=conf) | |
30 | return cls(configuration=conf)⏎ |
89 | 89 | :param context: the user context for authentication |
90 | 90 | :return: a Barbican Client object |
91 | 91 | :raises Forbidden: if the context is None |
92 | :raises KeyManagerError: if context is missing tenant or | |
93 | tenant is None | |
92 | :raises KeyManagerError: if context is missing tenant or tenant is | |
93 | None or error occurs while creating client | |
94 | 94 | """ |
95 | 95 | |
96 | 96 | # Confirm context is provided, if not raise forbidden |
103 | 103 | msg = u._("Unable to create Barbican Client without tenant " |
104 | 104 | "attribute in context object.") |
105 | 105 | LOG.error(msg) |
106 | raise exception.KeyManagerError(msg) | |
106 | raise exception.KeyManagerError(reason=msg) | |
107 | 107 | |
108 | 108 | if self._barbican_client and self._current_context == context: |
109 | 109 | return self._barbican_client |
119 | 119 | endpoint=self._barbican_endpoint) |
120 | 120 | |
121 | 121 | except Exception as e: |
122 | with excutils.save_and_reraise_exception(): | |
123 | LOG.error(u._LE("Error creating Barbican client: %s"), e) | |
122 | LOG.error(u._LE("Error creating Barbican client: %s"), e) | |
123 | raise exception.KeyManagerError(reason=e) | |
124 | 124 | |
125 | 125 | self._base_url = self._create_base_url(auth, |
126 | 126 | sess, |
157 | 157 | msg = u._LE( |
158 | 158 | "Could not find discovery information for %s") % endpoint |
159 | 159 | LOG.error(msg) |
160 | raise exception.KeyManagerError(msg) | |
160 | raise exception.KeyManagerError(reason=msg) | |
161 | 161 | latest_version = raw_data[-1] |
162 | 162 | api_version = latest_version.get('id') |
163 | 163 | |
165 | 165 | endpoint, api_version) |
166 | 166 | return base_url |
167 | 167 | |
168 | def create_key(self, context, algorithm, length, expiration=None): | |
168 | def create_key(self, context, algorithm, length, | |
169 | expiration=None, name=None): | |
169 | 170 | """Creates a symmetric key. |
170 | 171 | |
171 | 172 | :param context: contains information of the user and the environment |
172 | 173 | for the request (castellan/context.py) |
173 | 174 | :param algorithm: the algorithm associated with the secret |
174 | 175 | :param length: the bit length of the secret |
176 | :param name: the name of the key | |
175 | 177 | :param expiration: the date the key will expire |
176 | 178 | :return: the UUID of the new key |
177 | :raises HTTPAuthError: if key creation fails with 401 | |
178 | :raises HTTPClientError: if key creation failes with 4xx | |
179 | :raises HTTPServerError: if key creation fails with 5xx | |
179 | :raises KeyManagerError: if key creation fails | |
180 | 180 | """ |
181 | 181 | barbican_client = self._get_barbican_client(context) |
182 | 182 | |
183 | 183 | try: |
184 | 184 | key_order = barbican_client.orders.create_key( |
185 | name=name, | |
185 | 186 | algorithm=algorithm, |
186 | 187 | bit_length=length, |
187 | 188 | expiration=expiration) |
191 | 192 | except (barbican_exceptions.HTTPAuthError, |
192 | 193 | barbican_exceptions.HTTPClientError, |
193 | 194 | barbican_exceptions.HTTPServerError) as e: |
194 | with excutils.save_and_reraise_exception(): | |
195 | LOG.error(u._LE("Error creating key: %s"), e) | |
196 | ||
197 | def create_key_pair(self, context, algorithm, length, expiration=None): | |
195 | LOG.error(u._LE("Error creating key: %s"), e) | |
196 | raise exception.KeyManagerError(reason=e) | |
197 | ||
198 | def create_key_pair(self, context, algorithm, length, | |
199 | expiration=None, name=None): | |
198 | 200 | """Creates an asymmetric key pair. |
199 | 201 | |
200 | 202 | :param context: contains information of the user and the environment |
201 | 203 | for the request (castellan/context.py) |
202 | 204 | :param algorithm: the algorithm associated with the secret |
203 | 205 | :param length: the bit length of the secret |
206 | :param name: the name of the key | |
204 | 207 | :param expiration: the date the key will expire |
205 | 208 | :return: the UUIDs of the new key, in the order (private, public) |
206 | 209 | :raises NotImplementedError: until implemented |
207 | :raises HTTPAuthError: if key creation fails with 401 | |
208 | :raises HTTPClientError: if key creation failes with 4xx | |
209 | :raises HTTPServerError: if key creation fails with 5xx | |
210 | :raises KeyManagerError: if key pair creation fails | |
210 | 211 | """ |
211 | 212 | barbican_client = self._get_barbican_client(context) |
212 | 213 | |
214 | 215 | key_pair_order = barbican_client.orders.create_asymmetric( |
215 | 216 | algorithm=algorithm, |
216 | 217 | bit_length=length, |
218 | name=name, | |
217 | 219 | expiration=expiration) |
218 | 220 | |
219 | 221 | order_ref = key_pair_order.submit() |
228 | 230 | except (barbican_exceptions.HTTPAuthError, |
229 | 231 | barbican_exceptions.HTTPClientError, |
230 | 232 | barbican_exceptions.HTTPServerError) as e: |
231 | with excutils.save_and_reraise_exception(): | |
232 | LOG.error(u._LE("Error creating key pair: %s"), e) | |
233 | LOG.error(u._LE("Error creating key pair: %s"), e) | |
234 | raise exception.KeyManagerError(reason=e) | |
233 | 235 | |
234 | 236 | def _get_barbican_object(self, barbican_client, managed_object): |
235 | 237 | """Converts the Castellan managed_object to a Barbican secret.""" |
238 | name = getattr(managed_object, 'name', None) | |
239 | ||
236 | 240 | try: |
237 | 241 | algorithm = managed_object.algorithm |
238 | 242 | bit_length = managed_object.bit_length |
247 | 251 | secret = barbican_client.secrets.create(payload=payload, |
248 | 252 | algorithm=algorithm, |
249 | 253 | bit_length=bit_length, |
254 | name=name, | |
250 | 255 | secret_type=secret_type) |
251 | 256 | return secret |
252 | 257 | |
286 | 291 | |
287 | 292 | :param context: contains information of the user and the environment |
288 | 293 | for the request (castellan/context.py) |
289 | :param managed_object: the unencrypted secret data. Known as "payload" | |
290 | to the barbicanclient api | |
294 | :param managed_object: a secret object with unencrypted payload. | |
295 | Known as "secret" to the barbicanclient api | |
291 | 296 | :param expiration: the expiration time of the secret in ISO 8601 |
292 | 297 | format |
293 | 298 | :returns: the UUID of the stored object |
294 | :raises HTTPAuthError: if object creation fails with 401 | |
295 | :raises HTTPClientError: if object creation failes with 4xx | |
296 | :raises HTTPServerError: if object creation fails with 5xx | |
299 | :raises KeyManagerError: if object store fails | |
297 | 300 | """ |
298 | 301 | barbican_client = self._get_barbican_client(context) |
299 | 302 | |
306 | 309 | except (barbican_exceptions.HTTPAuthError, |
307 | 310 | barbican_exceptions.HTTPClientError, |
308 | 311 | barbican_exceptions.HTTPServerError) as e: |
309 | with excutils.save_and_reraise_exception(): | |
310 | LOG.error(u._LE("Error storing object: %s"), e) | |
311 | ||
312 | def _create_secret_ref(self, key_id): | |
312 | LOG.error(u._LE("Error storing object: %s"), e) | |
313 | raise exception.KeyManagerError(reason=e) | |
314 | ||
315 | def _create_secret_ref(self, object_id): | |
313 | 316 | """Creates the URL required for accessing a secret. |
314 | 317 | |
315 | :param key_id: the UUID of the key to copy | |
318 | :param object_id: the UUID of the key to copy | |
316 | 319 | :return: the URL of the requested secret |
317 | 320 | """ |
318 | if not key_id: | |
321 | if not object_id: | |
319 | 322 | msg = "Key ID is None" |
320 | raise exception.KeyManagerError(msg) | |
323 | raise exception.KeyManagerError(reason=msg) | |
321 | 324 | base_url = self._base_url |
322 | 325 | if base_url[-1] != '/': |
323 | 326 | base_url += '/' |
324 | return urllib.parse.urljoin(base_url, "secrets/" + key_id) | |
327 | return urllib.parse.urljoin(base_url, "secrets/" + object_id) | |
325 | 328 | |
326 | 329 | def _get_active_order(self, barbican_client, order_ref): |
327 | 330 | """Returns the order when it is active. |
355 | 358 | 'num_retries': |
356 | 359 | number_of_retries} |
357 | 360 | LOG.error(msg) |
358 | raise exception.KeyManagerError(msg) | |
361 | raise exception.KeyManagerError(reason=msg) | |
359 | 362 | |
360 | 363 | def _retrieve_secret_uuid(self, secret_ref): |
361 | 364 | """Retrieves the UUID of the secret from the secret_ref. |
420 | 423 | if issubclass(secret_type, key_base_class.Key): |
421 | 424 | return secret_type(secret.algorithm, |
422 | 425 | secret.bit_length, |
423 | secret_data) | |
426 | secret_data, | |
427 | secret.name) | |
424 | 428 | else: |
425 | return secret_type(secret_data) | |
426 | ||
427 | def _get_secret(self, context, key_id): | |
429 | return secret_type(secret_data, | |
430 | secret.name) | |
431 | ||
432 | def _get_secret(self, context, object_id): | |
428 | 433 | """Returns the metadata of the secret. |
429 | 434 | |
430 | 435 | :param context: contains information of the user and the environment |
431 | 436 | for the request (castellan/context.py) |
432 | :param key_id: UUID of the secret | |
437 | :param object_id: UUID of the secret | |
433 | 438 | :return: the secret's metadata |
434 | 439 | :raises HTTPAuthError: if object retrieval fails with 401 |
435 | 440 | :raises HTTPClientError: if object retrieval fails with 4xx |
439 | 444 | barbican_client = self._get_barbican_client(context) |
440 | 445 | |
441 | 446 | try: |
442 | secret_ref = self._create_secret_ref(key_id) | |
447 | secret_ref = self._create_secret_ref(object_id) | |
443 | 448 | return barbican_client.secrets.get(secret_ref) |
444 | 449 | except (barbican_exceptions.HTTPAuthError, |
445 | 450 | barbican_exceptions.HTTPClientError, |
447 | 452 | with excutils.save_and_reraise_exception(): |
448 | 453 | LOG.error(u._LE("Error getting secret metadata: %s"), e) |
449 | 454 | |
455 | def _is_secret_not_found_error(self, error): | |
456 | if (isinstance(error, barbican_exceptions.HTTPClientError) and | |
457 | error.status_code == 404): | |
458 | return True | |
459 | else: | |
460 | return False | |
461 | ||
450 | 462 | def get(self, context, managed_object_id): |
451 | 463 | """Retrieves the specified managed object. |
452 | 464 | |
456 | 468 | for the request (castellan/context.py) |
457 | 469 | :param managed_object_id: the UUID of the object to retrieve |
458 | 470 | :return: SymmetricKey representation of the key |
459 | :raises HTTPAuthError: if object retrieval fails with 401 | |
460 | :raises HTTPClientError: if object retrieval fails with 4xx | |
461 | :raises HTTPServerError: if object retrieval fails with 5xx | |
471 | :raises KeyManagerError: if object retrieval fails | |
472 | :raises ManagedObjectNotFoundError: if object not found | |
462 | 473 | """ |
463 | 474 | try: |
464 | 475 | secret = self._get_secret(context, managed_object_id) |
466 | 477 | except (barbican_exceptions.HTTPAuthError, |
467 | 478 | barbican_exceptions.HTTPClientError, |
468 | 479 | barbican_exceptions.HTTPServerError) as e: |
469 | with excutils.save_and_reraise_exception(): | |
470 | LOG.error(u._LE("Error getting object: %s"), e) | |
480 | LOG.error(u._LE("Error retrieving object: %s"), e) | |
481 | if self._is_secret_not_found_error(e): | |
482 | raise exception.ManagedObjectNotFoundError( | |
483 | uuid=managed_object_id) | |
484 | else: | |
485 | raise exception.KeyManagerError(reason=e) | |
471 | 486 | |
472 | 487 | def delete(self, context, managed_object_id): |
473 | 488 | """Deletes the specified managed object. |
475 | 490 | :param context: contains information of the user and the environment |
476 | 491 | for the request (castellan/context.py) |
477 | 492 | :param managed_object_id: the UUID of the object to delete |
478 | :raises HTTPAuthError: if key deletion fails with 401 | |
479 | :raises HTTPClientError: if key deletion fails with 4xx | |
480 | :raises HTTPServerError: if key deletion fails with 5xx | |
493 | :raises KeyManagerError: if key deletion fails | |
494 | :raises ManagedObjectNotFoundError: if the object could not be found | |
481 | 495 | """ |
482 | 496 | barbican_client = self._get_barbican_client(context) |
483 | 497 | |
487 | 501 | except (barbican_exceptions.HTTPAuthError, |
488 | 502 | barbican_exceptions.HTTPClientError, |
489 | 503 | barbican_exceptions.HTTPServerError) as e: |
490 | with excutils.save_and_reraise_exception(): | |
491 | LOG.error(u._LE("Error deleting object: %s"), e) | |
504 | LOG.error(u._LE("Error deleting object: %s"), e) | |
505 | if self._is_secret_not_found_error(e): | |
506 | raise exception.ManagedObjectNotFoundError( | |
507 | uuid=managed_object_id) | |
508 | else: | |
509 | raise exception.KeyManagerError(reason=e) |
39 | 39 | pass |
40 | 40 | |
41 | 41 | @abc.abstractmethod |
42 | def create_key(self, context, algorithm, length, expiration=None): | |
42 | def create_key(self, context, algorithm, length, | |
43 | expiration=None, name=None): | |
43 | 44 | """Creates a symmetric key. |
44 | 45 | |
45 | 46 | This method creates a symmetric key and returns the key's UUID. If the |
49 | 50 | pass |
50 | 51 | |
51 | 52 | @abc.abstractmethod |
52 | def create_key_pair(self, context, algorithm, length, expiration=None): | |
53 | def create_key_pair(self, context, algorithm, length, | |
54 | expiration=None, name=None): | |
53 | 55 | """Creates an asymmetric key pair. |
54 | 56 | |
55 | 57 | This method creates an asymmetric key pair and returns the pair of key |
28 | 28 | super(NotImplementedKeyManager, self).__init__(configuration) |
29 | 29 | |
30 | 30 | def create_key(self, context, algorithm='AES', length=256, |
31 | expiration=None, **kwargs): | |
31 | expiration=None, name=None, **kwargs): | |
32 | 32 | raise NotImplementedError() |
33 | 33 | |
34 | def create_key_pair(self, context, algorithm, lengthm, expiration=None): | |
34 | def create_key_pair(self, context, algorithm, length, | |
35 | expiration=None, name=None): | |
35 | 36 | raise NotImplementedError() |
36 | 37 | |
37 | 38 | def store(self, context, managed_object, expiration=None, **kwargs): |
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 oslo_config import cfg | |
15 | from oslo_log import log | |
14 | 16 | |
15 | 17 | from castellan import key_manager as km |
16 | 18 | try: |
18 | 20 | except ImportError: |
19 | 21 | bkm = None |
20 | 22 | |
23 | _DEFAULT_LOG_LEVELS = ['castellan=WARN'] | |
24 | ||
25 | _DEFAULT_LOGGING_CONTEXT_FORMAT = ('%(asctime)s.%(msecs)03d %(process)d ' | |
26 | '%(levelname)s %(name)s [%(request_id)s ' | |
27 | '%(user_identity)s] %(instance)s' | |
28 | '%(message)s') | |
29 | ||
21 | 30 | |
22 | 31 | def set_defaults(conf, api_class=None, barbican_endpoint=None, |
23 | barbican_api_version=None): | |
24 | '''Set defaults for configuration values. | |
32 | barbican_api_version=None, auth_endpoint=None, | |
33 | retry_delay=None, number_of_retries=None): | |
34 | """Set defaults for configuration values. | |
25 | 35 | |
26 | 36 | Overrides the default options values. |
27 | ||
28 | 37 | :param conf: Config instance in which to set default options. |
29 | ||
30 | 38 | :param api_class: The full class name of the key manager API class. |
31 | ||
32 | 39 | :param barbican_endpoint: Use this endpoint to connect to Barbican. |
33 | ||
34 | 40 | :param barbican_api_version: Version of the Barbican API. |
35 | ''' | |
41 | :param auth_endpoint: Use this endpoint to connect to Keystone. | |
42 | :param retry_delay: Use this attribute to set retry delay. | |
43 | :param number_of_retries: Use this attribute to set number of retries. | |
44 | """ | |
36 | 45 | conf.register_opts(km.key_manager_opts, group='key_manager') |
37 | 46 | if bkm: |
38 | 47 | conf.register_opts(bkm.barbican_opts, group=bkm.BARBICAN_OPT_GROUP) |
45 | 54 | if bkm is not None and barbican_api_version is not None: |
46 | 55 | conf.set_default('barbican_api_version', barbican_api_version, |
47 | 56 | group=bkm.BARBICAN_OPT_GROUP) |
57 | if bkm is not None and auth_endpoint is not None: | |
58 | conf.set_default('auth_endpoint', auth_endpoint, | |
59 | group=bkm.BARBICAN_OPT_GROUP) | |
60 | ||
61 | if bkm is not None and retry_delay is not None: | |
62 | conf.set_default('retry_delay', retry_delay, | |
63 | group=bkm.BARBICAN_OPT_GROUP) | |
64 | ||
65 | if bkm is not None and number_of_retries is not None: | |
66 | conf.set_default('number_of_retries', number_of_retries, | |
67 | group=bkm.BARBICAN_OPT_GROUP) | |
68 | ||
69 | ||
70 | def enable_logging(conf=None, app_name='castellan'): | |
71 | conf = conf or cfg.CONF | |
72 | ||
73 | log.register_options(conf) | |
74 | log.set_defaults(_DEFAULT_LOGGING_CONTEXT_FORMAT, | |
75 | _DEFAULT_LOG_LEVELS) | |
76 | ||
77 | log.setup(conf, app_name) | |
48 | 78 | |
49 | 79 | |
50 | 80 | def list_opts(): |
51 | '''Returns a list of oslo.config options available in the library. | |
81 | """Returns a list of oslo.config options available in the library. | |
52 | 82 | |
53 | 83 | The returned list includes all oslo.config options which may be registered |
54 | 84 | at runtime by the library. |
62 | 92 | generator to discover the options exposed to users by this library. |
63 | 93 | |
64 | 94 | :returns: a list of (group_name, opts) tuples |
65 | ''' | |
95 | """ | |
66 | 96 | opts = [('key_manager', km.key_manager_opts)] |
67 | 97 | if bkm is not None: |
68 | 98 | opts.append((bkm.BARBICAN_OPT_GROUP, bkm.barbican_opts)) |
0 | #!/bin/bash | |
1 | ||
2 | set -xe | |
3 | ||
4 | CASTELLAN_DIR="$BASE/new/castellan" | |
5 | ||
6 | ||
7 | function generate_testr_results { | |
8 | if [ -f .testrepository/0 ]; then | |
9 | sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit | |
10 | sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit | |
11 | sudo /usr/os-testr-env/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html | |
12 | sudo gzip -9 $BASE/logs/testrepository.subunit | |
13 | sudo gzip -9 $BASE/logs/testr_results.html | |
14 | sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz | |
15 | sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz | |
16 | fi | |
17 | } | |
18 | ||
19 | owner=tempest | |
20 | ||
21 | # Set owner permissions according to job's requirements. | |
22 | cd $CASTELLAN_DIR | |
23 | sudo chown -R $owner:stack $CASTELLAN_DIR | |
24 | ||
25 | testenv=functional | |
26 | ||
27 | sudo -H -u $owner tox -e genconfig | |
28 | ||
29 | if [ ! -d /etc/castellan ]; then | |
30 | sudo mkdir /etc/castellan | |
31 | fi | |
32 | ||
33 | sudo cp $CASTELLAN_DIR/etc/castellan/castellan-functional.conf.sample /etc/castellan/castellan-functional.conf | |
34 | ||
35 | # Run tests | |
36 | echo "Running Castellan $testenv test suite" | |
37 | set +e | |
38 | ||
39 | sudo -H -u $owner tox -e $testenv | |
40 | ||
41 | testr_exit_code=$? | |
42 | set -e | |
43 | ||
44 | # Collect and parse results | |
45 | generate_testr_results | |
46 | exit $testr_exit_code |
20 | 20 | |
21 | 21 | import uuid |
22 | 22 | |
23 | from barbicanclient import exceptions as barbican_exceptions | |
24 | 23 | from keystoneclient.v3 import client |
24 | from oslo_config import cfg | |
25 | 25 | from oslo_context import context |
26 | from oslotest import base | |
26 | 27 | |
27 | 28 | from castellan.common import exception |
28 | from castellan.common.objects import symmetric_key | |
29 | 29 | from castellan.key_manager import barbican_key_manager |
30 | 30 | from castellan.tests.functional import config |
31 | 31 | from castellan.tests.functional.key_manager import test_key_manager |
34 | 34 | CONF = config.get_config() |
35 | 35 | |
36 | 36 | |
37 | class BarbicanKeyManagerTestCase(test_key_manager.KeyManagerTestCase): | |
37 | class BarbicanKeyManagerTestCase(test_key_manager.KeyManagerTestCase, | |
38 | base.BaseTestCase): | |
38 | 39 | |
39 | 40 | def _create_key_manager(self): |
40 | return barbican_key_manager.BarbicanKeyManager() | |
41 | return barbican_key_manager.BarbicanKeyManager(cfg.CONF) | |
41 | 42 | |
42 | 43 | def setUp(self): |
43 | 44 | super(BarbicanKeyManagerTestCase, self).setUp() |
48 | 49 | keystone_client = client.Client(username=username, |
49 | 50 | password=password, |
50 | 51 | project_name=project_name, |
51 | auth_url=auth_url) | |
52 | auth_url=auth_url, | |
53 | project_domain_id='default') | |
54 | project_list = keystone_client.projects.list(name=project_name) | |
55 | ||
52 | 56 | self.ctxt = context.RequestContext( |
53 | auth_token=keystone_client.auth_token) | |
57 | auth_token=keystone_client.auth_token, | |
58 | tenant=project_list[0].id) | |
54 | 59 | |
55 | 60 | def tearDown(self): |
56 | 61 | super(BarbicanKeyManagerTestCase, self).tearDown() |
57 | ||
58 | def test_create_key(self): | |
59 | key_uuid = self.key_mgr.create_key(self.ctxt, | |
60 | algorithm='AES', | |
61 | length=256) | |
62 | self.addCleanup(self.key_mgr.delete_key, self.ctxt, key_uuid) | |
63 | self.assertIsNotNone(key_uuid) | |
64 | 62 | |
65 | 63 | def test_create_null_context(self): |
66 | 64 | self.assertRaises(exception.Forbidden, |
67 | 65 | self.key_mgr.create_key, None, 'AES', 256) |
68 | 66 | |
69 | def test_delete_symmetric_key(self): | |
70 | key_uuid = self.key_mgr.create_key(self.ctxt, | |
71 | algorithm='AES', | |
72 | length=256) | |
73 | self.key_mgr.delete_key(self.ctxt, key_uuid) | |
74 | try: | |
75 | self.key_mgr.get_key(self.ctxt, key_uuid) | |
76 | except barbican_exceptions.HTTPClientError as e: | |
77 | self.assertEqual(404, e.status_code) | |
78 | else: | |
79 | self.fail('No exception when deleting non-existent key') | |
67 | def test_create_key_pair_null_context(self): | |
68 | self.assertRaises(exception.Forbidden, | |
69 | self.key_mgr.create_key_pair, None, 'RSA', 2048) | |
80 | 70 | |
81 | 71 | def test_delete_null_context(self): |
82 | key_uuid = self.key_mgr.create_key(self.ctxt, | |
83 | algorithm='AES', | |
84 | length=256) | |
85 | self.addCleanup(self.key_mgr.delete_key, self.ctxt, key_uuid) | |
72 | key_uuid = self._get_valid_object_uuid( | |
73 | test_key_manager._get_test_symmetric_key()) | |
74 | self.addCleanup(self.key_mgr.delete, self.ctxt, key_uuid) | |
86 | 75 | self.assertRaises(exception.Forbidden, |
87 | self.key_mgr.delete_key, None, key_uuid) | |
76 | self.key_mgr.delete, None, key_uuid) | |
88 | 77 | |
89 | def test_delete_null_key(self): | |
78 | def test_delete_null_object(self): | |
90 | 79 | self.assertRaises(exception.KeyManagerError, |
91 | self.key_mgr.delete_key, self.ctxt, None) | |
80 | self.key_mgr.delete, self.ctxt, None) | |
92 | 81 | |
93 | def test_delete_unknown_key(self): | |
94 | bad_key_uuid = str(uuid.uuid4()) | |
95 | self.assertRaises(barbican_exceptions.HTTPClientError, | |
96 | self.key_mgr.delete_key, self.ctxt, bad_key_uuid) | |
97 | ||
98 | def test_get_key(self): | |
99 | secret_key = b'\x01\x02\xA0\xB3' | |
100 | key = symmetric_key.SymmetricKey('AES', secret_key) | |
101 | ||
102 | uuid = self.key_mgr.store_key(self.ctxt, key) | |
103 | self.addCleanup(self.key_mgr.delete_key, self.ctxt, uuid) | |
104 | ||
105 | retrieved_key = self.key_mgr.get_key(self.ctxt, uuid) | |
106 | self.assertEqual(key.get_encoded(), retrieved_key.get_encoded()) | |
82 | def test_delete_unknown_object(self): | |
83 | unknown_uuid = str(uuid.uuid4()) | |
84 | self.assertRaises(exception.ManagedObjectNotFoundError, | |
85 | self.key_mgr.delete, self.ctxt, unknown_uuid) | |
107 | 86 | |
108 | 87 | def test_get_null_context(self): |
109 | key_uuid = self.key_mgr.create_key(self.ctxt, | |
110 | algorithm='AES', | |
111 | length=256) | |
88 | key_uuid = self._get_valid_object_uuid( | |
89 | test_key_manager._get_test_symmetric_key()) | |
112 | 90 | self.assertRaises(exception.Forbidden, |
113 | self.key_mgr.get_key, None, key_uuid) | |
91 | self.key_mgr.get, None, key_uuid) | |
114 | 92 | |
115 | def test_get_null_key(self): | |
116 | key_uuid = self.key_mgr.create_key(self.ctxt, | |
117 | algorithm='AES', | |
118 | length=256) | |
119 | self.addCleanup(self.key_mgr.delete_key, self.ctxt, key_uuid) | |
93 | def test_get_null_object(self): | |
120 | 94 | self.assertRaises(exception.KeyManagerError, |
121 | self.key_mgr.get_key, self.ctxt, None) | |
95 | self.key_mgr.get, self.ctxt, None) | |
122 | 96 | |
123 | 97 | def test_get_unknown_key(self): |
124 | key_uuid = self.key_mgr.create_key(self.ctxt, | |
125 | algorithm='AES', | |
126 | length=256) | |
127 | self.addCleanup(self.key_mgr.delete_key, self.ctxt, key_uuid) | |
128 | 98 | bad_key_uuid = str(uuid.uuid4()) |
129 | self.assertRaises(barbican_exceptions.HTTPClientError, | |
130 | self.key_mgr.get_key, self.ctxt, bad_key_uuid) | |
131 | ||
132 | def test_store(self): | |
133 | secret_key = b'\x01\x02\xA0\xB3' | |
134 | key = symmetric_key.SymmetricKey('AES', secret_key) | |
135 | ||
136 | uuid = self.key_mgr.store_key(self.ctxt, key) | |
137 | self.addCleanup(self.key_mgr.delete_key, self.ctxt, uuid) | |
138 | ||
139 | retrieved_key = self.key_mgr.get_key(self.ctxt, uuid) | |
140 | self.assertEqual(key.get_encoded(), retrieved_key.get_encoded()) | |
99 | self.assertRaises(exception.ManagedObjectNotFoundError, | |
100 | self.key_mgr.get, self.ctxt, bad_key_uuid) | |
141 | 101 | |
142 | 102 | def test_store_null_context(self): |
143 | secret_key = b'\x01\x02\xA0\xB3' | |
144 | key = symmetric_key.SymmetricKey('AES', secret_key) | |
103 | key = test_key_manager._get_test_symmetric_key() | |
145 | 104 | |
146 | 105 | self.assertRaises(exception.Forbidden, |
147 | self.key_mgr.store_key, None, key) | |
106 | self.key_mgr.store, None, key) |
13 | 13 | # under the License. |
14 | 14 | |
15 | 15 | """ |
16 | Test cases for the key manager. | |
16 | Test cases for a key manager. | |
17 | ||
18 | These test cases should pass against any key manager. | |
17 | 19 | """ |
18 | 20 | |
19 | from castellan.tests import base | |
21 | from castellan.common import exception | |
22 | from castellan.common.objects import opaque_data | |
23 | from castellan.common.objects import passphrase | |
24 | from castellan.common.objects import private_key | |
25 | from castellan.common.objects import public_key | |
26 | from castellan.common.objects import symmetric_key | |
27 | from castellan.common.objects import x_509 | |
28 | from castellan.tests import utils | |
20 | 29 | |
21 | 30 | |
22 | class KeyManagerTestCase(base.TestCase): | |
31 | def _get_test_symmetric_key(): | |
32 | key_bytes = bytes(utils.get_symmetric_key()) | |
33 | bit_length = 128 | |
34 | key = symmetric_key.SymmetricKey('AES', bit_length, key_bytes) | |
35 | return key | |
36 | ||
37 | ||
38 | def _get_test_public_key(): | |
39 | key_bytes = bytes(utils.get_public_key_der()) | |
40 | bit_length = 2048 | |
41 | key = public_key.PublicKey('RSA', bit_length, key_bytes) | |
42 | return key | |
43 | ||
44 | ||
45 | def _get_test_private_key(): | |
46 | key_bytes = bytes(utils.get_private_key_der()) | |
47 | bit_length = 2048 | |
48 | key = private_key.PrivateKey('RSA', bit_length, key_bytes) | |
49 | return key | |
50 | ||
51 | ||
52 | def _get_test_certificate(): | |
53 | data = bytes(utils.get_certificate_der()) | |
54 | cert = x_509.X509(data) | |
55 | return cert | |
56 | ||
57 | ||
58 | def _get_test_opaque_data(): | |
59 | data = bytes(b'opaque data') | |
60 | opaque_object = opaque_data.OpaqueData(data) | |
61 | return opaque_object | |
62 | ||
63 | ||
64 | def _get_test_passphrase(): | |
65 | data = bytes(b'passphrase') | |
66 | passphrase_object = passphrase.Passphrase(data) | |
67 | return passphrase_object | |
68 | ||
69 | ||
70 | @utils.parameterized_test_case | |
71 | class KeyManagerTestCase(object): | |
23 | 72 | |
24 | 73 | def _create_key_manager(self): |
25 | 74 | raise NotImplementedError() |
26 | 75 | |
27 | 76 | def setUp(self): |
28 | 77 | super(KeyManagerTestCase, self).setUp() |
78 | self.key_mgr = self._create_key_manager() | |
29 | 79 | |
30 | self.key_mgr = self._create_key_manager() | |
80 | def _get_valid_object_uuid(self, managed_object): | |
81 | object_uuid = self.key_mgr.store(self.ctxt, managed_object) | |
82 | self.assertIsNotNone(object_uuid) | |
83 | return object_uuid | |
84 | ||
85 | def test_create_key(self): | |
86 | key_uuid = self.key_mgr.create_key(self.ctxt, | |
87 | algorithm='AES', | |
88 | length=256) | |
89 | self.addCleanup(self.key_mgr.delete, self.ctxt, key_uuid) | |
90 | self.assertIsNotNone(key_uuid) | |
91 | ||
92 | def test_create_key_pair(self): | |
93 | private_key_uuid, public_key_uuid = self.key_mgr.create_key_pair( | |
94 | self.ctxt, | |
95 | algorithm='RSA', | |
96 | length=2048) | |
97 | ||
98 | self.addCleanup(self.key_mgr.delete, self.ctxt, private_key_uuid) | |
99 | self.addCleanup(self.key_mgr.delete, self.ctxt, public_key_uuid) | |
100 | ||
101 | self.assertIsNotNone(private_key_uuid) | |
102 | self.assertIsNotNone(public_key_uuid) | |
103 | self.assertNotEqual(private_key_uuid, public_key_uuid) | |
104 | ||
105 | @utils.parameterized_dataset({ | |
106 | 'symmetric_key': [_get_test_symmetric_key()], | |
107 | 'public_key': [_get_test_public_key()], | |
108 | 'private_key': [_get_test_private_key()], | |
109 | 'certificate': [_get_test_certificate()], | |
110 | 'passphrase': [_get_test_passphrase()], | |
111 | 'opaque_data': [_get_test_opaque_data()], | |
112 | }) | |
113 | def test_delete(self, managed_object): | |
114 | object_uuid = self._get_valid_object_uuid(managed_object) | |
115 | self.key_mgr.delete(self.ctxt, object_uuid) | |
116 | try: | |
117 | self.key_mgr.get(self.ctxt, object_uuid) | |
118 | except exception.ManagedObjectNotFoundError: | |
119 | pass | |
120 | else: | |
121 | self.fail('No exception when deleting non-existent key') | |
122 | ||
123 | @utils.parameterized_dataset({ | |
124 | 'symmetric_key': [_get_test_symmetric_key()], | |
125 | 'public_key': [_get_test_public_key()], | |
126 | 'private_key': [_get_test_private_key()], | |
127 | 'certificate': [_get_test_certificate()], | |
128 | 'passphrase': [_get_test_passphrase()], | |
129 | 'opaque_data': [_get_test_opaque_data()], | |
130 | }) | |
131 | def test_get(self, managed_object): | |
132 | uuid = self._get_valid_object_uuid(managed_object) | |
133 | self.addCleanup(self.key_mgr.delete, self.ctxt, uuid) | |
134 | ||
135 | retrieved_object = self.key_mgr.get(self.ctxt, uuid) | |
136 | self.assertEqual(managed_object.get_encoded(), | |
137 | retrieved_object.get_encoded()) | |
138 | ||
139 | @utils.parameterized_dataset({ | |
140 | 'symmetric_key': [_get_test_symmetric_key()], | |
141 | 'public_key': [_get_test_public_key()], | |
142 | 'private_key': [_get_test_private_key()], | |
143 | 'certificate': [_get_test_certificate()], | |
144 | 'passphrase': [_get_test_passphrase()], | |
145 | 'opaque_data': [_get_test_opaque_data()], | |
146 | }) | |
147 | def test_store(self, managed_object): | |
148 | uuid = self.key_mgr.store(self.ctxt, managed_object) | |
149 | self.addCleanup(self.key_mgr.delete, self.ctxt, uuid) | |
150 | ||
151 | retrieved_object = self.key_mgr.get(self.ctxt, uuid) | |
152 | self.assertEqual(managed_object.get_encoded(), | |
153 | retrieved_object.get_encoded()) |
63 | 63 | return hex_encoded |
64 | 64 | |
65 | 65 | def _generate_key(self, **kwargs): |
66 | name = kwargs.get('name', None) | |
66 | 67 | key_length = kwargs.get('key_length', 256) |
67 | 68 | _hex = self._generate_hex_key(key_length) |
68 | 69 | return sym_key.SymmetricKey( |
69 | 70 | 'AES', |
70 | 71 | key_length, |
71 | bytes(binascii.unhexlify(_hex))) | |
72 | bytes(binascii.unhexlify(_hex)), | |
73 | name) | |
72 | 74 | |
73 | 75 | def create_key(self, context, **kwargs): |
74 | 76 | """Creates a symmetric key. |
83 | 85 | key = self._generate_key(**kwargs) |
84 | 86 | return self.store(context, key) |
85 | 87 | |
86 | def _generate_public_and_private_key(self, length): | |
88 | def _generate_public_and_private_key(self, length, name): | |
87 | 89 | crypto_private_key = rsa.generate_private_key( |
88 | 90 | public_exponent=65537, |
89 | 91 | key_size=length, |
103 | 105 | private_key = pri_key.PrivateKey( |
104 | 106 | algorithm='RSA', |
105 | 107 | bit_length=length, |
106 | key=bytearray(private_der)) | |
108 | key=bytearray(private_der), | |
109 | name=name) | |
107 | 110 | |
108 | 111 | public_key = pub_key.PublicKey( |
109 | 112 | algorithm='RSA', |
110 | 113 | bit_length=length, |
111 | key=bytearray(public_der)) | |
114 | key=bytearray(public_der), | |
115 | name=name) | |
112 | 116 | |
113 | 117 | return private_key, public_key |
114 | 118 | |
115 | def create_key_pair(self, context, algorithm, length, expiration=None): | |
119 | def create_key_pair(self, context, algorithm, length, | |
120 | expiration=None, name=None): | |
116 | 121 | """Creates an asymmetric key pair. |
117 | 122 | |
118 | 123 | This implementation returns UUIDs for the created keys in the order: |
133 | 138 | length, valid_lengths) |
134 | 139 | raise ValueError(msg) |
135 | 140 | |
136 | private_key, public_key = self._generate_public_and_private_key(length) | |
141 | private_key, public_key = self._generate_public_and_private_key(length, | |
142 | name) | |
137 | 143 | |
138 | 144 | private_key_uuid = self.store(context, private_key) |
139 | 145 | public_key_uuid = self.store(context, public_key) |
105 | 105 | self.mock_barbican.orders.create_key.return_value = key_order |
106 | 106 | key_order.submit = mock.Mock( |
107 | 107 | side_effect=barbican_exceptions.HTTPClientError('test error')) |
108 | self.assertRaises(barbican_exceptions.HTTPClientError, | |
108 | self.assertRaises(exception.KeyManagerError, | |
109 | 109 | self.key_mgr.create_key, self.ctxt, 'AES', 256) |
110 | 110 | |
111 | 111 | def test_create_key_pair(self): |
158 | 158 | self.mock_barbican.orders.create_asymmetric.return_value = asym_order |
159 | 159 | asym_order.submit = mock.Mock( |
160 | 160 | side_effect=barbican_exceptions.HTTPClientError('test error')) |
161 | self.assertRaises(barbican_exceptions.HTTPClientError, | |
161 | self.assertRaises(exception.KeyManagerError, | |
162 | 162 | self.key_mgr.create_key_pair, self.ctxt, 'RSA', 2048) |
163 | 163 | |
164 | 164 | def test_delete_null_context(self): |
177 | 177 | def test_delete_with_error(self): |
178 | 178 | self.mock_barbican.secrets.delete = mock.Mock( |
179 | 179 | side_effect=barbican_exceptions.HTTPClientError('test error')) |
180 | self.assertRaises(barbican_exceptions.HTTPClientError, | |
180 | self.assertRaises(exception.KeyManagerError, | |
181 | 181 | self.key_mgr.delete, self.ctxt, self.key_id) |
182 | 182 | |
183 | 183 | def test_get_key(self): |
185 | 185 | original_secret_metadata.algorithm = mock.sentinel.alg |
186 | 186 | original_secret_metadata.bit_length = mock.sentinel.bit |
187 | 187 | original_secret_metadata.secret_type = 'symmetric' |
188 | ||
189 | key_name = 'my key' | |
190 | original_secret_metadata.name = key_name | |
191 | ||
188 | 192 | original_secret_data = b'test key' |
189 | 193 | original_secret_metadata.payload = original_secret_data |
190 | 194 | |
192 | 196 | key = self.key_mgr.get(self.ctxt, self.key_id) |
193 | 197 | |
194 | 198 | self.get.assert_called_once_with(self.secret_ref) |
199 | self.assertEqual(key_name, key.name) | |
195 | 200 | self.assertEqual(original_secret_data, key.get_encoded()) |
196 | 201 | |
197 | 202 | def test_get_null_context(self): |
206 | 211 | def test_get_with_error(self): |
207 | 212 | self.mock_barbican.secrets.get = mock.Mock( |
208 | 213 | side_effect=barbican_exceptions.HTTPClientError('test error')) |
209 | self.assertRaises(barbican_exceptions.HTTPClientError, | |
214 | self.assertRaises(exception.KeyManagerError, | |
210 | 215 | self.key_mgr.get, self.ctxt, self.key_id) |
211 | 216 | |
212 | 217 | def test_store_key(self): |
227 | 232 | |
228 | 233 | self.create.assert_called_once_with(algorithm='AES', |
229 | 234 | bit_length=key_length, |
235 | name=None, | |
230 | 236 | payload=secret_key, |
237 | secret_type='symmetric') | |
238 | self.assertEqual(self.key_id, returned_uuid) | |
239 | ||
240 | def test_store_key_with_name(self): | |
241 | # Create Key to store | |
242 | secret_key = bytes(b'\x01\x02\xA0\xB3') | |
243 | key_length = len(secret_key) * 8 | |
244 | secret_name = 'My Secret' | |
245 | _key = sym_key.SymmetricKey('AES', | |
246 | key_length, | |
247 | secret_key, | |
248 | secret_name) | |
249 | ||
250 | # Define the return values | |
251 | secret = mock.Mock() | |
252 | self.create.return_value = secret | |
253 | secret.store.return_value = self.secret_ref | |
254 | ||
255 | # Store the Key | |
256 | returned_uuid = self.key_mgr.store(self.ctxt, _key) | |
257 | ||
258 | self.create.assert_called_once_with(algorithm='AES', | |
259 | bit_length=key_length, | |
260 | payload=secret_key, | |
261 | name=secret_name, | |
231 | 262 | secret_type='symmetric') |
232 | 263 | self.assertEqual(self.key_id, returned_uuid) |
233 | 264 | |
244 | 275 | _key = sym_key.SymmetricKey('AES', |
245 | 276 | key_length, |
246 | 277 | secret_key) |
247 | self.assertRaises(barbican_exceptions.HTTPClientError, | |
278 | self.assertRaises(exception.KeyManagerError, | |
248 | 279 | self.key_mgr.store, self.ctxt, _key) |
249 | 280 | |
250 | 281 | def test_get_active_order(self): |
65 | 65 | key = self.key_mgr.get(self.context, key_id) |
66 | 66 | self.assertEqual(length / 8, len(key.get_encoded())) |
67 | 67 | |
68 | def test_create_key_with_name(self): | |
69 | name = 'my key' | |
70 | key_id = self.key_mgr.create_key(self.context, name=name) | |
71 | key = self.key_mgr.get(self.context, key_id) | |
72 | self.assertEqual(name, key.name) | |
73 | ||
68 | 74 | def test_create_key_null_context(self): |
69 | 75 | self.assertRaises(exception.Forbidden, |
70 | 76 | self.key_mgr.create_key, None) |
71 | 77 | |
72 | 78 | def test_create_key_pair(self): |
73 | 79 | for length in [2048, 3072, 4096]: |
80 | name = str(length) + ' key' | |
74 | 81 | private_key_uuid, public_key_uuid = self.key_mgr.create_key_pair( |
75 | self.context, 'RSA', length) | |
82 | self.context, 'RSA', length, name=name) | |
76 | 83 | |
77 | 84 | private_key = self.key_mgr.get(self.context, private_key_uuid) |
78 | 85 | public_key = self.key_mgr.get(self.context, public_key_uuid) |
79 | 86 | |
80 | 87 | crypto_private_key = get_cryptography_private_key(private_key) |
81 | 88 | crypto_public_key = get_cryptography_public_key(public_key) |
89 | ||
90 | self.assertEqual(name, private_key.name) | |
91 | self.assertEqual(name, public_key.name) | |
82 | 92 | |
83 | 93 | self.assertEqual(length, crypto_private_key.key_size) |
84 | 94 | self.assertEqual(length, crypto_public_key.key_size) |
23 | 23 | class OpaqueDataTestCase(base.TestCase): |
24 | 24 | |
25 | 25 | def _create_data(self): |
26 | return opaque_data.OpaqueData(self.data) | |
26 | return opaque_data.OpaqueData(self.data, self.name) | |
27 | 27 | |
28 | 28 | def setUp(self): |
29 | 29 | self.data = bytes(b"secret opaque data") |
30 | self.name = 'my opaque' | |
30 | 31 | self.opaque_data = self._create_data() |
31 | 32 | |
32 | 33 | super(OpaqueDataTestCase, self).setUp() |
37 | 38 | def test_get_encoded(self): |
38 | 39 | self.assertEqual(self.data, self.opaque_data.get_encoded()) |
39 | 40 | |
41 | def test_get_name(self): | |
42 | self.assertEqual(self.name, self.opaque_data.name) | |
43 | ||
40 | 44 | def test___eq__(self): |
41 | 45 | self.assertTrue(self.opaque_data == self.opaque_data) |
46 | self.assertTrue(self.opaque_data is self.opaque_data) | |
42 | 47 | |
43 | 48 | self.assertFalse(self.opaque_data is None) |
44 | 49 | self.assertFalse(None == self.opaque_data) |
45 | 50 | |
46 | def test___ne__(self): | |
47 | self.assertFalse(self.opaque_data != self.opaque_data) | |
51 | other_opaque_data = opaque_data.OpaqueData(self.data, self.name) | |
52 | self.assertTrue(self.opaque_data == other_opaque_data) | |
53 | self.assertFalse(self.opaque_data is other_opaque_data) | |
48 | 54 | |
55 | def test___ne___none(self): | |
49 | 56 | self.assertTrue(self.opaque_data is not None) |
50 | 57 | self.assertTrue(None != self.opaque_data) |
58 | ||
59 | def test___ne___data(self): | |
60 | other_opaque = opaque_data.OpaqueData(b'other data', self.name) | |
61 | self.assertTrue(self.opaque_data != other_opaque) | |
62 | ||
63 | def test___ne___name(self): | |
64 | other_opaque = opaque_data.OpaqueData(self.data, "other opaque") | |
65 | self.assertTrue(self.opaque_data != other_opaque) |
23 | 23 | class PassphraseTestCase(base.TestCase): |
24 | 24 | |
25 | 25 | def _create_passphrase(self): |
26 | return passphrase.Passphrase(self.passphrase_data) | |
26 | return passphrase.Passphrase(self.passphrase_data, | |
27 | self.name) | |
27 | 28 | |
28 | 29 | def setUp(self): |
29 | 30 | self.passphrase_data = bytes(b"secret passphrase") |
31 | self.name = 'my phrase' | |
30 | 32 | self.passphrase = self._create_passphrase() |
31 | 33 | |
32 | 34 | super(PassphraseTestCase, self).setUp() |
37 | 39 | def test_get_encoded(self): |
38 | 40 | self.assertEqual(self.passphrase_data, self.passphrase.get_encoded()) |
39 | 41 | |
42 | def test_get_name(self): | |
43 | self.assertEqual(self.name, self.passphrase.name) | |
44 | ||
40 | 45 | def test___eq__(self): |
41 | 46 | self.assertTrue(self.passphrase == self.passphrase) |
47 | self.assertTrue(self.passphrase is self.passphrase) | |
42 | 48 | |
43 | 49 | self.assertFalse(self.passphrase is None) |
44 | 50 | self.assertFalse(None == self.passphrase) |
45 | 51 | |
46 | def test___ne__(self): | |
47 | self.assertFalse(self.passphrase != self.passphrase) | |
52 | other_passphrase = passphrase.Passphrase(self.passphrase_data, | |
53 | self.name) | |
54 | self.assertTrue(self.passphrase == other_passphrase) | |
55 | self.assertFalse(self.passphrase is other_passphrase) | |
48 | 56 | |
57 | def test___ne___none(self): | |
49 | 58 | self.assertTrue(self.passphrase is not None) |
50 | 59 | self.assertTrue(None != self.passphrase) |
60 | ||
61 | def test___ne___data(self): | |
62 | other_phrase = passphrase.Passphrase(b"other passphrase", self.name) | |
63 | self.assertTrue(self.passphrase != other_phrase) | |
64 | ||
65 | def test___ne__name(self): | |
66 | other_phrase = passphrase.Passphrase(self.passphrase_data, | |
67 | "other phrase") | |
68 | self.assertTrue(self.passphrase != other_phrase) |
26 | 26 | def _create_key(self): |
27 | 27 | return private_key.PrivateKey(self.algorithm, |
28 | 28 | self.length, |
29 | self.encoded) | |
29 | self.encoded, | |
30 | self.name) | |
30 | 31 | |
31 | 32 | def setUp(self): |
32 | 33 | self.algorithm = 'RSA' |
33 | 34 | self.length = 2048 |
34 | 35 | self.encoded = bytes(utils.get_private_key_der()) |
36 | self.name = 'my key' | |
35 | 37 | |
36 | 38 | super(PrivateKeyTestCase, self).setUp() |
37 | 39 | |
41 | 43 | def test_get_length(self): |
42 | 44 | self.assertEqual(self.length, self.key.bit_length) |
43 | 45 | |
46 | def test_get_name(self): | |
47 | self.assertEqual(self.name, self.key.name) | |
48 | ||
44 | 49 | def test_get_format(self): |
45 | 50 | self.assertEqual('PKCS8', self.key.format) |
46 | 51 | |
49 | 54 | |
50 | 55 | def test___eq__(self): |
51 | 56 | self.assertTrue(self.key == self.key) |
57 | self.assertTrue(self.key is self.key) | |
52 | 58 | |
53 | 59 | self.assertFalse(self.key is None) |
54 | 60 | self.assertFalse(None == self.key) |
55 | 61 | |
56 | def test___ne__(self): | |
57 | self.assertFalse(self.key != self.key) | |
62 | other_key = private_key.PrivateKey(self.algorithm, | |
63 | self.length, | |
64 | self.encoded, | |
65 | self.name) | |
66 | self.assertTrue(self.key == other_key) | |
67 | self.assertFalse(self.key is other_key) | |
58 | 68 | |
69 | def test___ne___none(self): | |
59 | 70 | self.assertTrue(self.key is not None) |
60 | 71 | self.assertTrue(None != self.key) |
72 | ||
73 | def test___ne___algorithm(self): | |
74 | other_key = private_key.PrivateKey('DSA', | |
75 | self.length, | |
76 | self.encoded, | |
77 | self.name) | |
78 | self.assertTrue(self.key != other_key) | |
79 | ||
80 | def test___ne___length(self): | |
81 | other_key = private_key.PrivateKey(self.algorithm, | |
82 | 4096, | |
83 | self.encoded, | |
84 | self.name) | |
85 | self.assertTrue(self.key != other_key) | |
86 | ||
87 | def test___ne___encoded(self): | |
88 | different_encoded = bytes(utils.get_private_key_der()) + b'\x00' | |
89 | other_key = private_key.PrivateKey(self.algorithm, | |
90 | self.length, | |
91 | different_encoded, | |
92 | self.name) | |
93 | self.assertTrue(self.key != other_key) | |
94 | ||
95 | def test___ne___name(self): | |
96 | other_key = private_key.PrivateKey(self.algorithm, | |
97 | self.length, | |
98 | self.encoded, | |
99 | 'other key') | |
100 | self.assertTrue(self.key != other_key) |
24 | 24 | class PublicKeyTestCase(base.KeyTestCase): |
25 | 25 | |
26 | 26 | def _create_key(self): |
27 | return public_key.PublicKey(self.algorithm, self.length, self.encoded) | |
27 | return public_key.PublicKey(self.algorithm, | |
28 | self.length, | |
29 | self.encoded, | |
30 | self.name) | |
28 | 31 | |
29 | 32 | def setUp(self): |
30 | 33 | self.algorithm = 'RSA' |
31 | 34 | self.length = 2048 |
32 | 35 | self.encoded = bytes(utils.get_public_key_der()) |
36 | self.name = 'my key' | |
33 | 37 | |
34 | 38 | super(PublicKeyTestCase, self).setUp() |
35 | 39 | |
39 | 43 | def test_get_length(self): |
40 | 44 | self.assertEqual(self.length, self.key.bit_length) |
41 | 45 | |
46 | def test_get_name(self): | |
47 | self.assertEqual(self.name, self.key.name) | |
48 | ||
42 | 49 | def test_get_format(self): |
43 | 50 | self.assertEqual('SubjectPublicKeyInfo', self.key.format) |
44 | 51 | |
47 | 54 | |
48 | 55 | def test___eq__(self): |
49 | 56 | self.assertTrue(self.key == self.key) |
57 | self.assertTrue(self.key is self.key) | |
50 | 58 | |
51 | 59 | self.assertFalse(self.key is None) |
52 | 60 | self.assertFalse(None == self.key) |
53 | 61 | |
54 | def test___ne__(self): | |
55 | self.assertFalse(self.key != self.key) | |
62 | other_key = public_key.PublicKey(self.algorithm, | |
63 | self.length, | |
64 | self.encoded, | |
65 | self.name) | |
66 | self.assertTrue(self.key == other_key) | |
67 | self.assertFalse(self.key is other_key) | |
56 | 68 | |
69 | def test___ne___none(self): | |
57 | 70 | self.assertTrue(self.key is not None) |
58 | 71 | self.assertTrue(None != self.key) |
72 | ||
73 | def test___ne___algorithm(self): | |
74 | other_key = public_key.PublicKey('DSA', | |
75 | self.length, | |
76 | self.encoded, | |
77 | self.name) | |
78 | self.assertTrue(self.key != other_key) | |
79 | ||
80 | def test___ne___length(self): | |
81 | other_key = public_key.PublicKey(self.algorithm, | |
82 | 4096, | |
83 | self.encoded, | |
84 | self.name) | |
85 | self.assertTrue(self.key != other_key) | |
86 | ||
87 | def test___ne___encoded(self): | |
88 | different_encoded = bytes(utils.get_public_key_der()) + b'\x00' | |
89 | other_key = public_key.PublicKey(self.algorithm, | |
90 | self.length, | |
91 | different_encoded, | |
92 | self.name) | |
93 | self.assertTrue(self.key != other_key) | |
94 | ||
95 | def test___ne__name(self): | |
96 | other_key = public_key.PublicKey(self.algorithm, | |
97 | self.length, | |
98 | self.encoded, | |
99 | 'other key') | |
100 | self.assertTrue(self.key != other_key) |
25 | 25 | def _create_key(self): |
26 | 26 | return sym_key.SymmetricKey(self.algorithm, |
27 | 27 | self.bit_length, |
28 | self.encoded) | |
28 | self.encoded, | |
29 | self.name) | |
29 | 30 | |
30 | 31 | def setUp(self): |
31 | 32 | self.algorithm = 'AES' |
32 | 33 | self.encoded = bytes(b'0' * 64) |
33 | 34 | self.bit_length = len(self.encoded) * 8 |
35 | self.name = 'my key' | |
34 | 36 | |
35 | 37 | super(SymmetricKeyTestCase, self).setUp() |
36 | 38 | |
37 | 39 | def test_get_format(self): |
38 | 40 | self.assertEqual('RAW', self.key.format) |
41 | ||
42 | def test_get_name(self): | |
43 | self.assertEqual(self.name, self.key.name) | |
39 | 44 | |
40 | 45 | def test_get_encoded(self): |
41 | 46 | self.assertEqual(self.encoded, self.key.get_encoded()) |
48 | 53 | |
49 | 54 | def test___eq__(self): |
50 | 55 | self.assertTrue(self.key == self.key) |
56 | self.assertTrue(self.key is self.key) | |
51 | 57 | |
52 | 58 | self.assertFalse(self.key is None) |
53 | 59 | self.assertFalse(None == self.key) |
54 | 60 | |
55 | def test___ne__(self): | |
56 | self.assertFalse(self.key != self.key) | |
61 | other_key = sym_key.SymmetricKey(self.algorithm, | |
62 | self.bit_length, | |
63 | self.encoded, | |
64 | self.name) | |
65 | self.assertTrue(self.key == other_key) | |
66 | self.assertFalse(self.key is other_key) | |
57 | 67 | |
68 | def test___ne___none(self): | |
58 | 69 | self.assertTrue(self.key is not None) |
59 | 70 | self.assertTrue(None != self.key) |
71 | ||
72 | def test___ne___algorithm(self): | |
73 | other_key = sym_key.SymmetricKey('DES', | |
74 | self.bit_length, | |
75 | self.encoded, | |
76 | self.name) | |
77 | self.assertTrue(self.key != other_key) | |
78 | ||
79 | def test___ne___length(self): | |
80 | other_key = sym_key.SymmetricKey(self.algorithm, | |
81 | self.bit_length * 2, | |
82 | self.encoded, | |
83 | self.name) | |
84 | self.assertTrue(self.key != other_key) | |
85 | ||
86 | def test___ne___encoded(self): | |
87 | different_encoded = self.encoded * 2 | |
88 | other_key = sym_key.SymmetricKey(self.algorithm, | |
89 | self.bit_length, | |
90 | different_encoded, | |
91 | self.name) | |
92 | self.assertTrue(self.key != other_key) | |
93 | ||
94 | def test___ne___name(self): | |
95 | other_key = sym_key.SymmetricKey(self.algorithm, | |
96 | self.bit_length, | |
97 | self.encoded, | |
98 | 'other key') | |
99 | self.assertTrue(self.key != other_key) |
24 | 24 | class X509TestCase(base.CertificateTestCase): |
25 | 25 | |
26 | 26 | def _create_cert(self): |
27 | return x_509.X509(self.data) | |
27 | return x_509.X509(self.data, self.name) | |
28 | 28 | |
29 | 29 | def setUp(self): |
30 | 30 | self.data = utils.get_certificate_der() |
31 | self.name = 'my cert' | |
31 | 32 | |
32 | 33 | super(X509TestCase, self).setUp() |
33 | 34 | |
34 | 35 | def test_get_format(self): |
35 | 36 | self.assertEqual('X.509', self.cert.format) |
36 | 37 | |
38 | def test_get_name(self): | |
39 | self.assertEqual(self.name, self.cert.name) | |
40 | ||
37 | 41 | def test_get_encoded(self): |
38 | 42 | self.assertEqual(self.data, self.cert.get_encoded()) |
39 | 43 | |
40 | 44 | def test___eq__(self): |
41 | 45 | self.assertTrue(self.cert == self.cert) |
46 | self.assertTrue(self.cert is self.cert) | |
42 | 47 | |
43 | 48 | self.assertFalse(self.cert is None) |
44 | 49 | self.assertFalse(None == self.cert) |
45 | 50 | |
46 | def test___ne__(self): | |
47 | self.assertFalse(self.cert != self.cert) | |
51 | other_x_509 = x_509.X509(self.data, self.name) | |
52 | self.assertTrue(self.cert == other_x_509) | |
53 | self.assertFalse(self.cert is other_x_509) | |
48 | 54 | |
55 | def test___ne___none(self): | |
49 | 56 | self.assertTrue(self.cert is not None) |
50 | 57 | self.assertTrue(None != self.cert) |
58 | ||
59 | def test___ne___data(self): | |
60 | other_x509 = x_509.X509(b'\x00\x00\x00', self.name) | |
61 | self.assertTrue(self.cert != other_x509) | |
62 | ||
63 | def test___ne__name(self): | |
64 | other_x509 = x_509.X509(self.data, "other x509") | |
65 | self.assertTrue(self.cert != other_x509) |
37 | 37 | options.set_defaults(conf, barbican_api_version=barbican_api_version) |
38 | 38 | self.assertEqual(barbican_api_version, |
39 | 39 | conf.get(bkm.BARBICAN_OPT_GROUP).barbican_api_version) |
40 | ||
41 | auth_endpoint = 'http://test-server.org:5000/' | |
42 | options.set_defaults(conf, auth_endpoint=auth_endpoint) | |
43 | self.assertEqual(auth_endpoint, | |
44 | conf.get(bkm.BARBICAN_OPT_GROUP).auth_endpoint) | |
45 | ||
46 | retry_delay = 3 | |
47 | options.set_defaults(conf, retry_delay=retry_delay) | |
48 | self.assertEqual(retry_delay, | |
49 | conf.get(bkm.BARBICAN_OPT_GROUP).retry_delay) | |
50 | ||
51 | number_of_retries = 10 | |
52 | options.set_defaults(conf, number_of_retries=number_of_retries) | |
53 | self.assertEqual(number_of_retries, | |
54 | conf.get(bkm.BARBICAN_OPT_GROUP).number_of_retries) |
14 | 14 | |
15 | 15 | |
16 | 16 | """These utility functions are borrowed from Barbican's testing utilities.""" |
17 | ||
18 | import functools | |
19 | import types | |
20 | ||
21 | import six | |
22 | ||
23 | ||
24 | def construct_new_test_function(original_func, name, build_params): | |
25 | """Builds a new test function based on parameterized data. | |
26 | ||
27 | :param original_func: The original test function that is used as a template | |
28 | :param name: The fullname of the new test function | |
29 | :param build_params: A dictionary or list containing args or kwargs | |
30 | for the new test | |
31 | :return: A new function object | |
32 | """ | |
33 | new_func = types.FunctionType( | |
34 | six.get_function_code(original_func), | |
35 | six.get_function_globals(original_func), | |
36 | name=name, | |
37 | argdefs=six.get_function_defaults(original_func) | |
38 | ) | |
39 | ||
40 | for key, val in six.iteritems(original_func.__dict__): | |
41 | if key != 'build_data': | |
42 | new_func.__dict__[key] = val | |
43 | ||
44 | # Support either an arg list or kwarg dict for our data | |
45 | build_args = build_params if isinstance(build_params, list) else [] | |
46 | build_kwargs = build_params if isinstance(build_params, dict) else {} | |
47 | ||
48 | # Build a test wrapper to execute with our kwargs | |
49 | def test_wrapper(func, test_args, test_kwargs): | |
50 | @functools.wraps(func) | |
51 | def wrapper(self): | |
52 | return func(self, *test_args, **test_kwargs) | |
53 | return wrapper | |
54 | ||
55 | return test_wrapper(new_func, build_args, build_kwargs) | |
56 | ||
57 | ||
58 | def process_parameterized_function(name, func_obj, build_data): | |
59 | """Build lists of functions to add and remove to a test case.""" | |
60 | to_remove = [] | |
61 | to_add = [] | |
62 | ||
63 | for subtest_name, params in six.iteritems(build_data): | |
64 | # Build new test function | |
65 | func_name = '{0}_{1}'.format(name, subtest_name) | |
66 | new_func = construct_new_test_function(func_obj, func_name, params) | |
67 | ||
68 | # Mark the new function as needed to be added to the class | |
69 | to_add.append((func_name, new_func)) | |
70 | ||
71 | # Mark key for removal | |
72 | to_remove.append(name) | |
73 | ||
74 | return to_remove, to_add | |
75 | ||
76 | ||
77 | def parameterized_test_case(cls): | |
78 | """Class decorator to process parameterized tests | |
79 | ||
80 | This allows for parameterization to be used for potentially any | |
81 | unittest compatible runner; including testr and py.test. | |
82 | """ | |
83 | tests_to_remove = [] | |
84 | tests_to_add = [] | |
85 | for key, val in six.iteritems(vars(cls)): | |
86 | # Only process tests with build data on them | |
87 | if key.startswith('test_') and val.__dict__.get('build_data'): | |
88 | to_remove, to_add = process_parameterized_function( | |
89 | name=key, | |
90 | func_obj=val, | |
91 | build_data=val.__dict__.get('build_data') | |
92 | ) | |
93 | tests_to_remove.extend(to_remove) | |
94 | tests_to_add.extend(to_add) | |
95 | ||
96 | # Add all new test functions | |
97 | [setattr(cls, name, func) for name, func in tests_to_add] | |
98 | ||
99 | # Remove all old test function templates (if they still exist) | |
100 | [delattr(cls, key) for key in tests_to_remove if hasattr(cls, key)] | |
101 | return cls | |
102 | ||
103 | ||
104 | def parameterized_dataset(build_data): | |
105 | """Simple decorator to mark a test method for processing.""" | |
106 | def decorator(func): | |
107 | func.__dict__['build_data'] = build_data | |
108 | return func | |
109 | return decorator | |
17 | 110 | |
18 | 111 | |
19 | 112 | def get_certificate_der(): |
210 | 303 | b'\x01\x15\xcd\x52\x83\x3f\x06\x67\xfd\xa1\x2d\x2b\x07\xba\x32' |
211 | 304 | b'\x62\x21\x07\x2f\x02\x03\x01\x00\x01') |
212 | 305 | return key_der |
306 | ||
307 | ||
308 | def get_symmetric_key(): | |
309 | """Returns symmetric key bytes | |
310 | ||
311 | 16 bytes that were randomly generated. Form a 128 bit key. | |
312 | """ | |
313 | symmetric_key = ( | |
314 | b'\x92\xcf\x1e\xd9\x54\xea\x30\x70\xd8\xc2\x48\xae\xc1\xc8\x72\xa3') | |
315 | return symmetric_key |
0 | ============ | |
1 | Contributing | |
2 | ============ | |
3 | .. include:: ../../CONTRIBUTING.rst | |
0 | .. include:: ../../CONTRIBUTING.rst⏎ |
0 | ======= | |
1 | Testing | |
2 | ======= | |
3 | ||
4 | Every Castellan code submission is automatically tested against a number | |
5 | of gating jobs to prevent regressions. Castellan developers should have a | |
6 | habit of running tests locally to ensure the code works as intended before | |
7 | submission. | |
8 | ||
9 | For your convenience we provide the ability to run all tests through | |
10 | the ``tox`` utility. If you are unfamiliar with tox please see | |
11 | refer to the `tox documentation`_ for assistance. | |
12 | ||
13 | .. _`tox documentation`: https://tox.readthedocs.org/en/latest/ | |
14 | ||
15 | Unit Tests | |
16 | ---------- | |
17 | ||
18 | Currently, we provide tox environments for a variety of different Python | |
19 | versions. By default all available test environments within the tox | |
20 | configuration will execute when calling ``tox``. If you want to run an | |
21 | independent version, you can do so with the following command: | |
22 | ||
23 | .. code-block:: bash | |
24 | ||
25 | # Executes tests on Python 2.7 | |
26 | tox -e py27 | |
27 | ||
28 | ||
29 | .. note:: | |
30 | ||
31 | Other available environments are py34, py26, and pypy. | |
32 | ||
33 | If you do not have the appropriate Python versions available, consider | |
34 | setting up PyEnv to install multiple versions of Python. See the | |
35 | documentation regarding `Setting up a Barbican development environment`_ | |
36 | for more information. | |
37 | ||
38 | Functional Tests | |
39 | ---------------- | |
40 | ||
41 | Unlike running unit tests, the functional tests require Barbican and | |
42 | Keystone services to be running in order to execute. For more | |
43 | information on this please see `Setting up a Barbican development environment`_ | |
44 | and `Using Keystone Middleware with Barbican`_ | |
45 | ||
46 | .. _`Setting up a Barbican development environment`: http://docs.openstack.org/developer/barbican/setup/dev.html | |
47 | .. _`Using Keystone Middleware with Barbican`: http://docs.openstack.org/developer/barbican/setup/keystone.html | |
48 | ||
49 | Castellan uses ``/etc/castellan/castellan-functional.conf`` in order to | |
50 | run functional tests. A sample file can be generated by running: | |
51 | ||
52 | .. code-block:: bash | |
53 | ||
54 | # Generate a sample configuration file | |
55 | tox -e genconfig | |
56 | ||
57 | ``castellan/etc/castellan/castellan-functional.conf.sample`` is generated. | |
58 | It must be renamed to ``castellan-functional.conf`` and placed in | |
59 | ``/etc/castellan``. | |
60 | ||
61 | The file should look something like the following: | |
62 | ||
63 | .. code-block:: bash | |
64 | ||
65 | [DEFAULT] | |
66 | ||
67 | [identity] | |
68 | username = 'admin' | |
69 | password = 'openstack' | |
70 | project_name = 'admin' | |
71 | auth_url = 'http://localhost:5000/v3' | |
72 | ||
73 | Once you have the appropriate services running and configured you can execute | |
74 | the functional tests through tox. | |
75 | ||
76 | .. code-block:: bash | |
77 | ||
78 | # Execute Barbican Functional Tests | |
79 | tox -e functional | |
80 | ||
81 | ||
82 | By default, the functional tox job will use ``testr`` to execute the | |
83 | functional tests. | |
84 | ||
85 | Debugging | |
86 | --------- | |
87 | ||
88 | In order to be able to debug code in Castellan, you must use the Python | |
89 | Debugger. This can be done by adding ``import pdb; pdb.set_trace()`` | |
90 | to set the breakpoint. Then run the following command to hit the breakpoint: | |
91 | ||
92 | .. code-block:: bash | |
93 | ||
94 | # hit the pdb breakpoint | |
95 | tox -e debug | |
96 | ||
97 | Once in the Python Debugger, you can use the commands as stated in the | |
98 | `Debugger Commands` section here: https://docs.python.org/2/library/pdb.html | |
99 | ||
100 | Pep8 Check | |
101 | ---------- | |
102 | ||
103 | Pep8 is a style guide for Python code. Castellan code should be have proper | |
104 | style before submission. In order to ensure that pep8 tests can be run through | |
105 | tox as follows: | |
106 | ||
107 | .. code-block:: bash | |
108 | ||
109 | # Checks python code style | |
110 | tox -e pep8 | |
111 | ||
112 | Any comments on bad coding style will output to the terminal. |
0 | ======== | |
0 | ===== | |
1 | 1 | Usage |
2 | ======== | |
2 | ===== | |
3 | 3 | |
4 | 4 | This document describes some of the common usage patterns for Castellan. When |
5 | 5 | incorporating this package into your applications, care should be taken to |
15 | 15 | |
16 | 16 | In addition to the key manager, Castellan also provides primitives for |
17 | 17 | various types of secrets (for example, asymmetric keys, simple passphrases, |
18 | and certificates). These primitives are used in conjuction with the key | |
18 | and certificates). These primitives are used in conjunction with the key | |
19 | 19 | manager to create, store, retrieve, and destroy managed secrets. |
20 | 20 | |
21 | 21 | Another fundamental concept to using Castellan is the context object, most |
25 | 25 | object will be used by Castellan to interact with the specific key manager |
26 | 26 | that is being abstracted. |
27 | 27 | |
28 | **Example. Creating RequestContext from Keystone Client** | |
29 | ||
30 | .. code:: python | |
31 | ||
32 | from keystoneclient.v3 import client | |
33 | from oslo_context import context | |
34 | ||
35 | username = 'admin' | |
36 | password = 'openstack' | |
37 | project_name = 'admin' | |
38 | auth_url = 'http://localhost:5000/v3' | |
39 | keystone_client = client.Client(username=username, | |
40 | password=password, | |
41 | project_name=project_name, | |
42 | auth_url=auth_url, | |
43 | project_domain_id='default') | |
44 | ||
45 | project_list = keystone_client.projects.list(name=project_name) | |
46 | ||
47 | ctxt = context.RequestContext(auth_token=keystone_client.auth_token, | |
48 | tenant=project_list[0].id) | |
49 | ||
50 | ctxt can then be passed into any key_manager api call which requires | |
51 | a RequestContext object. | |
52 | ||
28 | 53 | **Example. Creating and storing a key.** |
29 | 54 | |
30 | 55 | .. code:: python |
136 | 161 | from castellan import key_manager |
137 | 162 | |
138 | 163 | options.set_defaults(cfg.CONF, api_class='some.other.KeyManager') |
164 | manager = key_manager.API() | |
165 | ||
166 | Logging from within Castellan | |
167 | ----------------------------- | |
168 | ||
169 | Castellan uses ``oslo_log`` for logging. Log information will be generated | |
170 | if your application has configured the ``oslo_log`` module. If your | |
171 | application does not use ``oslo_log`` then you can enable default logging | |
172 | using ``enable_logging`` in the ``castellan.options`` module. | |
173 | ||
174 | **Example. Enabling default logging.** | |
175 | ||
176 | .. code:: python | |
177 | ||
178 | from castellan import options | |
179 | from castellan import key_manager | |
180 | ||
181 | options.enable_logging() | |
139 | 182 | manager = key_manager.API() |
140 | 183 | |
141 | 184 | Generating sample configuration files |
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 | 3 | |
4 | pbr<2.0,>=1.6 | |
4 | pbr>=1.6 | |
5 | 5 | Babel>=1.3 |
6 | 6 | cryptography>=1.0 # Apache-2.0 |
7 | oslo.config>=2.3.0 # Apache-2.0 | |
7 | oslo.config>=2.7.0 # Apache-2.0 | |
8 | 8 | oslo.context>=0.2.0 # Apache-2.0 |
9 | oslo.log>=1.8.0 # Apache-2.0 | |
9 | oslo.log>=1.12.0 # Apache-2.0 | |
10 | 10 | oslo.policy>=0.5.0 # Apache-2.0 |
11 | oslo.serialization>=1.4.0 # Apache-2.0 | |
12 | oslo.utils>=2.0.0 # Apache-2.0 | |
11 | oslo.serialization>=1.10.0 # Apache-2.0 | |
12 | oslo.utils>=2.8.0 # Apache-2.0 |
48 | 48 | keywords = _ gettext ngettext l_ lazy_gettext |
49 | 49 | mapping_file = babel.cfg |
50 | 50 | output_file = castellan/locale/castellan.pot |
51 | ||
52 | [wheel] | |
53 | universal = 1 |
24 | 24 | pass |
25 | 25 | |
26 | 26 | setuptools.setup( |
27 | setup_requires=['pbr>=1.3'], | |
27 | setup_requires=['pbr>=1.8'], | |
28 | 28 | pbr=True) |