Raise exception on unfound secret in source document

This PS introduces a new exception SubstitutionSourceSecretNotFound
which is raised when a src.path referenced under the substitutions
of a destination document isn't found in the data section of
the corresponding source document if fail_on_missing_sub_src
is True -- else a warning is logged.

Change-Id: If2b08f443cde765a1dbfaf7bac6b549591e59148
This commit is contained in:
Felipe Monteiro 2018-04-05 16:26:11 -04:00
parent 35f15ee601
commit a3d32c3459
6 changed files with 120 additions and 28 deletions

View File

@ -213,9 +213,10 @@ class SecretsSubstitution(object):
substitution; documents need not be filtered prior to being passed to
the constructor.
:param substitution_sources: List of documents that are potential
sources for substitution. Or dict of documents keyed on tuple of
(schema, metadata.name). Should only include concrete documents.
:param substitution_sources: (DEPRECATED) List of documents that are
potential sources for substitution. Or dict of documents keyed on
tuple of (schema, metadata.name). Should only include concrete
documents.
:type substitution_sources: List[dict] or dict
:param bool fail_on_missing_sub_src: Whether to fail on a missing
substitution source. Default is True.
@ -239,6 +240,51 @@ class SecretsSubstitution(object):
self._substitution_sources.setdefault(
(document.schema, document.name), document)
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 _get_encrypted_secret(self, src_secret, src_doc, dest_doc):
try:
src_secret = SecretsManager.get(src_secret)
except errors.BarbicanException as e:
LOG.error(
'Failed to resolve a Barbican reference for substitution '
'source document [%s, %s] %s referenced in document [%s, %s] '
'%s. Details: %s', src_doc.schema, src_doc.layer, src_doc.name,
dest_doc.schema, dest_doc.layer, dest_doc.name,
e.format_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=e.format_message())
else:
return src_secret
def _check_src_secret_is_not_none(self, src_secret, src_path, src_doc,
dest_doc):
if src_secret is None:
if self._fail_on_missing_sub_src:
raise errors.SubstitutionSourceDataNotFound(
src_path=src_path, src_schema=src_doc.schema,
src_layer=src_doc.layer, src_name=src_doc.name,
dest_schema=dest_doc.schema, dest_layer=dest_doc.layer,
dest_name=dest_doc.name)
else:
LOG.warning('Could not find source path %s in source document '
'or the secret extracted is None. Source document:'
' [%s, %s] %s. Destination document: [%s, %s] %s.',
src_path, src_doc.schema, src_doc.layer,
src_doc.name, dest_doc.schema, dest_doc.layer,
dest_doc.name)
def substitute_all(self, documents):
"""Substitute all documents that have a `metadata.substitutions` field.
@ -307,24 +353,14 @@ class SecretsSubstitution(object):
else:
src_secret = src_doc.get('data')
self._check_src_secret_is_not_none(src_secret, src_path,
src_doc, document)
# If the document has storagePolicy == encrypted then resolve
# the Barbican reference into the actual secret.
if src_doc.is_encrypted:
try:
src_secret = SecretsManager.get(src_secret)
except errors.BarbicanException as e:
LOG.error(
'Failed to resolve a Barbican reference for '
'substitution source document [%s, %s] %s '
'referenced in document [%s, %s] %s. Details: %s',
src_schema, src_doc.layer, src_name,
document.schema, document.layer, document.name,
e.format_message())
raise errors.UnknownSubstitutionError(
src_schema=src_schema, src_layer=src_doc.layer,
src_name=src_name, schema=document.schema,
layer=document.layer, name=document.name,
details=e.format_message())
src_secret = self._get_encrypted_secret(
src_secret, src_doc, document)
dest_path = sub['dest']['path']
dest_pattern = sub['dest'].get('pattern', None)
@ -357,12 +393,8 @@ class SecretsSubstitution(object):
exc_message = six.text_type(e)
finally:
if exc_message:
LOG.error(exc_message)
raise errors.UnknownSubstitutionError(
src_schema=src_schema, src_layer=src_doc.layer,
src_name=src_name, schema=document.schema,
layer=document.layer, name=document.name,
details=exc_message)
self._handle_unknown_substitution_exc(
exc_message, src_doc, document)
yield document

View File

@ -302,6 +302,26 @@ class RevisionTagBadFormat(DeckhandException):
code = 400
class SubstitutionSourceDataNotFound(DeckhandException):
"""Required substitution source secret was not found in the substitution
source document at the path ``metadata.substitutions.[*].src.path`` in the
destination document.
**Troubleshoot:**
* Ensure that the missing source secret exists at the ``src.path``
specified under the given substitution in the destination document and
that the ``src.path`` itself exists in the source document.
"""
msg_fmt = (
"Required substitution source secret was not found at path "
"%(src_path)s in source document [%(src_schema)s, %(src_layer)s] "
"%(src_name)s which is referenced by destination document "
"[%(dest_schema)s, %(dest_layer)s] %(dest_name)s under its "
"`metadata.substitutions`.")
code = 400
class DocumentNotFound(DeckhandException):
"""The requested document could not be found.
@ -376,7 +396,7 @@ class LayeringPolicyNotFound(DeckhandException):
class SubstitutionSourceNotFound(DeckhandException):
"""Required substitution source document was not found for layering.
"""Required substitution source document was not found.
**Troubleshoot:**

