Skip to content

Commit

Permalink
Fix redacting secrets in context exceptions. (#17618)
Browse files Browse the repository at this point in the history
* Fix redacting secrets in context exceptions.

Secret masking did not work in implicit and
explicit context exceptions (see
https://www.python.org/dev/peps/pep-3134/)
When there was a `try/except/raise` sequence,
or `raise ... from` exception - the original
exceptions were not redacted.

Related: #17604
  • Loading branch information
potiuk authored Aug 14, 2021
1 parent 1632c9f commit 6df3ee7
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 2 deletions.
10 changes: 8 additions & 2 deletions airflow/utils/log/secrets_masker.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ def _record_attrs_to_ignore(self) -> Iterable[str]:
)
return frozenset(record.__dict__).difference({'msg', 'args'})

def _redact_exception_with_context(self, exception):
exception.args = (self.redact(v) for v in exception.args)
if exception.__context__:
self._redact_exception_with_context(exception.__context__)
if exception.__cause__ and exception.__cause__ is not exception.__context__:
self._redact_exception_with_context(exception.__cause__)

def filter(self, record) -> bool:
if self.ALREADY_FILTERED_FLAG in record.__dict__:
# Filters are attached to multiple handlers and logs, keep a
Expand All @@ -157,8 +164,7 @@ def filter(self, record) -> bool:
record.__dict__[k] = self.redact(v)
if record.exc_info and record.exc_info[1] is not None:
exc = record.exc_info[1]
# I'm not sure if this is a good idea!
exc.args = (self.redact(v) for v in exc.args)
self._redact_exception_with_context(exc)
record.__dict__[self.ALREADY_FILTERED_FLAG] = True

return True
Expand Down
76 changes: 76 additions & 0 deletions tests/utils/log/test_secrets_masker.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
from airflow.utils.log.secrets_masker import SecretsMasker, should_hide_value_for_key
from tests.test_utils.config import conf_vars

p = "password"


@pytest.fixture
def logger(caplog):
Expand Down Expand Up @@ -145,6 +147,80 @@ def test_exc_tb(self, logger, caplog):
"""
)

def test_masking_in_implicit_context_exceptions(self, logger, caplog):
"""
Show that redacting password works in context exceptions.
"""
try:
try:
try:
raise RuntimeError(f"Cannot connect to user:{p}")
except RuntimeError as ex1:
raise RuntimeError(f'Exception: {ex1}')
except RuntimeError as ex2:
raise RuntimeError(f'Exception: {ex2}')
except RuntimeError:
logger.exception("Err")

line = lineno() - 8

assert caplog.text == textwrap.dedent(
f"""\
ERROR Err
Traceback (most recent call last):
File ".../test_secrets_masker.py", line {line}, in test_masking_in_implicit_context_exceptions
raise RuntimeError(f"Cannot connect to user:{{p}}")
RuntimeError: Cannot connect to user:***
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File ".../test_secrets_masker.py", line {line+2}, in test_masking_in_implicit_context_exceptions
raise RuntimeError(f'Exception: {{ex1}}')
RuntimeError: Exception: Cannot connect to user:***
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File ".../test_secrets_masker.py", line {line+4}, in test_masking_in_implicit_context_exceptions
raise RuntimeError(f'Exception: {{ex2}}')
RuntimeError: Exception: Exception: Cannot connect to user:***
"""
)

def test_masking_in_explicit_context_exceptions(self, logger, caplog):
"""
Show that redacting password works in context exceptions.
"""
exception = None
try:
raise RuntimeError(f"Cannot connect to user:{p}")
except RuntimeError as ex:
exception = ex
try:
raise RuntimeError(f'Exception: {exception}') from exception
except RuntimeError:
logger.exception("Err")

line = lineno() - 8

assert caplog.text == textwrap.dedent(
f"""\
ERROR Err
Traceback (most recent call last):
File ".../test_secrets_masker.py", line {line}, in test_masking_in_explicit_context_exceptions
raise RuntimeError(f"Cannot connect to user:{{p}}")
RuntimeError: Cannot connect to user:***
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File ".../test_secrets_masker.py", line {line+4}, in test_masking_in_explicit_context_exceptions
raise RuntimeError(f'Exception: {{exception}}') from exception
RuntimeError: Exception: Cannot connect to user:***
"""
)

@pytest.mark.parametrize(
("name", "value", "expected_mask"),
[
Expand Down

0 comments on commit 6df3ee7

Please sign in to comment.