def delete(self, context, key_id): """Represents deleting the key.""" # Confirm context is provided, if not raise forbidden if not context: msg = _("User is not authorized to use key manager.") raise exception.Forbidden(msg) if not key_id: raise exception.KeyManagerError('key identifier not provided') headers = {'X-Vault-Token': self._root_token_id} try: resource_url = self._get_url() + 'v1/secret/' + key_id resp = self._session.delete(resource_url, verify=self._verify_server, headers=headers) except requests.exceptions.Timeout as ex: raise exception.KeyManagerError(six.text_type(ex)) except requests.exceptions.ConnectionError as ex: raise exception.KeyManagerError(six.text_type(ex)) except Exception as ex: raise exception.KeyManagerError(six.text_type(ex)) if resp.status_code in _EXCEPTIONS_BY_CODE: raise exception.KeyManagerError(resp.reason) if resp.status_code == requests.codes['forbidden']: raise exception.Forbidden() if resp.status_code == requests.codes['not_found']: raise exception.ManagedObjectNotFoundError(uuid=key_id)
def list(self, context, object_type=None, metadata_only=False): """Lists the managed objects given the criteria.""" # Confirm context is provided, if not raise forbidden if not context: msg = _("User is not authorized to use key manager.") raise exception.Forbidden(msg) if object_type and object_type not in self._secret_type_dict: msg = _("Invalid secret type: %s") % object_type raise exception.KeyManagerError(reason=msg) resp = self._do_http_request(self._session.get, self._get_resource_url()) if resp.status_code == requests.codes['not_found']: keys = [] else: keys = resp.json()['data']['keys'] objects = [] for obj_id in keys: try: obj = self.get(context, obj_id, metadata_only=metadata_only) if object_type is None or isinstance(obj, object_type): objects.append(obj) except exception.ManagedObjectNotFoundError as e: LOG.warning( _("Error occurred while retrieving object " "metadata, not adding it to the list: %s"), e) pass return objects
def _store_key_value(self, key_id, value): type_value = self._secret_type_dict.get(type(value)) if type_value is None: raise exception.KeyManagerError("Unknown type for value : %r" % value) record = { 'type': type_value, 'value': binascii.hexlify(value.get_encoded()).decode('utf-8'), 'algorithm': (value.algorithm if hasattr(value, 'algorithm') else None), 'bit_length': (value.bit_length if hasattr(value, 'bit_length') else None), 'name': value.name, 'created': value.created } if self._get_api_version() != '1': record = {'data': record} self._do_http_request(self._session.post, self._get_resource_url(key_id), json=record) return key_id
def _create_base_url(self, auth, sess, endpoint): api_version = None if self.conf.barbican.barbican_api_version: api_version = self.conf.barbican.barbican_api_version elif getattr(auth, 'service_catalog', None): endpoint_data = auth.service_catalog.endpoint_data_for( service_type='key-manager') api_version = endpoint_data.api_version elif getattr(auth, 'get_discovery', None): discovery = auth.get_discovery(sess, url=endpoint) raw_data = discovery.raw_version_data() if len(raw_data) == 0: msg = _( "Could not find discovery information for %s") % endpoint LOG.error(msg) raise exception.KeyManagerError(reason=msg) latest_version = raw_data[-1] api_version = latest_version.get('id') if endpoint[-1] != '/': endpoint += '/' base_url = urllib.parse.urljoin( endpoint, api_version) return base_url
def create_key(self, context, algorithm, length, expiration=None, name=None): """Creates a symmetric key. :param context: contains information of the user and the environment for the request (castellan/context.py) :param algorithm: the algorithm associated with the secret :param length: the bit length of the secret :param name: the name of the key :param expiration: the date the key will expire :return: the UUID of the new key :raises KeyManagerError: if key creation fails """ barbican_client = self._get_barbican_client(context) try: key_order = barbican_client.orders.create_key( name=name, algorithm=algorithm, bit_length=length, expiration=expiration) order_ref = key_order.submit() order = self._get_active_order(barbican_client, order_ref) return self._retrieve_secret_uuid(order.secret_ref) except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: LOG.error("Error creating key: %s", e) raise exception.KeyManagerError(reason=e)
def store(self, context, managed_object, expiration=None): """Stores (i.e., registers) an object with the key manager. :param context: contains information of the user and the environment for the request (castellan/context.py) :param managed_object: a secret object with unencrypted payload. Known as "secret" to the barbicanclient api :param expiration: the expiration time of the secret in ISO 8601 format :returns: the UUID of the stored object :raises KeyManagerError: if object store fails """ barbican_client = self._get_barbican_client(context) try: secret = self._get_barbican_object(barbican_client, managed_object) secret.expiration = expiration secret_ref = secret.store() return self._retrieve_secret_uuid(secret_ref) except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: LOG.error("Error storing object: %s", e) raise exception.KeyManagerError(reason=e)
def _encrypt_data(context, data): try: # TODO(pbourke): move auth construction into common area if it ends up # been required in other areas auth = identity.V3Password( auth_url=settings.KEY_MANAGER['auth_url'], username=settings.KEY_MANAGER['username'], user_domain_name=settings.KEY_MANAGER['user_domain_name'], password=settings.KEY_MANAGER['password'], project_name=settings.KEY_MANAGER['project_name'], project_domain_name=settings.KEY_MANAGER['project_domain_name'] ) except (KeyError, AttributeError) as e: LOG.exception(e) msg = ('Could not find valid key manager credentials in the ' 'murano-dashboard config. encryptData yaql function not ' 'available') raise castellan_exception.KeyManagerError(message_arg=msg) sess = session.Session(auth=auth) auth_context = _oslo_context.RequestContext( auth_token=auth.get_token(sess), tenant=auth.get_project_id(sess)) options.set_defaults(cfg.CONF, auth_endpoint=settings.KEY_MANAGER['auth_url']) manager = key_manager.API() try: # TODO(pbourke): while we feel opaque data should cover the most common # use case, we may want to add support for other secret types in the # future (see https://goo.gl/tZhfqe) stored_key_id = manager.store(auth_context, opaque_data.OpaqueData(data)) except castellan_exception.KeyManagerError as e: LOG.exception(e) raise return stored_key_id
def _get_active_order(self, barbican_client, order_ref): """Returns the order when it is active. Barbican key creation is done asynchronously, so this loop continues checking until the order is active or a timeout occurs. """ active_status = u'ACTIVE' error_status = u'ERROR' number_of_retries = self.conf.barbican.number_of_retries retry_delay = self.conf.barbican.retry_delay order = barbican_client.orders.get(order_ref) time.sleep(.25) for n in range(number_of_retries): if order.status == error_status: kwargs = { "status": error_status, "code": order.error_status_code, "reason": order.error_reason } msg = _("Order is in %(status)s status - status code: " "%(code)s, status reason: %(reason)s") % kwargs LOG.error(msg) raise exception.KeyManagerError(reason=msg) if order.status != active_status: kwargs = { 'attempt': n, 'total': number_of_retries, 'status': order.status, 'active': active_status, 'delay': retry_delay } msg = _("Retry attempt #%(attempt)i out of %(total)i: " "Order status is '%(status)s'. Waiting for " "'%(active)s', will retry in %(delay)s " "seconds") LOG.info(msg, kwargs) time.sleep(retry_delay) order = barbican_client.orders.get(order_ref) else: return order msg = _("Exceeded retries: Failed to find '%(active)s' status " "within %(num_retries)i retries") % { 'active': active_status, 'num_retries': number_of_retries } LOG.error(msg) raise exception.KeyManagerError(reason=msg)
def list(self, context, object_type=None, metadata_only=False): """Lists the managed objects given the criteria.""" # Confirm context is provided, if not raise forbidden if not context: msg = _("User is not authorized to use key manager.") raise exception.Forbidden(msg) if object_type and object_type not in self._secret_type_dict: msg = _("Invalid secret type: %s") % object_type raise exception.KeyManagerError(reason=msg) headers = {'X-Vault-Token': self._root_token_id} try: resource_url = self._get_url() + 'v1/secret/?list=true' resp = self._session.get(resource_url, verify=self._verify_server, headers=headers) keys = resp.json()['data']['keys'] except requests.exceptions.Timeout as ex: raise exception.KeyManagerError(six.text_type(ex)) except requests.exceptions.ConnectionError as ex: raise exception.KeyManagerError(six.text_type(ex)) except Exception as ex: raise exception.KeyManagerError(six.text_type(ex)) if resp.status_code in _EXCEPTIONS_BY_CODE: raise exception.KeyManagerError(resp.reason) if resp.status_code == requests.codes['forbidden']: raise exception.Forbidden() if resp.status_code == requests.codes['not_found']: keys = [] objects = [] for obj_id in keys: try: obj = self.get(context, obj_id, metadata_only=metadata_only) if object_type is None or isinstance(obj, object_type): objects.append(obj) except exception.ManagedObjectNotFoundError as e: LOG.warning( _("Error occurred while retrieving object " "metadata, not adding it to the list: %s"), e) pass return objects
def _do_http_request(self, method, resource, json=None): verify = self._verify_server headers = self._build_auth_headers() try: resp = method(resource, headers=headers, json=json, verify=verify) except requests.exceptions.Timeout as ex: raise exception.KeyManagerError(six.text_type(ex)) except requests.exceptions.ConnectionError as ex: raise exception.KeyManagerError(six.text_type(ex)) except Exception as ex: raise exception.KeyManagerError(six.text_type(ex)) if resp.status_code in _EXCEPTIONS_BY_CODE: raise exception.KeyManagerError(resp.reason) if resp.status_code == requests.codes['forbidden']: raise exception.Forbidden() return resp
def _get_barbican_client(self, context): """Creates a client to connect to the Barbican service. :param context: the user context for authentication :return: a Barbican Client object :raises Forbidden: if the context is None :raises KeyManagerError: if context is missing tenant or tenant is None or error occurs while creating client """ # Confirm context is provided, if not raise forbidden if not context: msg = u._("User is not authorized to use key manager.") LOG.error(msg) raise exception.Forbidden(msg) if not hasattr(context, 'tenant') or context.tenant is None: msg = u._("Unable to create Barbican Client without tenant " "attribute in context object.") LOG.error(msg) raise exception.KeyManagerError(reason=msg) if self._barbican_client and self._current_context == context: return self._barbican_client try: self._current_context = context auth = self._get_keystone_auth(context) sess = session.Session(auth=auth) self._barbican_endpoint = self._get_barbican_endpoint(auth, sess) self._barbican_client = barbican_client.Client( session=sess, endpoint=self._barbican_endpoint) except Exception as e: LOG.error(u._LE("Error creating Barbican client: %s"), e) raise exception.KeyManagerError(reason=e) self._base_url = self._create_base_url(auth, sess, self._barbican_endpoint) return self._barbican_client
def delete(self, context, key_id): """Represents deleting the key.""" if not key_id: raise exception.KeyManagerError('key identifier not provided') resp = self._do_http_request(self._session.delete, self._get_resource_url(key_id)) if resp.status_code == requests.codes['not_found']: raise exception.ManagedObjectNotFoundError(uuid=key_id)
def list(self, context, object_type=None, metadata_only=False): """Retrieves a list of managed objects that match the criteria. If no search criteria is given, all objects are returned. :param context: contains information of the user and the environment for the request (castellan/context.py) :param object_type: the type of object to retrieve :param metadata_only: whether secret data should be included :raises KeyManagerError: if listing secrets fails """ objects = [] barbican_client = self._get_barbican_client(context) if object_type and object_type not in self._secret_type_dict: msg = _("Invalid secret type: %s") % object_type LOG.error(msg) raise exception.KeyManagerError(reason=msg) secret_type = self._secret_type_dict.get(object_type) try: secrets = barbican_client.secrets.list(secret_type=secret_type) except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: LOG.error(_("Error listing objects: %s"), e) raise exception.KeyManagerError(reason=e) for secret in secrets: try: obj = self._get_castellan_object(secret, metadata_only) objects.append(obj) except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: LOG.warning( _("Error occurred while retrieving object " "metadata, not adding it to the list: %s"), e) return objects
def _store_key_value(self, key_id, value): type_value = self._secret_type_dict.get(type(value)) if type_value is None: raise exception.KeyManagerError("Unknown type for value : %r" % value) headers = {'X-Vault-Token': self._root_token_id} try: resource_url = self._get_url() + 'v1/secret/' + key_id record = { 'type': type_value, 'value': binascii.hexlify(value.get_encoded()).decode('utf-8'), 'algorithm': (value.algorithm if hasattr(value, 'algorithm') else None), 'bit_length': (value.bit_length if hasattr(value, 'bit_length') else None), 'name': value.name, 'created': value.created } resp = self._session.post(resource_url, verify=self._verify_server, json=record, headers=headers) except requests.exceptions.Timeout as ex: raise exception.KeyManagerError(six.text_type(ex)) except requests.exceptions.ConnectionError as ex: raise exception.KeyManagerError(six.text_type(ex)) except Exception as ex: raise exception.KeyManagerError(six.text_type(ex)) if resp.status_code in _EXCEPTIONS_BY_CODE: raise exception.KeyManagerError(resp.reason) if resp.status_code == requests.codes['forbidden']: raise exception.Forbidden() return key_id
def _build_auth_headers(self): if self._root_token_id: return {'X-Vault-Token': self._root_token_id} if self._approle_token_id: return {'X-Vault-Token': self._approle_token_id} if self._approle_role_id: params = { 'role_id': self._approle_role_id } if self._approle_secret_id: params['secret_id'] = self._approle_secret_id approle_login_url = '{}v1/auth/approle/login'.format( self._get_url() ) token_issue_utc = timeutils.utcnow() try: resp = self._session.post(url=approle_login_url, json=params, verify=self._verify_server) except requests.exceptions.Timeout as ex: raise exception.KeyManagerError(str(ex)) except requests.exceptions.ConnectionError as ex: raise exception.KeyManagerError(str(ex)) except Exception as ex: raise exception.KeyManagerError(str(ex)) if resp.status_code in _EXCEPTIONS_BY_CODE: raise exception.KeyManagerError(resp.reason) if resp.status_code == requests.codes['forbidden']: raise exception.Forbidden() resp = resp.json() self._cached_approle_token_id = resp['auth']['client_token'] self._approle_token_issue = token_issue_utc self._approle_token_ttl = resp['auth']['lease_duration'] return {'X-Vault-Token': self._approle_token_id} return {}
def _create_secret_ref(self, object_id): """Creates the URL required for accessing a secret. :param object_id: the UUID of the key to copy :return: the URL of the requested secret """ if not object_id: msg = _("Key ID is None") raise exception.KeyManagerError(reason=msg) base_url = self._base_url if base_url[-1] != '/': base_url += '/' return urllib.parse.urljoin(base_url, "secrets/" + object_id)
def get(self, context, key_id, metadata_only=False): """Retrieves the key identified by the specified id.""" if not key_id: raise exception.KeyManagerError('key identifier not provided') resp = self._do_http_request(self._session.get, self._get_resource_url(key_id)) if resp.status_code == requests.codes['not_found']: raise exception.ManagedObjectNotFoundError(uuid=key_id) record = resp.json()['data'] if self._get_api_version() != '1': record = record['data'] key = None if metadata_only else binascii.unhexlify(record['value']) clazz = None for type_clazz, type_name in self._secret_type_dict.items(): if type_name == record['type']: clazz = type_clazz if clazz is None: raise exception.KeyManagerError( "Unknown type : %r" % record['type']) if hasattr(clazz, 'algorithm') and hasattr(clazz, 'bit_length'): return clazz(record['algorithm'], record['bit_length'], key, record['name'], record['created'], key_id) else: return clazz(key, record['name'], record['created'], key_id)
def delete(self, context, key_id): """Represents deleting the key.""" # Confirm context is provided, if not raise forbidden if not context: msg = _("User is not authorized to use key manager.") raise exception.Forbidden(msg) if not key_id: raise exception.KeyManagerError('key identifier not provided') resp = self._do_http_request(self._session.delete, self._get_resource_url(key_id)) if resp.status_code == requests.codes['not_found']: raise exception.ManagedObjectNotFoundError(uuid=key_id)
def _create_base_url(self, auth, sess, endpoint): if self.conf.barbican.barbican_api_version: api_version = self.conf.barbican.barbican_api_version else: discovery = auth.get_discovery(sess, url=endpoint) raw_data = discovery.raw_version_data() if len(raw_data) == 0: msg = u._LE( "Could not find discovery information for %s") % endpoint LOG.error(msg) raise exception.KeyManagerError(reason=msg) latest_version = raw_data[-1] api_version = latest_version.get('id') base_url = urllib.parse.urljoin(endpoint, api_version) return base_url
def _get_barbican_client(self, context): """Creates a client to connect to the Barbican service. :param context: the user context for authentication :return: a Barbican Client object :raises Forbidden: if the context is None :raises KeyManagerError: if context is missing tenant or tenant is None or error occurs while creating client """ # Confirm context is provided, if not raise forbidden if not context: msg = _("User is not authorized to use key manager.") LOG.error(msg) raise exception.Forbidden(msg) if self._barbican_client and self._current_context == context: return self._barbican_client try: auth = self._get_keystone_auth(context) sess = session.Session(auth=auth, verify=self.conf.barbican.verify_ssl) self._barbican_endpoint = self._get_barbican_endpoint(auth, sess) self._barbican_client = barbican_client_import.Client( session=sess, endpoint=self._barbican_endpoint) self._current_context = context # TODO(pbourke): more fine grained exception handling - we are eating # tracebacks here except Exception as e: LOG.error("Error creating Barbican client: %s", e) raise exception.KeyManagerError(reason=e) self._base_url = self._create_base_url(auth, sess, self._barbican_endpoint) return self._barbican_client
def create_key_pair(self, context, algorithm, length, expiration=None, name=None): """Creates an asymmetric key pair. :param context: contains information of the user and the environment for the request (castellan/context.py) :param algorithm: the algorithm associated with the secret :param length: the bit length of the secret :param name: the name of the key :param expiration: the date the key will expire :return: the UUIDs of the new key, in the order (private, public) :raises NotImplementedError: until implemented :raises KeyManagerError: if key pair creation fails """ barbican_client = self._get_barbican_client(context) try: key_pair_order = barbican_client.orders.create_asymmetric( algorithm=algorithm, bit_length=length, name=name, expiration=expiration) order_ref = key_pair_order.submit() order = self._get_active_order(barbican_client, order_ref) container = barbican_client.containers.get(order.container_ref) private_key_uuid = self._retrieve_secret_uuid( container.secret_refs['private_key']) public_key_uuid = self._retrieve_secret_uuid( container.secret_refs['public_key']) return private_key_uuid, public_key_uuid except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: LOG.error("Error creating key pair: %s", e) raise exception.KeyManagerError(reason=e)
def get(self, context, key_id, metadata_only=False): """Retrieves the key identified by the specified id.""" # Confirm context is provided, if not raise forbidden if not context: msg = _("User is not authorized to use key manager.") raise exception.Forbidden(msg) if not key_id: raise exception.KeyManagerError('key identifier not provided') headers = {'X-Vault-Token': self._root_token_id} try: resource_url = self._get_url() + 'v1/secret/' + key_id resp = self._session.get(resource_url, verify=self._verify_server, headers=headers) except requests.exceptions.Timeout as ex: raise exception.KeyManagerError(six.text_type(ex)) except requests.exceptions.ConnectionError as ex: raise exception.KeyManagerError(six.text_type(ex)) except Exception as ex: raise exception.KeyManagerError(six.text_type(ex)) if resp.status_code in _EXCEPTIONS_BY_CODE: raise exception.KeyManagerError(resp.reason) if resp.status_code == requests.codes['forbidden']: raise exception.Forbidden() if resp.status_code == requests.codes['not_found']: raise exception.ManagedObjectNotFoundError(uuid=key_id) record = resp.json()['data'] key = None if metadata_only else binascii.unhexlify(record['value']) clazz = None for type_clazz, type_name in self._secret_type_dict.items(): if type_name == record['type']: clazz = type_clazz if clazz is None: raise exception.KeyManagerError("Unknown type : %r" % record['type']) if hasattr(clazz, 'algorithm') and hasattr(clazz, 'bit_length'): return clazz(record['algorithm'], record['bit_length'], key, record['name'], record['created'], key_id) else: return clazz(key, record['name'], record['created'], key_id)
def get(self, context, managed_object_id): """Retrieves the specified managed object. :param context: contains information of the user and the environment for the request (castellan/context.py) :param managed_object_id: the UUID of the object to retrieve :return: ManagedObject representation of the managed object :raises KeyManagerError: if object retrieval fails :raises ManagedObjectNotFoundError: if object not found """ try: secret = self._get_secret(context, managed_object_id) return self._get_castellan_object(secret) except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: LOG.error(_LE("Error retrieving object: %s"), e) if self._is_secret_not_found_error(e): raise exception.ManagedObjectNotFoundError( uuid=managed_object_id) else: raise exception.KeyManagerError(reason=e)
def delete(self, context, managed_object_id): """Deletes the specified managed object. :param context: contains information of the user and the environment for the request (castellan/context.py) :param managed_object_id: the UUID of the object to delete :raises KeyManagerError: if object deletion fails :raises ManagedObjectNotFoundError: if the object could not be found """ barbican_client = self._get_barbican_client(context) try: secret_ref = self._create_secret_ref(managed_object_id) barbican_client.secrets.delete(secret_ref) except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: LOG.error("Error deleting object: %s", e) if self._is_secret_not_found_error(e): raise exception.ManagedObjectNotFoundError( uuid=managed_object_id) else: raise exception.KeyManagerError(reason=e)
def get_latest_key(self, context, mode=None, algorithm=None, bits=0, name=None): """ Retrieve the user's latest root encryption secret from an external key management system using Castellan. :param context: the user context for authentication :param user_token: the mode of the key to look for :param algorithm: the algorithm of the key to look for :param bits: the bitlength of the key to look for :param name: the name of the key to look for :return: a tuple containing the id of the latest root key, and the castellan object representation of the key :rtype: (string, castellan object) """ barbican_client = self._get_barbican_client(context) latest_secret = None latest_secret_id = None offset = 0 more = True try: while more: # List all keys that match the requirements found = barbican_client.secrets.list(limit=LIST_LIMIT, offset=offset, name=name, algorithm=algorithm, mode=mode, bits=bits) # No (more) results if found is None: break # At least one result if len(found) > 0: for s in found: secret = self._get_castellan_object(s) secret_id = s.secret_ref.rpartition('/')[-1] # Check if the key is the most latest one if self._is_current_key_latest(latest_secret, latest_secret_id, secret, secret_id): latest_secret = secret latest_secret_id = secret_id # If there were less than LIST_LIMIT results, we have reached # the last page of results. if len(found) < LIST_LIMIT: break # Otherwise, continue with the next page of results. offset += LIST_LIMIT return latest_secret_id, latest_secret except (barbican_exceptions.HTTPAuthError, barbican_exceptions.HTTPClientError, barbican_exceptions.HTTPServerError) as e: LOG.error("Error listing keys: %s", e) raise exception.KeyManagerError(reason=e)