1
0
mirror of https://github.com/ansible/awx.git synced 2024-11-01 16:51:11 +03:00

Merge pull request #3052 from jakemcdermott/inputs_enc

explicitly handle .inputs in decrypt_field, encrypt_field

Reviewed-by: Ryan Petrello
             https://github.com/ryanpetrello
This commit is contained in:
softwarefactory-project-zuul[bot] 2019-01-24 17:04:30 +00:00 committed by GitHub
commit be4b3c75b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 54 additions and 13 deletions

View File

@ -32,9 +32,6 @@ from rest_framework.permissions import AllowAny
from rest_framework.renderers import StaticHTMLRenderer, JSONRenderer from rest_framework.renderers import StaticHTMLRenderer, JSONRenderer
from rest_framework.negotiation import DefaultContentNegotiation from rest_framework.negotiation import DefaultContentNegotiation
# cryptography
from cryptography.fernet import InvalidToken
# AWX # AWX
from awx.api.filters import FieldLookupBackend from awx.api.filters import FieldLookupBackend
from awx.main.models import * # noqa from awx.main.models import * # noqa
@ -858,11 +855,14 @@ class CopyAPIView(GenericAPIView):
and isinstance(field_val[sub_field], six.string_types): and isinstance(field_val[sub_field], six.string_types):
try: try:
field_val[sub_field] = decrypt_field(obj, field_name, sub_field) field_val[sub_field] = decrypt_field(obj, field_name, sub_field)
except InvalidToken: except AttributeError:
# Catching the corner case with v1 credential fields # Catching the corner case with v1 credential fields
field_val[sub_field] = decrypt_field(obj, sub_field) field_val[sub_field] = decrypt_field(obj, sub_field)
elif isinstance(field_val, six.string_types): elif isinstance(field_val, six.string_types):
field_val = decrypt_field(obj, field_name) try:
field_val = decrypt_field(obj, field_name)
except AttributeError:
return field_val
return field_val return field_val
def _build_create_dict(self, obj): def _build_create_dict(self, obj):

View File

@ -325,10 +325,11 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
@property @property
def has_encrypted_ssh_key_data(self): def has_encrypted_ssh_key_data(self):
if self.pk: try:
ssh_key_data = decrypt_field(self, 'ssh_key_data') ssh_key_data = decrypt_field(self, 'ssh_key_data')
else: except AttributeError:
ssh_key_data = self.ssh_key_data return False
try: try:
pem_objects = validate_ssh_private_key(ssh_key_data) pem_objects = validate_ssh_private_key(ssh_key_data)
for pem_object in pem_objects: for pem_object in pem_objects:
@ -383,9 +384,8 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
super(Credential, self).save(*args, **kwargs) super(Credential, self).save(*args, **kwargs)
def encrypt_field(self, field, ask): def encrypt_field(self, field, ask):
if not hasattr(self, field): if field not in self.inputs:
return None return None
encrypted = encrypt_field(self, field, ask=ask) encrypted = encrypt_field(self, field, ask=ask)
if encrypted: if encrypted:
self.inputs[field] = encrypted self.inputs[field] = encrypted
@ -444,7 +444,12 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
:param default(optional[str]): A default return value to use. :param default(optional[str]): A default return value to use.
""" """
if field_name in self.credential_type.secret_fields: if field_name in self.credential_type.secret_fields:
return decrypt_field(self, field_name) try:
return decrypt_field(self, field_name)
except AttributeError:
if 'default' in kwargs:
return kwargs['default']
raise AttributeError
if field_name in self.inputs: if field_name in self.inputs:
return self.inputs[field_name] return self.inputs[field_name]
if 'default' in kwargs: if 'default' in kwargs:

View File

