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 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 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 _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 _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 create_key(self, context, algorithm, length, name=None, **kwargs): """Creates a symmetric 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 length % 8: msg = _("Length must be multiple of 8.") raise ValueError(msg) key_id = uuid.uuid4().hex key_value = os.urandom((length or 256) // 8) key = sym_key.SymmetricKey(algorithm, length or 256, key_value, key_id, name or int(time.time())) return self._store_key_value(key_id, key)
def store(self, context, key_value, **kwargs): """Stores (i.e., registers) a key with the key manager.""" # Confirm context is provided, if not raise forbidden if not context: msg = _("User is not authorized to use key manager.") raise exception.Forbidden(msg) key_id = uuid.uuid4().hex return self._store_key_value(key_id, key_value)
def store(self, context, key_value, **kwargs): """Stores (i.e., registers) a key with the key manager.""" # Confirm context is provided, if not raise forbidden if not context: msg = _("User is not authorized to use key manager.") raise exception.Forbidden(msg) key_id = uuid.uuid4().hex return self._store_key_value(key_id, key_value)
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 create_key(self, context, algorithm, length, name=None, **kwargs): """Creates a symmetric 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 length % 8: msg = _("Length must be multiple of 8.") raise ValueError(msg) key_id = uuid.uuid4().hex key_value = os.urandom((length or 256) // 8) key = sym_key.SymmetricKey(algorithm, length or 256, key_value, key_id, name or int(time.time())) return self._store_key_value(key_id, key)
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 create_key_pair(self, context, algorithm, length, expiration=None, name=None): """Creates an asymmetric key pair.""" # 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 algorithm.lower() != 'rsa': raise NotImplementedError( "VaultKeyManager only implements rsa keys" ) priv_key = rsa.generate_private_key( public_exponent=65537, key_size=length, backend=default_backend() ) private_key = pri_key.PrivateKey( 'RSA', length, priv_key.private_bytes( Encoding.PEM, PrivateFormat.PKCS8, NoEncryption() ) ) private_key_id = uuid.uuid4().hex private_id = self._store_key_value( private_key_id, private_key ) # pub_key = priv_key.public_key() public_key = pub_key.PublicKey( 'RSA', length, priv_key.public_key().public_bytes( Encoding.PEM, PublicFormat.SubjectPublicKeyInfo ) ) public_key_id = uuid.uuid4().hex public_id = self._store_key_value( public_key_id, public_key ) return private_id, public_id
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 create_key(self, context, algorithm, length, name=None, **kwargs): """Creates a symmetric key.""" if length % 8: msg = _("Length must be multiple of 8.") raise ValueError(msg) key_id = uuid.uuid4().hex key_value = os.urandom((length or 256) // 8) key = sym_key.SymmetricKey(algorithm, length or 256, key_value, key_id, name or int(time.time())) return self._store_key_value(key_id, key)
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 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 _get_keystone_auth(self, context): if context.__class__.__name__ == 'KeystonePassword': return identity.Password( auth_url=context.auth_url, username=context.username, password=context.password, user_id=context.user_id, user_domain_id=context.user_domain_id, user_domain_name=context.user_domain_name, trust_id=context.trust_id, domain_id=context.domain_id, domain_name=context.domain_name, project_id=context.project_id, project_name=context.project_name, project_domain_id=context.project_domain_id, project_domain_name=context.project_domain_name, reauthenticate=context.reauthenticate) elif context.__class__.__name__ == 'KeystoneToken': return identity.Token( auth_url=context.auth_url, token=context.token, trust_id=context.trust_id, domain_id=context.domain_id, domain_name=context.domain_name, project_id=context.project_id, project_name=context.project_name, project_domain_id=context.project_domain_id, project_domain_name=context.project_domain_name, reauthenticate=context.reauthenticate) # this will be kept for oslo.context compatibility until # projects begin to use utils.credential_factory elif context.__class__.__name__ == 'RequestContext': if getattr(context, 'get_auth_plugin', None): return context.get_auth_plugin() else: return identity.Token( auth_url=self.conf.barbican.auth_endpoint, token=context.auth_token, project_id=context.project_id, project_name=context.project_name, project_domain_id=context.project_domain_id, project_domain_name=context.project_domain_name) else: msg = _("context must be of type KeystonePassword, " "KeystoneToken, or RequestContext.") LOG.error(msg) raise exception.Forbidden(reason=msg)
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') 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 _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 = _( "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 _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
class CastellanException(Exception): """Base Castellan Exception To correctly use this class, inherit from it and define a 'message' property. That message will get printf'd with the keyword arguments provided to the constructor. """ message = _("An unknown exception occurred") def __init__(self, message_arg=None, *args, **kwargs): if not message_arg: message_arg = self.message try: self.message = message_arg % kwargs except Exception: if _FATAL_EXCEPTION_FORMAT_ERRORS: raise else: # at least get the core message out if something happened pass super(CastellanException, self).__init__(self.message)
def create_key_pair(self, context, algorithm, length, expiration=None, name=None): """Creates an asymmetric key pair.""" # 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 algorithm.lower() != 'rsa': raise NotImplementedError( "VaultKeyManager only implements rsa keys") priv_key = rsa.generate_private_key(public_exponent=65537, key_size=length, backend=default_backend()) private_key = pri_key.PrivateKey( 'RSA', length, priv_key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())) private_key_id = uuid.uuid4().hex private_id = self._store_key_value(private_key_id, private_key) # pub_key = priv_key.public_key() public_key = pub_key.PublicKey( 'RSA', length, priv_key.public_key().public_bytes( Encoding.PEM, PublicFormat.SubjectPublicKeyInfo)) public_key_id = uuid.uuid4().hex public_id = self._store_key_value(public_key_id, public_key) return private_id, public_id
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') 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)
help='AppRole role_id for authentication with vault'), cfg.StrOpt('approle_secret_id', help='AppRole secret_id for authentication with vault'), cfg.StrOpt('kv_mountpoint', default=_DEFAULT_MOUNTPOINT, help='Mountpoint of KV store in Vault to use, for example: ' '{}'.format(_DEFAULT_MOUNTPOINT)), cfg.StrOpt('vault_url', default=_DEFAULT_VAULT_URL, help='Use this endpoint to connect to Vault, for example: ' '"%s"' % _DEFAULT_VAULT_URL), cfg.StrOpt('ssl_ca_crt_file', help='Absolute path to ca cert file'), cfg.BoolOpt('use_ssl', default=False, help=_('SSL Enabled/Disabled')), ] _VAULT_OPT_GROUP = 'vault' _EXCEPTIONS_BY_CODE = [ requests.codes['internal_server_error'], requests.codes['service_unavailable'], requests.codes['request_timeout'], requests.codes['gateway_timeout'], requests.codes['precondition_failed'], ] LOG = logging.getLogger(__name__)
class AuthTypeInvalidError(CastellanException): message = _("Invalid auth_type was specified, auth_type: %(type)s")
help='AppRole role_id for authentication with vault'), cfg.StrOpt('approle_secret_id', help='AppRole secret_id for authentication with vault'), cfg.StrOpt('kv_mountpoint', default=DEFAULT_MOUNTPOINT, help='Mountpoint of KV store in Vault to use, for example: ' '{}'.format(DEFAULT_MOUNTPOINT)), cfg.StrOpt('vault_url', default=DEFAULT_VAULT_URL, help='Use this endpoint to connect to Vault, for example: ' '"%s"' % DEFAULT_VAULT_URL), cfg.StrOpt('ssl_ca_crt_file', help='Absolute path to ca cert file'), cfg.BoolOpt('use_ssl', default=False, help=_('SSL Enabled/Disabled')), ] VAULT_OPT_GROUP = 'vault' _EXCEPTIONS_BY_CODE = [ requests.codes['internal_server_error'], requests.codes['service_unavailable'], requests.codes['request_timeout'], requests.codes['gateway_timeout'], requests.codes['precondition_failed'], ] LOG = logging.getLogger(__name__)
from oslo_config import cfg from oslo_log import log LOG = log.getLogger(__name__) DEFAULT_VAULT_URL = "http://127.0.0.1:8200" vault_opt_group = cfg.OptGroup(name='vault_plugin', title='Vault Plugin') vault_opts = [ cfg.StrOpt('root_token_id', help='root token for vault'), cfg.StrOpt('vault_url', default=DEFAULT_VAULT_URL, help='Use this endpoint to connect to Vault, for example: ' '"%s"' % DEFAULT_VAULT_URL), cfg.StrOpt('ssl_ca_crt_file', help='Absolute path to ca cert file'), cfg.BoolOpt('use_ssl', default=False, help=_('SSL Enabled/Disabled')), ] CONF = config.new_config() CONF.register_group(vault_opt_group) CONF.register_opts(vault_opts, group=vault_opt_group) config.parse_args(CONF) def list_opts(): yield vault_opt_group, vault_opts # pragma: no cover class VaultSecretStore(css.CastellanSecretStore): def __init__(self, conf=CONF): """Constructor - create the vault secret store."""
class ManagedObjectNotFoundError(CastellanException): message = _("Key not found, uuid: %(uuid)s")
class Forbidden(CastellanException): message = _("You are not authorized to complete this action.")
class InsufficientCredentialDataError(CastellanException): message = _("Insufficient credential data was provided, either " "\"token\" must be set in the passed conf, or a context " "with an \"auth_token\" property must be passed.")
class KeyManagerError(CastellanException): message = _("Key manager error: %(reason)s")