def test_encrypted_survey_answer(post, patch, admin_user, project, inventory, survey_spec_factory): job_template = JobTemplate.objects.create( name='test-jt', project=project, playbook='helloworld.yml', inventory=inventory, ask_variables_on_launch=False, survey_enabled=True, survey_spec=survey_spec_factory([{'variable': 'var1', 'type': 'password'}]), ) # test encrypted-on-create url = reverse('api:job_template_schedules_list', kwargs={'pk': job_template.id}) r = post(url, {'name': 'test sch', 'rrule': RRULE_EXAMPLE, 'extra_data': '{"var1": "foo"}'}, admin_user, expect=201) assert r.data['extra_data']['var1'] == "$encrypted$" schedule = Schedule.objects.get(pk=r.data['id']) assert schedule.extra_data['var1'].startswith('$encrypted$') assert decrypt_value(get_encryption_key('value', pk=None), schedule.extra_data['var1']) == 'foo' # test a no-op change r = patch(schedule.get_absolute_url(), data={'extra_data': {'var1': '$encrypted$'}}, user=admin_user, expect=200) assert r.data['extra_data']['var1'] == '$encrypted$' schedule.refresh_from_db() assert decrypt_value(get_encryption_key('value', pk=None), schedule.extra_data['var1']) == 'foo' # change to a different value r = patch(schedule.get_absolute_url(), data={'extra_data': {'var1': 'bar'}}, user=admin_user, expect=200) assert r.data['extra_data']['var1'] == '$encrypted$' schedule.refresh_from_db() assert decrypt_value(get_encryption_key('value', pk=None), schedule.extra_data['var1']) == 'bar'
def _accept_or_ignore_variables(self, data, errors=None, _exclude_errors=(), extra_passwords=None): survey_is_enabled = (self.survey_enabled and self.survey_spec) extra_vars = data.copy() if errors is None: errors = {} rejected = {} accepted = {} if survey_is_enabled: # Check for data violation of survey rules survey_errors = [] for survey_element in self.survey_spec.get("spec", []): key = survey_element.get('variable', None) value = data.get(key, None) validate_required = 'required' not in _exclude_errors if extra_passwords and key in extra_passwords and is_encrypted( value): element_errors = self._survey_element_validation( survey_element, { key: decrypt_value(get_encryption_key('value', pk=None), value) }, validate_required=validate_required) else: element_errors = self._survey_element_validation( survey_element, data, validate_required=validate_required) if element_errors: survey_errors += element_errors if key is not None and key in extra_vars: rejected[key] = extra_vars.pop(key) elif key in extra_vars: accepted[key] = extra_vars.pop(key) if survey_errors: errors['variables_needed_to_start'] = survey_errors if self.ask_variables_on_launch: # We can accept all variables accepted.update(extra_vars) extra_vars = {} if extra_vars: # Leftover extra_vars, keys provided that are not allowed rejected.update(extra_vars) # ignored variables does not block manual launch if 'prompts' not in _exclude_errors: errors['extra_vars'] = [ _('Variables {list_of_keys} are not allowed on launch. Check the Prompt on Launch setting ' + 'on the Job Template to include Extra Variables.'). format(list_of_keys=', '.join(extra_vars.keys())) ] return (accepted, rejected, errors)
def _update_unified_job_kwargs(self, create_kwargs, kwargs): ''' Combine extra_vars with variable precedence order: JT extra_vars -> JT survey defaults -> runtime extra_vars :param create_kwargs: key-worded arguments to be updated and later used for creating unified job. :type create_kwargs: dict :param kwargs: request parameters used to override unified job template fields with runtime values. :type kwargs: dict :return: modified create_kwargs. :rtype: dict ''' # Job Template extra_vars extra_vars = self.extra_vars_dict survey_defaults = {} # transform to dict if 'extra_vars' in kwargs: runtime_extra_vars = kwargs['extra_vars'] runtime_extra_vars = parse_yaml_or_json(runtime_extra_vars) else: runtime_extra_vars = {} # Overwrite job template extra vars with survey default vars if self.survey_enabled and 'spec' in self.survey_spec: for survey_element in self.survey_spec.get("spec", []): default = survey_element.get('default') variable_key = survey_element.get('variable') if survey_element.get('type') == 'password': if variable_key in runtime_extra_vars: kw_value = runtime_extra_vars[variable_key] if kw_value == '$encrypted$': runtime_extra_vars.pop(variable_key) if default is not None: decrypted_default = default if (survey_element['type'] == "password" and isinstance(decrypted_default, str) and decrypted_default.startswith('$encrypted$')): decrypted_default = decrypt_value( get_encryption_key('value', pk=None), decrypted_default) errors = self._survey_element_validation( survey_element, {variable_key: decrypted_default}) if not errors: survey_defaults[variable_key] = default extra_vars.update(survey_defaults) # Overwrite job template extra vars with explicit job extra vars # and add on job extra vars extra_vars.update(runtime_extra_vars) create_kwargs['extra_vars'] = json.dumps(extra_vars) return create_kwargs
def decrypted_extra_vars(self): """ Decrypts fields marked as passwords in survey. """ if self.survey_passwords: extra_vars = json.loads(self.extra_vars) for key in self.survey_passwords: value = extra_vars.get(key) if value and isinstance(value, str) and value.startswith('$encrypted$'): extra_vars[key] = decrypt_value(get_encryption_key('value', pk=None), value) return json.dumps(extra_vars) else: return self.extra_vars
def _survey_passwords(self): for _type in (JobTemplate, WorkflowJobTemplate): for jt in _type.objects.exclude(survey_spec={}): changed = False if jt.survey_spec.get('spec', []): for field in jt.survey_spec['spec']: if field.get('type') == 'password' and field.get( 'default', ''): raw = decrypt_value( get_encryption_key('value', None, secret_key=self.old_key), field['default']) field['default'] = encrypt_value( raw, pk=None, secret_key=self.new_key) changed = True if changed: jt.save(update_fields=["survey_spec"]) for _type in (Job, WorkflowJob): for job in _type.objects.exclude(survey_passwords={}).iterator(): changed = False for key in job.survey_passwords: if key in job.extra_vars: extra_vars = json.loads(job.extra_vars) if not extra_vars.get(key): continue raw = decrypt_value( get_encryption_key('value', None, secret_key=self.old_key), extra_vars[key]) extra_vars[key] = encrypt_value( raw, pk=None, secret_key=self.new_key) job.extra_vars = json.dumps(extra_vars) changed = True if changed: job.save(update_fields=["extra_vars"])
def test_oauth_application_encryption(admin, organization, post): response = post( reverse('api:o_auth2_application_list'), { 'name': 'test app', 'organization': organization.pk, 'client_type': 'confidential', 'authorization_grant_type': 'password', }, admin, expect=201 ) pk = response.data.get('id') secret = response.data.get('client_secret') with connection.cursor() as cursor: encrypted = cursor.execute( 'SELECT client_secret FROM main_oauth2application WHERE id={}'.format(pk) ).fetchone()[0] assert encrypted.startswith('$encrypted$') assert decrypt_value(get_encryption_key('value', pk=None), encrypted) == secret
def from_db_value(self, value, expression, connection, context): if value and value.startswith('$encrypted$'): return decrypt_value(get_encryption_key('value', pk=None), value) return value
def test_encrypt_empty_string_twice(self): # Encryption is idempotent val = encryption.encrypt_value('foobar') val2 = encryption.encrypt_value(val) assert encryption.decrypt_value(self._key, val2) == 'foobar'