@ -345,6 +345,10 @@ def test_credential_get_input(organization_factory):
'id': 'vault_id', 'id': 'vault_id',
'type': 'string', 'type': 'string',
'secret': False 'secret': False
}, {
'id': 'secret',
'type': 'string',
'secret': True,
}] }]
} }
) )
@ -372,6 +376,12 @@ def test_credential_get_input(organization_factory):
cred.get_input('field_not_on_credential_type') cred.get_input('field_not_on_credential_type')
# verify that the provided default is used for undefined inputs # verify that the provided default is used for undefined inputs
assert cred.get_input('field_not_on_credential_type', default='bar') == 'bar' assert cred.get_input('field_not_on_credential_type', default='bar') == 'bar'
# verify expected exception is raised when attempting to access an unset secret
# input without providing a default
with pytest.raises(AttributeError):
cred.get_input('secret')
# verify that the provided default is used for undefined inputs
assert cred.get_input('secret', default='fiz') == 'fiz'
# verify return values for encrypted secret fields are decrypted # verify return values for encrypted secret fields are decrypted
assert cred.inputs['vault_password'].startswith('$encrypted$') assert cred.inputs['vault_password'].startswith('$encrypted$')
assert cred.get_input('vault_password') == 'testing321' assert cred.get_input('vault_password') == 'testing321'

View File

@ -1656,6 +1656,7 @@ class TestJobCredentials(TestJobExecution):
'password': 'secret' 'password': 'secret'
} }
) )
azure_rm_credential.inputs['secret'] = ''
azure_rm_credential.inputs['secret'] = encrypt_field(azure_rm_credential, 'secret') azure_rm_credential.inputs['secret'] = encrypt_field(azure_rm_credential, 'secret')
self.instance.credentials.add(azure_rm_credential) self.instance.credentials.add(azure_rm_credential)
@ -2089,6 +2090,7 @@ class TestInventoryUpdateCredentials(TestJobExecution):
'host': 'https://keystone.example.org' 'host': 'https://keystone.example.org'
} }
) )
cred.inputs['ssh_key_data'] = ''
cred.inputs['ssh_key_data'] = encrypt_field( cred.inputs['ssh_key_data'] = encrypt_field(
cred, 'ssh_key_data' cred, 'ssh_key_data'
) )

View File

@ -2,6 +2,8 @@
# Copyright (c) 2017 Ansible, Inc. # Copyright (c) 2017 Ansible, Inc.
# All Rights Reserved. # All Rights Reserved.
import pytest
from awx.conf.models import Setting from awx.conf.models import Setting
from awx.main.utils import encryption from awx.main.utils import encryption
@ -45,6 +47,16 @@ def test_encrypt_field_with_empty_value():
assert encrypted is None assert encrypted is None
def test_encrypt_field_with_undefined_attr_raises_expected_exception():
with pytest.raises(AttributeError):
encryption.encrypt_field({}, 'undefined_attr')
def test_decrypt_field_with_undefined_attr_raises_expected_exception():
with pytest.raises(AttributeError):
encryption.decrypt_field({}, 'undefined_attr')
class TestSurveyReversibilityValue: class TestSurveyReversibilityValue:
''' '''
Tests to enforce the contract with survey password question encrypted values Tests to enforce the contract with survey password question encrypted values

View File

@ -63,7 +63,13 @@ def encrypt_field(instance, field_name, ask=False, subfield=None):
''' '''
Return content of the given instance and field name encrypted. Return content of the given instance and field name encrypted.
''' '''
value = getattr(instance, field_name) try:
value = instance.inputs[field_name]
except (TypeError, AttributeError):
value = getattr(instance, field_name)
except KeyError:
raise AttributeError(field_name)
if isinstance(value, dict) and subfield is not None: if isinstance(value, dict) and subfield is not None:
value = value[subfield] value = value[subfield]
if value is None: if value is None:
@ -98,7 +104,13 @@ def decrypt_field(instance, field_name, subfield=None):
''' '''
Return content of the given instance and field name decrypted. Return content of the given instance and field name decrypted.
''' '''
value = getattr(instance, field_name) try:
value = instance.inputs[field_name]
except (TypeError, AttributeError):
value = getattr(instance, field_name)
except KeyError:
raise AttributeError(field_name)
if isinstance(value, dict) and subfield is not None: if isinstance(value, dict) and subfield is not None:
value = value[subfield] value = value[subfield]
value = smart_str(value) value = smart_str(value)