redact project update urls when downloading stdout

* For ProjectUpdate jobs. Redact potentially sensitive urls from the
output.
This commit is contained in:
chris meyers
2018-04-13 15:26:40 -04:00
parent 04693ecb0f
commit 09d5645b90
2 changed files with 32 additions and 13 deletions

View File

@@ -77,6 +77,7 @@ from awx.main.utils import (
from awx.main.utils.encryption import encrypt_value from awx.main.utils.encryption import encrypt_value
from awx.main.utils.filters import SmartFilter from awx.main.utils.filters import SmartFilter
from awx.main.utils.insights import filter_insights_api_response from awx.main.utils.insights import filter_insights_api_response
from awx.main.redact import UriCleaner
from awx.api.permissions import ( from awx.api.permissions import (
JobTemplateCallbackPermission, JobTemplateCallbackPermission,
TaskPermission, TaskPermission,
@@ -4645,9 +4646,17 @@ class UnifiedJobList(ListAPIView):
serializer_class = UnifiedJobListSerializer serializer_class = UnifiedJobListSerializer
class StdoutANSIFilter(object): def redact_ansi(line):
# Remove ANSI escape sequences used to embed event data.
line = re.sub(r'\x1b\[K(?:[A-Za-z0-9+/=]+\x1b\[\d+D)+\x1b\[K', '', line)
# Remove ANSI color escape sequences.
return re.sub(r'\x1b[^m]*m', '', line)
class StdoutFilter(object):
def __init__(self, fileobj): def __init__(self, fileobj):
self._functions = []
self.fileobj = fileobj self.fileobj = fileobj
self.extra_data = '' self.extra_data = ''
if hasattr(fileobj, 'close'): if hasattr(fileobj, 'close'):
@@ -4659,10 +4668,7 @@ class StdoutANSIFilter(object):
line = self.fileobj.readline(size) line = self.fileobj.readline(size)
if not line: if not line:
break break
# Remove ANSI escape sequences used to embed event data. line = self.process_line(line)
line = re.sub(r'\x1b\[K(?:[A-Za-z0-9+/=]+\x1b\[\d+D)+\x1b\[K', '', line)
# Remove ANSI color escape sequences.
line = re.sub(r'\x1b[^m]*m', '', line)
data += line data += line
if size > 0 and len(data) > size: if size > 0 and len(data) > size:
self.extra_data = data[size:] self.extra_data = data[size:]
@@ -4671,6 +4677,14 @@ class StdoutANSIFilter(object):
self.extra_data = '' self.extra_data = ''
return data return data
def register(self, func):
self._functions.append(func)
def process_line(self, line):
for func in self._functions:
line = func(line)
return line
class UnifiedJobStdout(RetrieveAPIView): class UnifiedJobStdout(RetrieveAPIView):
@@ -4728,9 +4742,12 @@ class UnifiedJobStdout(RetrieveAPIView):
suffix='.ansi' if target_format == 'ansi_download' else '' suffix='.ansi' if target_format == 'ansi_download' else ''
) )
content_fd = unified_job.result_stdout_raw_handle(enforce_max_bytes=False) content_fd = unified_job.result_stdout_raw_handle(enforce_max_bytes=False)
redactor = StdoutFilter(content_fd)
if target_format == 'txt_download': if target_format == 'txt_download':
content_fd = StdoutANSIFilter(content_fd) redactor.register(redact_ansi)
response = HttpResponse(FileWrapper(content_fd), content_type='text/plain') if type(unified_job) == ProjectUpdate:
redactor.register(UriCleaner.remove_sensitive)
response = HttpResponse(FileWrapper(redactor), content_type='text/plain')
response["Content-Disposition"] = 'attachment; filename="{}"'.format(filename) response["Content-Disposition"] = 'attachment; filename="{}"'.format(filename)
return response return response
else: else:

View File

@@ -79,20 +79,22 @@ TEST_CLEARTEXT.append({
}) })
@pytest.mark.parametrize('username, password, not_uri', [ @pytest.mark.parametrize('username, password, not_uri, expected', [
('', '', 'www.famfamfam.com](http://www.famfamfam.com/fijdlfd'), ('', '', 'www.famfamfam.com](http://www.famfamfam.com/fijdlfd', 'www.famfamfam.com](http://www.famfamfam.com/fijdlfd'),
('', '', 'https://www.famfamfam.com](http://www.famfamfam.com/fijdlfd'), ('', '', 'https://www.famfamfam.com](http://www.famfamfam.com/fijdlfd', '$encrypted$'),
('root', 'gigity', 'https://root@gigity@www.famfamfam.com](http://www.famfamfam.com/fijdlfd'), ('root', 'gigity', 'https://root@gigity@www.famfamfam.com](http://www.famfamfam.com/fijdlfd', '$encrypted$'),
('root', 'gigity@', 'https://root:gigity@@@www.famfamfam.com](http://www.famfamfam.com/fijdlfd'), ('root', 'gigity@', 'https://root:gigity@@@www.famfamfam.com](http://www.famfamfam.com/fijdlfd', '$encrypted$'),
]) ])
# should redact sensitive usernames and passwords # should redact sensitive usernames and passwords
def test_non_uri_redact(username, password, not_uri): def test_non_uri_redact(username, password, not_uri, expected):
redacted_str = UriCleaner.remove_sensitive(not_uri) redacted_str = UriCleaner.remove_sensitive(not_uri)
if username: if username:
assert username not in redacted_str assert username not in redacted_str
if password: if password:
assert password not in redacted_str assert password not in redacted_str
assert redacted_str == expected
def test_multiple_non_uri_redact(): def test_multiple_non_uri_redact():
non_uri = 'https://www.famfamfam.com](http://www.famfamfam.com/fijdlfd hi ' non_uri = 'https://www.famfamfam.com](http://www.famfamfam.com/fijdlfd hi '