def _base64_encode_payload(self, secret_doc): """Ensures secret document payload is compatible with Barbican.""" payload = secret_doc.data secret_type = None # Explicitly list the "empty" payloads we are refusing to store. # We don't use ``if not payload`` because that would not encrypt # and store something like ``data: !!int 0`` if payload in ('', {}, [], None): # There is no point in even bothering to encrypt an empty # body, which just leads to needless overhead, so return # early. LOG.info('Barbican does not accept empty payloads so ' 'Deckhand will not encrypt document [%s, %s] %s.', secret_doc.schema, secret_doc.layer, secret_doc.name) secret_doc.storage_policy = types.CLEARTEXT else: LOG.debug('Setting secret_type=opaque and ' 'base64-encoding payload of type %s for ' 'document [%s, %s] %s.', type(payload), secret_doc.schema, secret_doc.layer, secret_doc.name) secret_type = 'opaque' # nosec # not a hardcoded password try: payload = base64.encode_as_text(repr(payload)) except Exception: message = ('Failed to base64-encode payload of type %s ' 'for Barbican storage.', type(payload)) LOG.error(message) raise errors.UnknownSubstitutionError( src_schema=secret_doc.schema, src_layer=secret_doc.layer, src_name=secret_doc.name, schema='N/A', layer='N/A', name='N/A', details=message) return secret_type, payload
def _handle_unknown_substitution_exc(self, exc_message, src_doc, dest_doc): if self._fail_on_missing_sub_src: LOG.error(exc_message) raise errors.UnknownSubstitutionError( src_schema=src_doc.schema, src_layer=src_doc.layer, src_name=src_doc.name, schema=dest_doc.schema, layer=dest_doc.layer, name=dest_doc.name, details=exc_message) else: LOG.warning(exc_message)
def _base64_encode_payload(self, secret_doc): """Ensures secret document payload is compatible with Barbican.""" payload = secret_doc.data secret_type = self._get_secret_type(secret_doc.schema) # NOTE(felipemonteiro): The logic for the 2 conditions below is # enforced from Barbican's Python client. Some pre-processing and # transformation is needed to make Barbican work with non-compatible # formats. if not payload and payload is not False: # There is no point in even bothering to encrypt an empty # body, which just leads to needless overhead, so return # early. LOG.info( 'Barbican does not accept empty payloads so ' 'Deckhand will not encrypt document [%s, %s] %s.', secret_doc.schema, secret_doc.layer, secret_doc.name) secret_doc.storage_policy = types.CLEARTEXT elif not isinstance(payload, (six.text_type, six.binary_type)): LOG.debug( 'Forcibly setting secret_type=opaque and ' 'base64-encoding non-string payload for ' 'document [%s, %s] %s.', secret_doc.schema, secret_doc.layer, secret_doc.name) # NOTE(felipemonteiro): base64-encoding the non-string payload is # done for serialization purposes, not for security purposes. # 'opaque' is used to avoid Barbican doing any further # serialization server-side. secret_type = 'opaque' try: payload = base64.encode_as_text(six.text_type(payload)) except Exception: message = ('Failed to base64-encode payload of type %s ' 'for Barbican storage.', type(payload)) LOG.error(message) raise errors.UnknownSubstitutionError( src_schema=secret_doc.schema, src_layer=secret_doc.layer, src_name=secret_doc.name, schema='N/A', layer='N/A', name='N/A', details=message) return secret_type, payload
def substitute_all(self, documents): """Substitute all documents that have a `metadata.substitutions` field. Concrete (non-abstract) documents can be used as a source of substitution into other documents. This substitution is layer-independent, a document in the region layer could insert data from a document in the site layer. :param documents: List of documents that are candidates for substitution. :type documents: dict or List[dict] :returns: List of fully substituted documents. :rtype: Generator[:class:`DocumentDict`] :raises SubstitutionSourceNotFound: If a substitution source document is referenced by another document but wasn't found. :raises UnknownSubstitutionError: If an unknown error occurred during substitution. """ documents_to_substitute = [] if not isinstance(documents, list): documents = [documents] for document in documents: if not isinstance(document, document_wrapper.DocumentDict): document = document_wrapper.DocumentDict(document) # If the document has substitutions include it. if document.substitutions: documents_to_substitute.append(document) LOG.debug( 'Performing substitution on following documents: %s', ', '.join([ '[%s] %s' % (d.schema, d.name) for d in documents_to_substitute ])) for document in documents_to_substitute: LOG.debug('Checking for substitutions for document [%s] %s.', document.schema, document.name) for sub in document.substitutions: src_schema = sub['src']['schema'] src_name = sub['src']['name'] src_path = sub['src']['path'] if (src_schema, src_name) in self._substitution_sources: src_doc = self._substitution_sources[(src_schema, src_name)] else: message = ('Could not find substitution source document ' '[%s] %s among the provided ' '`substitution_sources`.', src_schema, src_name) LOG.error(message) raise errors.SubstitutionSourceNotFound( src_schema=src_schema, src_name=src_name, document_schema=document.schema, document_name=document.name) # If the data is a dictionary, retrieve the nested secret # via jsonpath_parse, else the secret is the primitive/string # stored in the data section itself. if isinstance(src_doc.get('data'), dict): src_secret = utils.jsonpath_parse(src_doc.get('data', {}), src_path) else: src_secret = src_doc.get('data') dest_path = sub['dest']['path'] dest_pattern = sub['dest'].get('pattern', None) LOG.debug( 'Substituting from schema=%s name=%s src_path=%s ' 'into dest_path=%s, dest_pattern=%s', src_schema, src_name, src_path, dest_path, dest_pattern) try: substituted_data = utils.jsonpath_replace( document['data'], src_secret, dest_path, dest_pattern) sub_source = self._substitution_sources.get( (document.schema, document.name)) if (isinstance(document['data'], dict) and isinstance(substituted_data, dict)): document['data'].update(substituted_data) if sub_source: sub_source['data'].update(substituted_data) elif substituted_data: document['data'] = substituted_data if sub_source: sub_source['data'] = substituted_data else: message = ('Failed to create JSON path "%s" in the ' 'destination document [%s] %s. No data was ' 'substituted.', dest_path, document.schema, document.name) LOG.error(message) raise errors.UnknownSubstitutionError(details=message) except Exception as e: LOG.error( 'Unexpected exception occurred while attempting ' 'secret substitution. %s', six.text_type(e)) raise errors.UnknownSubstitutionError( details=six.text_type(e)) yield document