redact survey password type values w/ tests

This commit is contained in:
Chris Meyers
2015-02-18 07:32:57 -05:00
parent 50e174ea43
commit 1e871f8d7c
13 changed files with 553 additions and 239 deletions

View File

@@ -5,6 +5,7 @@
import hmac
import json
import logging
import re
# Django
from django.conf import settings
@@ -23,6 +24,7 @@ from awx.main.models.base import * # noqa
from awx.main.models.unified_jobs import * # noqa
from awx.main.utils import decrypt_field, ignore_inventory_computed_fields
from awx.main.utils import emit_websocket_notification
from awx.main.redact import PlainTextCleaner
logger = logging.getLogger('awx.main.models.jobs')
@@ -220,7 +222,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions):
if survey_element['variable'] not in data and \
survey_element['required']:
errors.append("'%s' value missing" % survey_element['variable'])
elif survey_element['type'] in ["textarea", "text"]:
elif survey_element['type'] in ["textarea", "text", "password"]:
if survey_element['variable'] in data:
if 'min' in survey_element and survey_element['min'] not in ["", None] and len(data[survey_element['variable']]) < survey_element['min']:
errors.append("'%s' value %s is too small (must be at least %s)" %
@@ -452,6 +454,31 @@ class Job(UnifiedJob, JobOptions):
evars.update(extra_vars)
self.update_fields(extra_vars=json.dumps(evars))
def _survey_search_and_replace(self, content):
# Use job template survey spec to identify password fields.
# Then lookup password fields in extra_vars and save the values
jt = self.job_template
if jt and jt.survey_enabled and 'spec' in jt.survey_spec:
vars = []
# Get variables that are type password
for survey_element in jt.survey_spec['spec']:
if survey_element['type'] == 'password':
vars.append(survey_element['variable'])
# Use password vars to find in extra_vars
for key in vars:
if key in self.extra_vars_dict:
content = PlainTextCleaner.remove_sensitive(content, self.extra_vars_dict[key])
return content
def _result_stdout_raw_limited(self, *args, **kwargs):
buff, start, end, abs_end = super(Job, self)._result_stdout_raw_limited(*args, **kwargs)
return self._survey_search_and_replace(buff), start, end, abs_end
def _result_stdout_raw(self, *args, **kwargs):
content = super(Job, self)._result_stdout_raw(*args, **kwargs)
return self._survey_search_and_replace(content)
def copy(self):
presets = {}
for kw in self.job_template._get_unified_job_field_names():

View File

@@ -625,16 +625,27 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
else:
return StringIO("stdout capture is missing")
def _escape_ascii(self, content):
ansi_escape = re.compile(r'\x1b[^m]*m')
return ansi_escape.sub('', content)
def _result_stdout_raw(self, redact_sensitive=True, escape_ascii=False):
content = self.result_stdout_raw_handle().read()
if redact_sensitive:
content = UriCleaner.remove_sensitive(content)
if escape_ascii:
content = self._escape_ascii(content)
return content
@property
def result_stdout_raw(self):
return self.result_stdout_raw_handle().read()
return self._result_stdout_raw()
@property
def result_stdout(self):
ansi_escape = re.compile(r'\x1b[^m]*m')
return ansi_escape.sub('', UriCleaner.remove_sensitive(self.result_stdout_raw))
return self._result_stdout_raw(escape_ascii=True)
def result_stdout_raw_limited(self, start_line=0, end_line=None):
def _result_stdout_raw_limited(self, start_line=0, end_line=None, redact_sensitive=True, escape_ascii=False):
return_buffer = u""
if end_line is not None:
end_line = int(end_line)
@@ -651,12 +662,19 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
end_actual = min(int(end_line), len(stdout_lines))
else:
end_actual = len(stdout_lines)
if redact_sensitive:
return_buffer = UriCleaner.remove_sensitive(return_buffer)
if escape_ascii:
return_buffer = self._escape_ascii(return_buffer)
return return_buffer, start_actual, end_actual, absolute_end
def result_stdout_raw_limited(self, start_line=0, end_line=None):
return self._result_stdout_raw_limited(start_line, end_line)
def result_stdout_limited(self, start_line=0, end_line=None):
ansi_escape = re.compile(r'\x1b[^m]*m')
content, start, end, absolute_end = UriCleaner.remove_sensitive(self.result_stdout_raw_limited(start_line, end_line))
return ansi_escape.sub('', content), start, end, absolute_end
return self._result_stdout_raw_limited(start_line, end_line, escape_ascii=True)
@property
def celery_task(self):
@@ -729,9 +747,6 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
def signal_start(self, **kwargs):
"""Notify the task runner system to begin work on this task."""
# Sanity check: If we are running unit tests, then run synchronously.
if getattr(settings, 'CELERY_UNIT_TEST', False):
return self.start(None, **kwargs)
# Sanity check: Are we able to start the job? If not, do not attempt
# to do so.
@@ -747,6 +762,10 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
if 'extra_vars' in kwargs:
self.handle_extra_data(kwargs['extra_vars'])
# Sanity check: If we are running unit tests, then run synchronously.
if getattr(settings, 'CELERY_UNIT_TEST', False):
return self.start(None, **kwargs)
# Save the pending status, and inform the SocketIO listener.
self.update_fields(start_args=json.dumps(kwargs), status='pending')
self.socketio_emit_status("pending")