diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 0c887da968..ca9464f93b 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -5,6 +5,7 @@ import copy import json import logging +import operator import re import six import urllib @@ -38,7 +39,13 @@ from rest_framework.utils.serializer_helpers import ReturnList from polymorphic.models import PolymorphicModel # AWX -from awx.main.constants import SCHEDULEABLE_PROVIDERS, ANSI_SGR_PATTERN, ACTIVE_STATES, TOKEN_CENSOR +from awx.main.constants import ( + SCHEDULEABLE_PROVIDERS, + ANSI_SGR_PATTERN, + ACTIVE_STATES, + TOKEN_CENSOR, + CHOICES_PRIVILEGE_ESCALATION_METHODS, +) from awx.main.models import * # noqa from awx.main.models.base import NEW_JOB_TYPE_CHOICES from awx.main.access import get_user_capabilities @@ -2494,6 +2501,9 @@ class CredentialTypeSerializer(BaseSerializer): field['label'] = _(field['label']) if 'help_text' in field: field['help_text'] = _(field['help_text']) + if field['type'] == 'become_method': + field.pop('type') + field['choices'] = map(operator.itemgetter(0), CHOICES_PRIVILEGE_ESCALATION_METHODS) return value def filter_field_metadata(self, fields, method): diff --git a/awx/main/constants.py b/awx/main/constants.py index edd00569ea..e7c8a943fc 100644 --- a/awx/main/constants.py +++ b/awx/main/constants.py @@ -15,7 +15,10 @@ CLOUD_PROVIDERS = ('azure_rm', 'ec2', 'gce', 'vmware', 'openstack', 'rhv', 'sate SCHEDULEABLE_PROVIDERS = CLOUD_PROVIDERS + ('custom', 'scm',) PRIVILEGE_ESCALATION_METHODS = [ ('sudo', _('Sudo')), ('su', _('Su')), ('pbrun', _('Pbrun')), ('pfexec', _('Pfexec')), - ('dzdo', _('DZDO')), ('pmrun', _('Pmrun')), ('runas', _('Runas'))] + ('dzdo', _('DZDO')), ('pmrun', _('Pmrun')), ('runas', _('Runas')), + ('enable', _('Enable')), ('doas', _('Doas')), +] +CHOICES_PRIVILEGE_ESCALATION_METHODS = [('', _('None'))] + PRIVILEGE_ESCALATION_METHODS ANSI_SGR_PATTERN = re.compile(r'\x1b\[[0-9;]*m') CAN_CANCEL = ('new', 'pending', 'waiting', 'running') ACTIVE_STATES = CAN_CANCEL diff --git a/awx/main/fields.py b/awx/main/fields.py index 1a41d711a3..44389f3879 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -4,6 +4,7 @@ # Python import copy import json +import operator import re import six import urllib @@ -45,6 +46,7 @@ from awx.main.utils.filters import SmartFilter from awx.main.utils.encryption import encrypt_value, decrypt_value, get_encryption_key from awx.main.validators import validate_ssh_private_key from awx.main.models.rbac import batch_role_ancestor_rebuilding, Role +from awx.main.constants import CHOICES_PRIVILEGE_ESCALATION_METHODS from awx.main import utils @@ -506,6 +508,9 @@ class CredentialInputField(JSONSchemaField): properties = {} for field in model_instance.credential_type.inputs.get('fields', []): field = field.copy() + if field['type'] == 'become_method': + field.pop('type') + field['choices'] = map(operator.itemgetter(0), CHOICES_PRIVILEGE_ESCALATION_METHODS) properties[field['id']] = field if field.get('choices', []): field['enum'] = field['choices'][:] @@ -649,7 +654,7 @@ class CredentialTypeInputField(JSONSchemaField): 'items': { 'type': 'object', 'properties': { - 'type': {'enum': ['string', 'boolean']}, + 'type': {'enum': ['string', 'boolean', 'become_method']}, 'format': {'enum': ['ssh_private_key']}, 'choices': { 'type': 'array', @@ -710,6 +715,17 @@ class CredentialTypeInputField(JSONSchemaField): # If no type is specified, default to string field['type'] = 'string' + if field['type'] == 'become_method': + if not model_instance.managed_by_tower: + raise django_exceptions.ValidationError( + _('become_method is a reserved type name'), + code='invalid', + params={'value': value}, + ) + else: + field.pop('type') + field['choices'] = CHOICES_PRIVILEGE_ESCALATION_METHODS + for key in ('choices', 'multiline', 'format', 'secret',): if key in field and field['type'] != 'string': raise django_exceptions.ValidationError( diff --git a/awx/main/migrations/0036_v330_credtype_remove_become_methods.py b/awx/main/migrations/0036_v330_credtype_remove_become_methods.py new file mode 100644 index 0000000000..3a43bd6a8b --- /dev/null +++ b/awx/main/migrations/0036_v330_credtype_remove_become_methods.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +# AWX +from awx.main.migrations import _credentialtypes as credentialtypes + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0035_v330_more_oauth2_help_text'), + + ] + + operations = [ + migrations.RunPython(credentialtypes.remove_become_methods), + ] diff --git a/awx/main/migrations/_credentialtypes.py b/awx/main/migrations/_credentialtypes.py index bf4128c92e..fbf812e8c2 100644 --- a/awx/main/migrations/_credentialtypes.py +++ b/awx/main/migrations/_credentialtypes.py @@ -197,3 +197,9 @@ def add_azure_cloud_environment_field(apps, schema_editor): name='Microsoft Azure Resource Manager') azure_rm_credtype.inputs = CredentialType.defaults.get('azure_rm')().inputs azure_rm_credtype.save() + + +def remove_become_methods(apps, schema_editor): + become_credtype = CredentialType.objects.filter(kind='ssh', managed_by_tower=True).first() + become_credtype.inputs = CredentialType.defaults.get('ssh')().inputs + become_credtype.save() diff --git a/awx/main/models/credential/__init__.py b/awx/main/models/credential/__init__.py index b390043765..0d988706f8 100644 --- a/awx/main/models/credential/__init__.py +++ b/awx/main/models/credential/__init__.py @@ -4,7 +4,6 @@ from collections import OrderedDict import functools import json import logging -import operator import os import re import stat @@ -22,7 +21,6 @@ from django.utils.encoding import force_text # AWX from awx.api.versioning import reverse -from awx.main.constants import PRIVILEGE_ESCALATION_METHODS from awx.main.fields import (ImplicitRoleField, CredentialInputField, CredentialTypeInputField, CredentialTypeInjectorField) @@ -35,6 +33,7 @@ from awx.main.models.rbac import ( ROLE_SINGLETON_SYSTEM_AUDITOR, ) from awx.main.utils import encrypt_field +from awx.main.constants import CHOICES_PRIVILEGE_ESCALATION_METHODS from . import injectors as builtin_injectors __all__ = ['Credential', 'CredentialType', 'V1Credential', 'build_safe_env'] @@ -165,7 +164,7 @@ class V1Credential(object): max_length=32, blank=True, default='', - choices=[('', _('None'))] + PRIVILEGE_ESCALATION_METHODS, + choices=CHOICES_PRIVILEGE_ESCALATION_METHODS, help_text=_('Privilege escalation method.') ), 'become_username': models.CharField( @@ -516,7 +515,7 @@ class CredentialType(CommonModelNameNotUnique): if field['id'] == field_id: if 'choices' in field: return field['choices'][0] - return {'string': '', 'boolean': False}[field['type']] + return {'string': '', 'boolean': False, 'become_method': ''}[field['type']] @classmethod def default(cls, f): @@ -708,8 +707,7 @@ def ssh(cls): }, { 'id': 'become_method', 'label': 'Privilege Escalation Method', - 'choices': map(operator.itemgetter(0), - V1Credential.FIELDS['become_method'].choices), + 'type': 'become_method', 'help_text': ('Specify a method for "become" operations. This is ' 'equivalent to specifying the --become-method ' 'Ansible parameter.')