View File

@ -90,6 +90,8 @@ class TestDocumentLayeringWithSubstitutionNegative(
In the case below, a self-reference or cycle exists for site-1 with
itself.
"""
# TODO(fmontei): Move to test_secrets_manager (negative)
mapping = {
"_GLOBAL_DATA_1_": {"data": {"a": {"x": 1, "y": 2}}},
"_SITE_NAME_1_": "site-1",
@ -119,6 +121,8 @@ class TestDocumentLayeringWithSubstitutionNegative(
def test_layering_with_missing_substitution_source_raises_exc(
self, mock_log):
"""Validate that a missing substitution source document fails."""
# TODO(fmontei): Move to test_secrets_manager (negative)
mapping = {
"_GLOBAL_SUBSTITUTIONS_1_": [{
"dest": {

View File

@ -737,8 +737,7 @@ class TestSecretsSubstitutionNegative(test_base.TestDbBase):
documents = self.create_documents(
bucket_name, [certificate] + [payload[-1]])
secrets_substitution = secrets_manager.SecretsSubstitution(
[certificate])
secrets_substitution = secrets_manager.SecretsSubstitution(documents)
with testtools.ExpectedException(expected_exception):
next(secrets_substitution.substitute_all(documents))
@ -755,3 +754,35 @@ class TestSecretsSubstitutionNegative(test_base.TestDbBase):
mock_utils.jsonpath_replace.side_effect = Exception('test')
self._test_secrets_substitution(
'cleartext', errors.UnknownSubstitutionError)
def test_secret_substititon_missing_src_path_in_src_doc_raises_exc(self):
"""Validates that if a secret can't be found in a substitution
source document then an exception is raised.
"""
certificate = self.secrets_factory.gen_test(
'Certificate', 'cleartext', data={})
certificate['metadata']['name'] = 'example-cert'
document_mapping = {
"_GLOBAL_SUBSTITUTIONS_1_": [{
"dest": {
"path": ".chart.values.tls.certificate"
},
"src": {
"schema": "deckhand/Certificate/v1",
"name": "example-cert",
"path": ".path-to-nowhere"
}
}]
}
payload = self.document_factory.gen_test(document_mapping,
global_abstract=False)
bucket_name = test_utils.rand_name('bucket')
documents = self.create_documents(
bucket_name, [certificate] + [payload[-1]])
secrets_substitution = secrets_manager.SecretsSubstitution(documents)
with testtools.ExpectedException(
errors.SubstitutionSourceDataNotFound):
next(secrets_substitution.substitute_all(documents))

View File

@ -94,6 +94,11 @@ Deckhand Exceptions
:members:
:show-inheritance:
:undoc-members:
* - SubstitutionSourceDataNotFound
- .. autoexception:: deckhand.errors.SubstitutionSourceDataNotFound
:members:
:show-inheritance:
:undoc-members:
* - SubstitutionSourceNotFound
- .. autoexception:: deckhand.errors.SubstitutionSourceNotFound
:members:

View File

@ -190,7 +190,7 @@ For example:
- method: replace
path: .debug
data:
debug: True
debug: true
...
And: