Codebase list python-castellan / 329bcda
Merge tag '0.3.1' into debian/mitaka castellan 0.3.1 release Thomas Goirand 8 years ago
40 changed file(s) with 971 addition(s) and 220 deletion(s). Raw diff Collapse all Expand all
33 omit = castellan/tests/*,castellan/openstack/*
44
55 [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 ============
23
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:
47
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
814
9 http://docs.openstack.org/infra/manual/developers.html#development-workflow
15 Before you start contributing take a look at the `Openstack Developers Guide`_.
1016
11 Pull requests submitted through GitHub will be ignored.
17 .. _`Openstack Developers Guide`: http://docs.openstack.org/infra/manual/developers.html
1218
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`_.
1425
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 =========
33
4 Generic Key Manager interface for OpenStack
4 Generic Key Manager interface for OpenStack.
55
6 * Free software: Apache license
6 * License: Apache License, Version 2.0
77 * Documentation: http://docs.openstack.org/developer/castellan
88 * Source: http://git.openstack.org/cgit/openstack/castellan
99 * Bugs: http://bugs.launchpad.net/castellan
10
11 Features
12 --------
13
14 * TODO
4141 if not message_arg:
4242 message_arg = self.message
4343 try:
44 self.message = message_arg.format(**kwargs)
44 self.message = message_arg % kwargs
4545 except Exception as e:
4646 if _FATAL_EXCEPTION_FORMAT_ERRORS:
4747 raise e
5757
5858 class KeyManagerError(CastellanException):
5959 message = u._("Key manager error: %(reason)s")
60
61
62 class ManagedObjectNotFoundError(CastellanException):
63 message = u._("Key not found, uuid: %(uuid)s")
2828 class ManagedObject(object):
2929 """Base class to represent all managed objects."""
3030
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
3143 @abc.abstractproperty
3244 def format(self):
3345 """Returns the encoding format.
2424 class OpaqueData(managed_object.ManagedObject):
2525 """This class represents opaque data."""
2626
27 def __init__(self, data):
27 def __init__(self, data, name=None):
2828 """Create a new OpaqueData object.
2929
3030 Expected type for data is a bytestring.
3131 """
3232 self._data = data
33 super(OpaqueData, self).__init__(name=name)
3334
3435 @property
3536 def format(self):
4243
4344 def __eq__(self, other):
4445 if isinstance(other, OpaqueData):
45 return self._data == other._data
46 return (self._data == other._data and
47 self._name == other._name)
4648 else:
4749 return False
4850
2424 class Passphrase(managed_object.ManagedObject):
2525 """This class represents a passphrase."""
2626
27 def __init__(self, passphrase):
27 def __init__(self, passphrase, name=None):
2828 """Create a new Passphrase object.
2929
3030 The expected type for the passphrase is a bytestring.
3131 """
3232 self._passphrase = passphrase
33 super(Passphrase, self).__init__(name=name)
3334
3435 @property
3536 def format(self):
4243
4344 def __eq__(self, other):
4445 if isinstance(other, Passphrase):
45 return self._passphrase == other._passphrase
46 return (self._passphrase == other._passphrase and
47 self._name == other._name)
4648 else:
4749 return False
4850
2424 class PrivateKey(key.Key):
2525 """This class represents private keys."""
2626
27 def __init__(self, algorithm, bit_length, key):
27 def __init__(self, algorithm, bit_length, key, name=None):
2828 """Create a new PrivateKey object.
2929
3030 The arguments specify the algorithm and bit length for the asymmetric
3333 self._alg = algorithm
3434 self._bit_length = bit_length
3535 self._key = key
36 super(PrivateKey, self).__init__(name=name)
3637
3738 @property
3839 def algorithm(self):
5657 def __eq__(self, other):
5758 if isinstance(other, PrivateKey):
5859 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)
6063 else:
6164 return False
6265
2424 class PublicKey(key.Key):
2525 """This class represents public keys."""
2626
27 def __init__(self, algorithm, bit_length, key):
27 def __init__(self, algorithm, bit_length, key, name=None):
2828 """Create a new PublicKey object.
2929
3030 The arguments specify the algorithm and bit length for the asymmetric
3434 self._alg = algorithm
3535 self._bit_length = bit_length
3636 self._key = key
37 super(PublicKey, self).__init__(name=name)
3738
3839 @property
3940 def algorithm(self):
5758 def __eq__(self, other):
5859 if isinstance(other, PublicKey):
5960 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)
6164 else:
6265 return False
6366
2424 class SymmetricKey(key.Key):
2525 """This class represents symmetric keys."""
2626
27 def __init__(self, algorithm, bit_length, key):
27 def __init__(self, algorithm, bit_length, key, name=None):
2828 """Create a new SymmetricKey object.
2929
3030 The arguments specify the algorithm and bit length for the symmetric
3333 self._alg = algorithm
3434 self._bit_length = bit_length
3535 self._key = key
36 super(SymmetricKey, self).__init__(name=name)
3637
3738 @property
3839 def algorithm(self):
5758 if isinstance(other, SymmetricKey):
5859 return (self._alg == other._alg and
5960 self._bit_length == other._bit_length and
60 self._key == other._key)
61 self._key == other._key and
62 self._name == other._name)
6163 else:
6264 return False
6365
2424 class X509(certificate.Certificate):
2525 """This class represents X.509 certificates."""
2626
27 def __init__(self, data):
27 def __init__(self, data, name=None):
2828 """Create a new X509 object.
2929
3030 The data should be in a bytestring.
3131 """
3232 self._data = data
33 super(X509, self).__init__(name=name)
3334
3435 @property
3536 def format(self):
4243
4344 def __eq__(self, other):
4445 if isinstance(other, X509):
45 return (self._data == other._data)
46 return (self._data == other._data and
47 self._name == other._name)
4648 else:
4749 return False
4850
1111 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1212 # License for the specific language governing permissions and limitations
1313 # under the License.
14
1514 from oslo_config import cfg
1615 from oslo_utils import importutils
1716
2827 conf.register_opts(key_manager_opts, group='key_manager')
2928
3029 cls = importutils.import_class(conf.key_manager.api_class)
31 return cls(configuration=conf)
30 return cls(configuration=conf)
8989 :param context: the user context for authentication
9090 :return: a Barbican Client object
9191 :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
9494 """
9595
9696 # Confirm context is provided, if not raise forbidden
103103 msg = u._("Unable to create Barbican Client without tenant "
104104 "attribute in context object.")
105105 LOG.error(msg)
106 raise exception.KeyManagerError(msg)
106 raise exception.KeyManagerError(reason=msg)
107107
108108 if self._barbican_client and self._current_context == context:
109109 return self._barbican_client
119119 endpoint=self._barbican_endpoint)
120120
121121 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)
124124
125125 self._base_url = self._create_base_url(auth,
126126 sess,
157157 msg = u._LE(
158158 "Could not find discovery information for %s") % endpoint
159159 LOG.error(msg)
160 raise exception.KeyManagerError(msg)
160 raise exception.KeyManagerError(reason=msg)
161161 latest_version = raw_data[-1]
162162 api_version = latest_version.get('id')
163163
165165 endpoint, api_version)
166166 return base_url
167167
168 def create_key(self, context, algorithm, length, expiration=None):
168 def create_key(self, context, algorithm, length,
169 expiration=None, name=None):
169170 """Creates a symmetric key.
170171
171172 :param context: contains information of the user and the environment
172173 for the request (castellan/context.py)
173174 :param algorithm: the algorithm associated with the secret
174175 :param length: the bit length of the secret
176 :param name: the name of the key
175177 :param expiration: the date the key will expire
176178 :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
180180 """
181181 barbican_client = self._get_barbican_client(context)
182182
183183 try:
184184 key_order = barbican_client.orders.create_key(
185 name=name,
185186 algorithm=algorithm,
186187 bit_length=length,
187188 expiration=expiration)
191192 except (barbican_exceptions.HTTPAuthError,
192193 barbican_exceptions.HTTPClientError,
193194 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):
198200 """Creates an asymmetric key pair.
199201
200202 :param context: contains information of the user and the environment
201203 for the request (castellan/context.py)
202204 :param algorithm: the algorithm associated with the secret
203205 :param length: the bit length of the secret
206 :param name: the name of the key
204207 :param expiration: the date the key will expire
205208 :return: the UUIDs of the new key, in the order (private, public)
206209 :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
210211 """
211212 barbican_client = self._get_barbican_client(context)
212213
214215 key_pair_order = barbican_client.orders.create_asymmetric(
215216 algorithm=algorithm,
216217 bit_length=length,
218 name=name,
217219 expiration=expiration)
218220
219221 order_ref = key_pair_order.submit()
228230 except (barbican_exceptions.HTTPAuthError,
229231 barbican_exceptions.HTTPClientError,
230232 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)
233235
234236 def _get_barbican_object(self, barbican_client, managed_object):
235237 """Converts the Castellan managed_object to a Barbican secret."""
238 name = getattr(managed_object, 'name', None)
239
236240 try:
237241 algorithm = managed_object.algorithm
238242 bit_length = managed_object.bit_length
247251 secret = barbican_client.secrets.create(payload=payload,
248252 algorithm=algorithm,
249253 bit_length=bit_length,
254 name=name,
250255 secret_type=secret_type)
251256 return secret
252257
286291
287292 :param context: contains information of the user and the environment
288293 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
291296 :param expiration: the expiration time of the secret in ISO 8601
292297 format
293298 :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
297300 """
298301 barbican_client = self._get_barbican_client(context)
299302
306309 except (barbican_exceptions.HTTPAuthError,
307310 barbican_exceptions.HTTPClientError,
308311 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):
313316 """Creates the URL required for accessing a secret.
314317
315 :param key_id: the UUID of the key to copy
318 :param object_id: the UUID of the key to copy
316319 :return: the URL of the requested secret
317320 """
318 if not key_id:
321 if not object_id:
319322 msg = "Key ID is None"
320 raise exception.KeyManagerError(msg)
323 raise exception.KeyManagerError(reason=msg)
321324 base_url = self._base_url
322325 if base_url[-1] != '/':
323326 base_url += '/'
324 return urllib.parse.urljoin(base_url, "secrets/" + key_id)
327 return urllib.parse.urljoin(base_url, "secrets/" + object_id)
325328
326329 def _get_active_order(self, barbican_client, order_ref):
327330 """Returns the order when it is active.
355358 'num_retries':
356359 number_of_retries}
357360 LOG.error(msg)
358 raise exception.KeyManagerError(msg)
361 raise exception.KeyManagerError(reason=msg)
359362
360363 def _retrieve_secret_uuid(self, secret_ref):
361364 """Retrieves the UUID of the secret from the secret_ref.
420423 if issubclass(secret_type, key_base_class.Key):
421424 return secret_type(secret.algorithm,
422425 secret.bit_length,
423 secret_data)
426 secret_data,
427 secret.name)
424428 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):
428433 """Returns the metadata of the secret.
429434
430435 :param context: contains information of the user and the environment
431436 for the request (castellan/context.py)
432 :param key_id: UUID of the secret
437 :param object_id: UUID of the secret
433438 :return: the secret's metadata
434439 :raises HTTPAuthError: if object retrieval fails with 401
435440 :raises HTTPClientError: if object retrieval fails with 4xx
439444 barbican_client = self._get_barbican_client(context)
440445
441446 try:
442 secret_ref = self._create_secret_ref(key_id)
447 secret_ref = self._create_secret_ref(object_id)
443448 return barbican_client.secrets.get(secret_ref)
444449 except (barbican_exceptions.HTTPAuthError,
445450 barbican_exceptions.HTTPClientError,
447452 with excutils.save_and_reraise_exception():
448453 LOG.error(u._LE("Error getting secret metadata: %s"), e)
449454
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
450462 def get(self, context, managed_object_id):
451463 """Retrieves the specified managed object.
452464
456468 for the request (castellan/context.py)
457469 :param managed_object_id: the UUID of the object to retrieve
458470 :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
462473 """
463474 try:
464475 secret = self._get_secret(context, managed_object_id)
466477 except (barbican_exceptions.HTTPAuthError,
467478 barbican_exceptions.HTTPClientError,
468479 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)
471486
472487 def delete(self, context, managed_object_id):
473488 """Deletes the specified managed object.
475490 :param context: contains information of the user and the environment
476491 for the request (castellan/context.py)
477492 :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
481495 """
482496 barbican_client = self._get_barbican_client(context)
483497
487501 except (barbican_exceptions.HTTPAuthError,
488502 barbican_exceptions.HTTPClientError,
489503 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)
3939 pass
4040
4141 @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):
4344 """Creates a symmetric key.
4445
4546 This method creates a symmetric key and returns the key's UUID. If the
4950 pass
5051
5152 @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):
5355 """Creates an asymmetric key pair.
5456
5557 This method creates an asymmetric key pair and returns the pair of key
2828 super(NotImplementedKeyManager, self).__init__(configuration)
2929
3030 def create_key(self, context, algorithm='AES', length=256,
31 expiration=None, **kwargs):
31 expiration=None, name=None, **kwargs):
3232 raise NotImplementedError()
3333
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):
3536 raise NotImplementedError()
3637
3738 def store(self, context, managed_object, expiration=None, **kwargs):
1111 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1212 # License for the specific language governing permissions and limitations
1313 # under the License.
14 from oslo_config import cfg
15 from oslo_log import log
1416
1517 from castellan import key_manager as km
1618 try:
1820 except ImportError:
1921 bkm = None
2022
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
2130
2231 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.
2535
2636 Overrides the default options values.
27
2837 :param conf: Config instance in which to set default options.
29
3038 :param api_class: The full class name of the key manager API class.
31
3239 :param barbican_endpoint: Use this endpoint to connect to Barbican.
33
3440 :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 """
3645 conf.register_opts(km.key_manager_opts, group='key_manager')
3746 if bkm:
3847 conf.register_opts(bkm.barbican_opts, group=bkm.BARBICAN_OPT_GROUP)
4554 if bkm is not None and barbican_api_version is not None:
4655 conf.set_default('barbican_api_version', barbican_api_version,
4756 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)
4878
4979
5080 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.
5282
5383 The returned list includes all oslo.config options which may be registered
5484 at runtime by the library.
6292 generator to discover the options exposed to users by this library.
6393
6494 :returns: a list of (group_name, opts) tuples
65 '''
95 """
6696 opts = [('key_manager', km.key_manager_opts)]
6797 if bkm is not None:
6898 opts.append((bkm.BARBICAN_OPT_GROUP, bkm.barbican_opts))
0 #!/bin/bash
1
2 set -ex
3
4 $BASE/new/devstack-gate/devstack-vm-gate.sh
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
2020
2121 import uuid
2222
23 from barbicanclient import exceptions as barbican_exceptions
2423 from keystoneclient.v3 import client
24 from oslo_config import cfg
2525 from oslo_context import context
26 from oslotest import base
2627
2728 from castellan.common import exception
28 from castellan.common.objects import symmetric_key
2929 from castellan.key_manager import barbican_key_manager
3030 from castellan.tests.functional import config
3131 from castellan.tests.functional.key_manager import test_key_manager
3434 CONF = config.get_config()
3535
3636
37 class BarbicanKeyManagerTestCase(test_key_manager.KeyManagerTestCase):
37 class BarbicanKeyManagerTestCase(test_key_manager.KeyManagerTestCase,
38 base.BaseTestCase):
3839
3940 def _create_key_manager(self):
40 return barbican_key_manager.BarbicanKeyManager()
41 return barbican_key_manager.BarbicanKeyManager(cfg.CONF)
4142
4243 def setUp(self):
4344 super(BarbicanKeyManagerTestCase, self).setUp()
4849 keystone_client = client.Client(username=username,
4950 password=password,
5051 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
5256 self.ctxt = context.RequestContext(
53 auth_token=keystone_client.auth_token)
57 auth_token=keystone_client.auth_token,
58 tenant=project_list[0].id)
5459
5560 def tearDown(self):
5661 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)
6462
6563 def test_create_null_context(self):
6664 self.assertRaises(exception.Forbidden,
6765 self.key_mgr.create_key, None, 'AES', 256)
6866
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)
8070
8171 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)
8675 self.assertRaises(exception.Forbidden,
87 self.key_mgr.delete_key, None, key_uuid)
76 self.key_mgr.delete, None, key_uuid)
8877
89 def test_delete_null_key(self):
78 def test_delete_null_object(self):
9079 self.assertRaises(exception.KeyManagerError,
91 self.key_mgr.delete_key, self.ctxt, None)
80 self.key_mgr.delete, self.ctxt, None)
9281
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)
10786
10887 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())
11290 self.assertRaises(exception.Forbidden,
113 self.key_mgr.get_key, None, key_uuid)
91 self.key_mgr.get, None, key_uuid)
11492
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):
12094 self.assertRaises(exception.KeyManagerError,
121 self.key_mgr.get_key, self.ctxt, None)
95 self.key_mgr.get, self.ctxt, None)
12296
12397 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)
12898 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)
141101
142102 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()
145104
146105 self.assertRaises(exception.Forbidden,
147 self.key_mgr.store_key, None, key)
106 self.key_mgr.store, None, key)
1313 # under the License.
1414
1515 """
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.
1719 """
1820
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
2029
2130
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):
2372
2473 def _create_key_manager(self):
2574 raise NotImplementedError()
2675
2776 def setUp(self):
2877 super(KeyManagerTestCase, self).setUp()
78 self.key_mgr = self._create_key_manager()
2979
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())
6363 return hex_encoded
6464
6565 def _generate_key(self, **kwargs):
66 name = kwargs.get('name', None)
6667 key_length = kwargs.get('key_length', 256)
6768 _hex = self._generate_hex_key(key_length)
6869 return sym_key.SymmetricKey(
6970 'AES',
7071 key_length,
71 bytes(binascii.unhexlify(_hex)))
72 bytes(binascii.unhexlify(_hex)),
73 name)
7274
7375 def create_key(self, context, **kwargs):
7476 """Creates a symmetric key.
8385 key = self._generate_key(**kwargs)
8486 return self.store(context, key)
8587
86 def _generate_public_and_private_key(self, length):
88 def _generate_public_and_private_key(self, length, name):
8789 crypto_private_key = rsa.generate_private_key(
8890 public_exponent=65537,
8991 key_size=length,
103105 private_key = pri_key.PrivateKey(
104106 algorithm='RSA',
105107 bit_length=length,
106 key=bytearray(private_der))
108 key=bytearray(private_der),
109 name=name)
107110
108111 public_key = pub_key.PublicKey(
109112 algorithm='RSA',
110113 bit_length=length,
111 key=bytearray(public_der))
114 key=bytearray(public_der),
115 name=name)
112116
113117 return private_key, public_key
114118
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):
116121 """Creates an asymmetric key pair.
117122
118123 This implementation returns UUIDs for the created keys in the order:
133138 length, valid_lengths)
134139 raise ValueError(msg)
135140
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)
137143
138144 private_key_uuid = self.store(context, private_key)
139145 public_key_uuid = self.store(context, public_key)
105105 self.mock_barbican.orders.create_key.return_value = key_order
106106 key_order.submit = mock.Mock(
107107 side_effect=barbican_exceptions.HTTPClientError('test error'))
108 self.assertRaises(barbican_exceptions.HTTPClientError,
108 self.assertRaises(exception.KeyManagerError,
109109 self.key_mgr.create_key, self.ctxt, 'AES', 256)
110110
111111 def test_create_key_pair(self):
158158 self.mock_barbican.orders.create_asymmetric.return_value = asym_order
159159 asym_order.submit = mock.Mock(
160160 side_effect=barbican_exceptions.HTTPClientError('test error'))
161 self.assertRaises(barbican_exceptions.HTTPClientError,
161 self.assertRaises(exception.KeyManagerError,
162162 self.key_mgr.create_key_pair, self.ctxt, 'RSA', 2048)
163163
164164 def test_delete_null_context(self):
177177 def test_delete_with_error(self):
178178 self.mock_barbican.secrets.delete = mock.Mock(
179179 side_effect=barbican_exceptions.HTTPClientError('test error'))
180 self.assertRaises(barbican_exceptions.HTTPClientError,
180 self.assertRaises(exception.KeyManagerError,
181181 self.key_mgr.delete, self.ctxt, self.key_id)
182182
183183 def test_get_key(self):
185185 original_secret_metadata.algorithm = mock.sentinel.alg
186186 original_secret_metadata.bit_length = mock.sentinel.bit
187187 original_secret_metadata.secret_type = 'symmetric'
188
189 key_name = 'my key'
190 original_secret_metadata.name = key_name
191
188192 original_secret_data = b'test key'
189193 original_secret_metadata.payload = original_secret_data
190194
192196 key = self.key_mgr.get(self.ctxt, self.key_id)
193197
194198 self.get.assert_called_once_with(self.secret_ref)
199 self.assertEqual(key_name, key.name)
195200 self.assertEqual(original_secret_data, key.get_encoded())
196201
197202 def test_get_null_context(self):
206211 def test_get_with_error(self):
207212 self.mock_barbican.secrets.get = mock.Mock(
208213 side_effect=barbican_exceptions.HTTPClientError('test error'))
209 self.assertRaises(barbican_exceptions.HTTPClientError,
214 self.assertRaises(exception.KeyManagerError,
210215 self.key_mgr.get, self.ctxt, self.key_id)
211216
212217 def test_store_key(self):
227232
228233 self.create.assert_called_once_with(algorithm='AES',
229234 bit_length=key_length,
235 name=None,
230236 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,
231262 secret_type='symmetric')
232263 self.assertEqual(self.key_id, returned_uuid)
233264
244275 _key = sym_key.SymmetricKey('AES',
245276 key_length,
246277 secret_key)
247 self.assertRaises(barbican_exceptions.HTTPClientError,
278 self.assertRaises(exception.KeyManagerError,
248279 self.key_mgr.store, self.ctxt, _key)
249280
250281 def test_get_active_order(self):
6565 key = self.key_mgr.get(self.context, key_id)
6666 self.assertEqual(length / 8, len(key.get_encoded()))
6767
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
6874 def test_create_key_null_context(self):
6975 self.assertRaises(exception.Forbidden,
7076 self.key_mgr.create_key, None)
7177
7278 def test_create_key_pair(self):
7379 for length in [2048, 3072, 4096]:
80 name = str(length) + ' key'
7481 private_key_uuid, public_key_uuid = self.key_mgr.create_key_pair(
75 self.context, 'RSA', length)
82 self.context, 'RSA', length, name=name)
7683
7784 private_key = self.key_mgr.get(self.context, private_key_uuid)
7885 public_key = self.key_mgr.get(self.context, public_key_uuid)
7986
8087 crypto_private_key = get_cryptography_private_key(private_key)
8188 crypto_public_key = get_cryptography_public_key(public_key)
89
90 self.assertEqual(name, private_key.name)
91 self.assertEqual(name, public_key.name)
8292
8393 self.assertEqual(length, crypto_private_key.key_size)
8494 self.assertEqual(length, crypto_public_key.key_size)
2323 class OpaqueDataTestCase(base.TestCase):
2424
2525 def _create_data(self):
26 return opaque_data.OpaqueData(self.data)
26 return opaque_data.OpaqueData(self.data, self.name)
2727
2828 def setUp(self):
2929 self.data = bytes(b"secret opaque data")
30 self.name = 'my opaque'
3031 self.opaque_data = self._create_data()
3132
3233 super(OpaqueDataTestCase, self).setUp()
3738 def test_get_encoded(self):
3839 self.assertEqual(self.data, self.opaque_data.get_encoded())
3940
41 def test_get_name(self):
42 self.assertEqual(self.name, self.opaque_data.name)
43
4044 def test___eq__(self):
4145 self.assertTrue(self.opaque_data == self.opaque_data)
46 self.assertTrue(self.opaque_data is self.opaque_data)
4247
4348 self.assertFalse(self.opaque_data is None)
4449 self.assertFalse(None == self.opaque_data)
4550
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)
4854
55 def test___ne___none(self):
4956 self.assertTrue(self.opaque_data is not None)
5057 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)
2323 class PassphraseTestCase(base.TestCase):
2424
2525 def _create_passphrase(self):
26 return passphrase.Passphrase(self.passphrase_data)
26 return passphrase.Passphrase(self.passphrase_data,
27 self.name)
2728
2829 def setUp(self):
2930 self.passphrase_data = bytes(b"secret passphrase")
31 self.name = 'my phrase'
3032 self.passphrase = self._create_passphrase()
3133
3234 super(PassphraseTestCase, self).setUp()
3739 def test_get_encoded(self):
3840 self.assertEqual(self.passphrase_data, self.passphrase.get_encoded())
3941
42 def test_get_name(self):
43 self.assertEqual(self.name, self.passphrase.name)
44
4045 def test___eq__(self):
4146 self.assertTrue(self.passphrase == self.passphrase)
47 self.assertTrue(self.passphrase is self.passphrase)
4248
4349 self.assertFalse(self.passphrase is None)
4450 self.assertFalse(None == self.passphrase)
4551
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)
4856
57 def test___ne___none(self):
4958 self.assertTrue(self.passphrase is not None)
5059 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)
2626 def _create_key(self):
2727 return private_key.PrivateKey(self.algorithm,
2828 self.length,
29 self.encoded)
29 self.encoded,
30 self.name)
3031
3132 def setUp(self):
3233 self.algorithm = 'RSA'
3334 self.length = 2048
3435 self.encoded = bytes(utils.get_private_key_der())
36 self.name = 'my key'
3537
3638 super(PrivateKeyTestCase, self).setUp()
3739
4143 def test_get_length(self):
4244 self.assertEqual(self.length, self.key.bit_length)
4345
46 def test_get_name(self):
47 self.assertEqual(self.name, self.key.name)
48
4449 def test_get_format(self):
4550 self.assertEqual('PKCS8', self.key.format)
4651
4954
5055 def test___eq__(self):
5156 self.assertTrue(self.key == self.key)
57 self.assertTrue(self.key is self.key)
5258
5359 self.assertFalse(self.key is None)
5460 self.assertFalse(None == self.key)
5561
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)
5868
69 def test___ne___none(self):
5970 self.assertTrue(self.key is not None)
6071 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)
2424 class PublicKeyTestCase(base.KeyTestCase):
2525
2626 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)
2831
2932 def setUp(self):
3033 self.algorithm = 'RSA'
3134 self.length = 2048
3235 self.encoded = bytes(utils.get_public_key_der())
36 self.name = 'my key'
3337
3438 super(PublicKeyTestCase, self).setUp()
3539
3943 def test_get_length(self):
4044 self.assertEqual(self.length, self.key.bit_length)
4145
46 def test_get_name(self):
47 self.assertEqual(self.name, self.key.name)
48
4249 def test_get_format(self):
4350 self.assertEqual('SubjectPublicKeyInfo', self.key.format)
4451
4754
4855 def test___eq__(self):
4956 self.assertTrue(self.key == self.key)
57 self.assertTrue(self.key is self.key)
5058
5159 self.assertFalse(self.key is None)
5260 self.assertFalse(None == self.key)
5361
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)
5668
69 def test___ne___none(self):
5770 self.assertTrue(self.key is not None)
5871 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)
2525 def _create_key(self):
2626 return sym_key.SymmetricKey(self.algorithm,
2727 self.bit_length,
28 self.encoded)
28 self.encoded,
29 self.name)
2930
3031 def setUp(self):
3132 self.algorithm = 'AES'
3233 self.encoded = bytes(b'0' * 64)
3334 self.bit_length = len(self.encoded) * 8
35 self.name = 'my key'
3436
3537 super(SymmetricKeyTestCase, self).setUp()
3638
3739 def test_get_format(self):
3840 self.assertEqual('RAW', self.key.format)
41
42 def test_get_name(self):
43 self.assertEqual(self.name, self.key.name)
3944
4045 def test_get_encoded(self):
4146 self.assertEqual(self.encoded, self.key.get_encoded())
4853
4954 def test___eq__(self):
5055 self.assertTrue(self.key == self.key)
56 self.assertTrue(self.key is self.key)
5157
5258 self.assertFalse(self.key is None)
5359 self.assertFalse(None == self.key)
5460
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)
5767
68 def test___ne___none(self):
5869 self.assertTrue(self.key is not None)
5970 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)
2424 class X509TestCase(base.CertificateTestCase):
2525
2626 def _create_cert(self):
27 return x_509.X509(self.data)
27 return x_509.X509(self.data, self.name)
2828
2929 def setUp(self):
3030 self.data = utils.get_certificate_der()
31 self.name = 'my cert'
3132
3233 super(X509TestCase, self).setUp()
3334
3435 def test_get_format(self):
3536 self.assertEqual('X.509', self.cert.format)
3637
38 def test_get_name(self):
39 self.assertEqual(self.name, self.cert.name)
40
3741 def test_get_encoded(self):
3842 self.assertEqual(self.data, self.cert.get_encoded())
3943
4044 def test___eq__(self):
4145 self.assertTrue(self.cert == self.cert)
46 self.assertTrue(self.cert is self.cert)
4247
4348 self.assertFalse(self.cert is None)
4449 self.assertFalse(None == self.cert)
4550
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)
4854
55 def test___ne___none(self):
4956 self.assertTrue(self.cert is not None)
5057 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)
3737 options.set_defaults(conf, barbican_api_version=barbican_api_version)
3838 self.assertEqual(barbican_api_version,
3939 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)
1414
1515
1616 """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
17110
18111
19112 def get_certificate_der():
210303 b'\x01\x15\xcd\x52\x83\x3f\x06\x67\xfd\xa1\x2d\x2b\x07\xba\x32'
211304 b'\x62\x21\x07\x2f\x02\x03\x01\x00\x01')
212305 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
1313 readme
1414 installation
1515 usage
16 testing
1617 contributing
1718
1819 Indices and tables
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 =====
11 Usage
2 ========
2 =====
33
44 This document describes some of the common usage patterns for Castellan. When
55 incorporating this package into your applications, care should be taken to
1515
1616 In addition to the key manager, Castellan also provides primitives for
1717 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
1919 manager to create, store, retrieve, and destroy managed secrets.
2020
2121 Another fundamental concept to using Castellan is the context object, most
2525 object will be used by Castellan to interact with the specific key manager
2626 that is being abstracted.
2727
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
2853 **Example. Creating and storing a key.**
2954
3055 .. code:: python
136161 from castellan import key_manager
137162
138163 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()
139182 manager = key_manager.API()
140183
141184 Generating sample configuration files
11 # of appearance. Changing the order has an impact on the overall integration
22 # process, which may cause wedges in the gate later.
33
4 pbr<2.0,>=1.6
4 pbr>=1.6
55 Babel>=1.3
66 cryptography>=1.0 # Apache-2.0
7 oslo.config>=2.3.0 # Apache-2.0
7 oslo.config>=2.7.0 # Apache-2.0
88 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
1010 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
4848 keywords = _ gettext ngettext l_ lazy_gettext
4949 mapping_file = babel.cfg
5050 output_file = castellan/locale/castellan.pot
51
52 [wheel]
53 universal = 1
2424 pass
2525
2626 setuptools.setup(
27 setup_requires=['pbr>=1.3'],
27 setup_requires=['pbr>=1.8'],
2828 pbr=True)
88 python-barbicanclient>=3.3.0
99 python-subunit>=0.0.18
1010 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
11 oslosphinx>=2.5.0 # Apache-2.0
11 oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
1212 oslotest>=1.10.0 # Apache-2.0
1313 testrepository>=0.0.18
1414 testscenarios>=0.4
1717
1818 [testenv:venv]
1919 commands = {posargs}
20
21 [testenv:debug]
22 commands = oslo_debug_helper {posargs}
2023
2124 [testenv:cover]
2225 commands = python setup.py testr --coverage --testr-args='{posargs}'