# Copyright (c) 2015 Ansible, Inc. # All Rights Reserved. from collections import OrderedDict import functools import logging import os import re import stat import tempfile import six # Jinja2 from jinja2 import Template # Django from django.db import models from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.core.exceptions import ValidationError from django.utils.encoding import force_text # AWX from awx.api.versioning import reverse from awx.main.fields import (ImplicitRoleField, CredentialInputField, CredentialTypeInputField, CredentialTypeInjectorField) from awx.main.utils import decrypt_field from awx.main.utils.safe_yaml import safe_dump from awx.main.validators import validate_ssh_private_key from awx.main.models.base import * # noqa from awx.main.models.mixins import ResourceMixin from awx.main.models.rbac import ( ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, 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'] logger = logging.getLogger('awx.main.models.credential') HIDDEN_PASSWORD = '**********' def build_safe_env(env): ''' Build environment dictionary, hiding potentially sensitive information such as passwords or keys. ''' hidden_re = re.compile(r'API|TOKEN|KEY|SECRET|PASS', re.I) urlpass_re = re.compile(r'^.*?://[^:]+:(.*?)@.*?$') safe_env = dict(env) for k, v in safe_env.items(): if k == 'AWS_ACCESS_KEY_ID': continue elif k.startswith('ANSIBLE_') and not k.startswith('ANSIBLE_NET'): continue elif hidden_re.search(k): safe_env[k] = HIDDEN_PASSWORD elif type(v) == str and urlpass_re.match(v): safe_env[k] = urlpass_re.sub(HIDDEN_PASSWORD, v) return safe_env class V1Credential(object): # # API v1 backwards compat; as long as we continue to support the # /api/v1/credentials/ endpoint, we'll keep these definitions around. # The credential serializers are smart enough to detect the request # version and use *these* fields for constructing the serializer if the URL # starts with /api/v1/ # PASSWORD_FIELDS = ('password', 'security_token', 'ssh_key_data', 'ssh_key_unlock', 'become_password', 'vault_password', 'secret', 'authorize_password') KIND_CHOICES = [ ('ssh', 'Machine'), ('net', 'Network'), ('scm', 'Source Control'), ('aws', 'Amazon Web Services'), ('vmware', 'VMware vCenter'), ('satellite6', 'Red Hat Satellite 6'), ('cloudforms', 'Red Hat CloudForms'), ('gce', 'Google Compute Engine'), ('azure_rm', 'Microsoft Azure Resource Manager'), ('openstack', 'OpenStack'), ('rhv', 'Red Hat Virtualization'), ('insights', 'Insights'), ('tower', 'Ansible Tower'), ] FIELDS = { 'kind': models.CharField( max_length=32, choices=[ (kind[0], _(kind[1])) for kind in KIND_CHOICES ], default='ssh', ), 'cloud': models.BooleanField( default=False, editable=False, ), 'host': models.CharField( blank=True, default='', max_length=1024, verbose_name=_('Host'), help_text=_('The hostname or IP address to use.'), ), 'username': models.CharField( blank=True, default='', max_length=1024, verbose_name=_('Username'), help_text=_('Username for this credential.'), ), 'password': models.CharField( blank=True, default='', max_length=1024, verbose_name=_('Password'), help_text=_('Password for this credential (or "ASK" to prompt the ' 'user for machine credentials).'), ), 'security_token': models.CharField( blank=True, default='', max_length=1024, verbose_name=_('Security Token'), help_text=_('Security Token for this credential'), ), 'project': models.CharField( blank=True, default='', max_length=100, verbose_name=_('Project'), help_text=_('The identifier for the project.'), ), 'domain': models.CharField( blank=True, default='', max_length=100, verbose_name=_('Domain'), help_text=_('The identifier for the domain.'), ), 'ssh_key_data': models.TextField( blank=True, default='', verbose_name=_('SSH private key'), help_text=_('RSA or DSA private key to be used instead of password.'), ), 'ssh_key_unlock': models.CharField( max_length=1024, blank=True, default='', verbose_name=_('SSH key unlock'), help_text=_('Passphrase to unlock SSH private key if encrypted (or ' '"ASK" to prompt the user for machine credentials).'), ), 'become_method': models.CharField( max_length=32, blank=True, default='', choices=CHOICES_PRIVILEGE_ESCALATION_METHODS, help_text=_('Privilege escalation method.') ), 'become_username': models.CharField( max_length=1024, blank=True, default='', help_text=_('Privilege escalation username.'), ), 'become_password': models.CharField( max_length=1024, blank=True, default='', help_text=_('Password for privilege escalation method.') ), 'vault_password': models.CharField( max_length=1024, blank=True, default='', help_text=_('Vault password (or "ASK" to prompt the user).'), ), 'authorize': models.BooleanField( default=False, help_text=_('Whether to use the authorize mechanism.'), ), 'authorize_password': models.CharField( max_length=1024, blank=True, default='', help_text=_('Password used by the authorize mechanism.'), ), 'client': models.CharField( max_length=128, blank=True, default='', help_text=_('Client Id or Application Id for the credential'), ), 'secret': models.CharField( max_length=1024, blank=True, default='', help_text=_('Secret Token for this credential'), ), 'subscription': models.CharField( max_length=1024, blank=True, default='', help_text=_('Subscription identifier for this credential'), ), 'tenant': models.CharField( max_length=1024, blank=True, default='', help_text=_('Tenant identifier for this credential'), ) } class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin): ''' A credential contains information about how to talk to a remote resource Usually this is a SSH key location, and possibly an unlock password. If used with sudo, a sudo password should be set if required. ''' class Meta: app_label = 'main' ordering = ('name',) unique_together = (('organization', 'name', 'credential_type')) PASSWORD_FIELDS = ['inputs'] credential_type = models.ForeignKey( 'CredentialType', related_name='credentials', null=False, help_text=_('Specify the type of credential you want to create. Refer ' 'to the Ansible Tower documentation for details on each type.') ) organization = models.ForeignKey( 'Organization', null=True, default=None, blank=True, on_delete=models.CASCADE, related_name='credentials', ) inputs = CredentialInputField( blank=True, default={}, help_text=_('Enter inputs using either JSON or YAML syntax. Use the ' 'radio button to toggle between the two. Refer to the ' 'Ansible Tower documentation for example syntax.') ) admin_role = ImplicitRoleField( parent_role=[ 'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, 'organization.credential_admin_role', ], ) use_role = ImplicitRoleField( parent_role=[ 'admin_role', ] ) read_role = ImplicitRoleField(parent_role=[ 'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR, 'organization.auditor_role', 'use_role', 'admin_role', ]) def __getattr__(self, item): if item != 'inputs': if item in V1Credential.FIELDS: return self.inputs.get(item, V1Credential.FIELDS[item].default) elif item in self.inputs: return self.inputs[item] raise AttributeError(item) def __setattr__(self, item, value): if item in V1Credential.FIELDS and item in self.credential_type.defined_fields: if value: self.inputs[item] = value elif item in self.inputs: del self.inputs[item] return super(Credential, self).__setattr__(item, value) @property def kind(self): # TODO 3.3: remove the need for this helper property by removing its # usage throughout the codebase type_ = self.credential_type if type_.kind != 'cloud': return type_.kind for field in V1Credential.KIND_CHOICES: kind, name = field if name == type_.name: return kind @property def cloud(self): return self.credential_type.kind == 'cloud' def get_absolute_url(self, request=None): return reverse('api:credential_detail', kwargs={'pk': self.pk}, request=request) # # TODO: the SSH-related properties below are largely used for validation # and for determining passwords necessary for job/ad-hoc launch # # These are SSH-specific; should we move them elsewhere? # @property def needs_ssh_password(self): return self.credential_type.kind == 'ssh' and self.password == 'ASK' @property def has_encrypted_ssh_key_data(self): if self.pk: ssh_key_data = decrypt_field(self, 'ssh_key_data') else: ssh_key_data = self.ssh_key_data try: pem_objects = validate_ssh_private_key(ssh_key_data) for pem_object in pem_objects: if pem_object.get('key_enc', False): return True except ValidationError: pass return False @property def needs_ssh_key_unlock(self): if self.credential_type.kind == 'ssh' and self.ssh_key_unlock in ('ASK', ''): return self.has_encrypted_ssh_key_data return False @property def needs_become_password(self): return self.credential_type.kind == 'ssh' and self.become_password == 'ASK' @property def needs_vault_password(self): return self.credential_type.kind == 'vault' and self.vault_password == 'ASK' @property def passwords_needed(self): needed = [] for field in ('ssh_password', 'become_password', 'ssh_key_unlock'): if getattr(self, 'needs_%s' % field): needed.append(field) if self.needs_vault_password: if self.inputs.get('vault_id'): needed.append('vault_password.{}'.format(self.inputs.get('vault_id'))) else: needed.append('vault_password') return needed def _password_field_allows_ask(self, field): return field in self.credential_type.askable_fields def save(self, *args, **kwargs): self.PASSWORD_FIELDS = self.credential_type.secret_fields if self.pk: cred_before = Credential.objects.get(pk=self.pk) inputs_before = cred_before.inputs # Look up the currently persisted value so that we can replace # $encrypted$ with the actual DB-backed value for field in self.PASSWORD_FIELDS: if self.inputs.get(field) == '$encrypted$': self.inputs[field] = inputs_before[field] super(Credential, self).save(*args, **kwargs) def encrypt_field(self, field, ask): if not hasattr(self, field): return None encrypted = encrypt_field(self, field, ask=ask) if encrypted: self.inputs[field] = encrypted elif field in self.inputs: del self.inputs[field] def mark_field_for_save(self, update_fields, field): if field in self.credential_type.secret_fields: # If we've encrypted a v1 field, we actually want to persist # self.inputs field = 'inputs' super(Credential, self).mark_field_for_save(update_fields, field) def display_inputs(self): field_val = self.inputs.copy() for k, v in field_val.items(): if force_text(v).startswith('$encrypted$'): field_val[k] = '$encrypted$' return field_val def unique_hash(self, display=False): ''' Credential exclusivity is not defined solely by the related credential type (due to vault), so this produces a hash that can be used to evaluate exclusivity ''' if display: type_alias = self.credential_type.name else: type_alias = self.credential_type_id if self.kind == 'vault' and self.inputs.get('vault_id', None): if display: fmt_str = six.text_type('{} (id={})') else: fmt_str = six.text_type('{}_{}') return fmt_str.format(type_alias, self.inputs.get('vault_id')) return six.text_type(type_alias) @staticmethod def unique_dict(cred_qs): ret = {} for cred in cred_qs: ret[cred.unique_hash()] = cred return ret class CredentialType(CommonModelNameNotUnique): ''' A reusable schema for a credential. Used to define a named credential type with fields (e.g., an API key) and output injectors (i.e., an environment variable that uses the API key). ''' defaults = OrderedDict() class Meta: app_label = 'main' ordering = ('kind', 'name') unique_together = (('name', 'kind'),) KIND_CHOICES = ( ('ssh', _('Machine')), ('vault', _('Vault')), ('net', _('Network')), ('scm', _('Source Control')), ('cloud', _('Cloud')), ('insights', _('Insights')), ) kind = models.CharField( max_length=32, choices=KIND_CHOICES ) managed_by_tower = models.BooleanField( default=False, editable=False ) inputs = CredentialTypeInputField( blank=True, default={}, help_text=_('Enter inputs using either JSON or YAML syntax. Use the ' 'radio button to toggle between the two. Refer to the ' 'Ansible Tower documentation for example syntax.') ) injectors = CredentialTypeInjectorField( blank=True, default={}, help_text=_('Enter injectors using either JSON or YAML syntax. Use the ' 'radio button to toggle between the two. Refer to the ' 'Ansible Tower documentation for example syntax.') ) def get_absolute_url(self, request=None): # Page does not exist in API v1 if request.version == 'v1': return reverse('api:credential_type_detail', kwargs={'pk': self.pk}) return reverse('api:credential_type_detail', kwargs={'pk': self.pk}, request=request) @property def unique_by_kind(self): return self.kind != 'cloud' @property def defined_fields(self): return [field.get('id') for field in self.inputs.get('fields', [])] @property def secret_fields(self): return [ field['id'] for field in self.inputs.get('fields', []) if field.get('secret', False) is True ] @property def askable_fields(self): return [ field['id'] for field in self.inputs.get('fields', []) if field.get('ask_at_runtime', False) is True ] def default_for_field(self, field_id): for field in self.inputs.get('fields', []): if field['id'] == field_id: if 'choices' in field: return field['choices'][0] return {'string': '', 'boolean': False, 'become_method': ''}[field['type']] @classmethod def default(cls, f): func = functools.partial(f, cls) cls.defaults[f.__name__] = func return func @classmethod def setup_tower_managed_defaults(cls, persisted=True): for default in cls.defaults.values(): default_ = default() if persisted: if CredentialType.objects.filter(name=default_.name, kind=default_.kind).count(): continue logger.debug(_( "adding %s credential type" % default_.name )) default_.save() @classmethod def from_v1_kind(cls, kind, data={}): match = None kind = kind or 'ssh' kind_choices = dict(V1Credential.KIND_CHOICES) requirements = {} if kind == 'ssh': if data.get('vault_password'): requirements['kind'] = 'vault' else: requirements['kind'] = 'ssh' elif kind in ('net', 'scm', 'insights'): requirements['kind'] = kind elif kind in kind_choices: requirements.update(dict( kind='cloud', name=kind_choices[kind] )) if requirements: requirements['managed_by_tower'] = True match = cls.objects.filter(**requirements)[:1].get() return match def inject_credential(self, credential, env, safe_env, args, safe_args, private_data_dir): """ Inject credential data into the environment variables and arguments passed to `ansible-playbook` :param credential: a :class:`awx.main.models.Credential` instance :param env: a dictionary of environment variables used in the `ansible-playbook` call. This method adds additional environment variables based on custom `env` injectors defined on this CredentialType. :param safe_env: a dictionary of environment variables stored in the database for the job run (`UnifiedJob.job_env`); secret values should be stripped :param args: a list of arguments passed to `ansible-playbook` in the style of `subprocess.call(args)`. This method appends additional arguments based on custom `extra_vars` injectors defined on this CredentialType. :param safe_args: a list of arguments stored in the database for the job run (`UnifiedJob.job_args`); secret values should be stripped :param private_data_dir: a temporary directory to store files generated by `file` injectors (like config files or key files) """ if not self.injectors: if self.managed_by_tower and credential.kind in dir(builtin_injectors): injected_env = {} getattr(builtin_injectors, credential.kind)(credential, injected_env, private_data_dir) env.update(injected_env) safe_env.update(build_safe_env(injected_env)) return class TowerNamespace: pass tower_namespace = TowerNamespace() # maintain a normal namespace for building the ansible-playbook arguments (env and args) namespace = {'tower': tower_namespace} # maintain a sanitized namespace for building the DB-stored arguments (safe_env and safe_args) safe_namespace = {'tower': tower_namespace} # build a normal namespace with secret values decrypted (for # ansible-playbook) and a safe namespace with secret values hidden (for # DB storage) for field_name, value in credential.inputs.items(): if type(value) is bool: # boolean values can't be secret/encrypted safe_namespace[field_name] = namespace[field_name] = value continue if field_name in self.secret_fields: value = decrypt_field(credential, field_name) safe_namespace[field_name] = '**********' elif len(value): safe_namespace[field_name] = value if len(value): namespace[field_name] = value # default missing boolean fields to False for field in self.inputs.get('fields', []): if field['type'] == 'boolean' and field['id'] not in credential.inputs.keys(): namespace[field['id']] = safe_namespace[field['id']] = False file_tmpls = self.injectors.get('file', {}) # If any file templates are provided, render the files and update the # special `tower` template namespace so the filename can be # referenced in other injectors for file_label, file_tmpl in file_tmpls.items(): data = Template(file_tmpl).render(**namespace) _, path = tempfile.mkstemp(dir=private_data_dir) with open(path, 'w') as f: f.write(data.encode('utf-8')) os.chmod(path, stat.S_IRUSR | stat.S_IWUSR) # determine if filename indicates single file or many if file_label.find('.') == -1: tower_namespace.filename = path else: if not hasattr(tower_namespace, 'filename'): tower_namespace.filename = TowerNamespace() file_label = file_label.split('.')[1] setattr(tower_namespace.filename, file_label, path) injector_field = self._meta.get_field('injectors') for env_var, tmpl in self.injectors.get('env', {}).items(): try: injector_field.validate_env_var_allowed(env_var) except ValidationError as e: logger.error(six.text_type( 'Ignoring prohibited env var {}, reason: {}' ).format(env_var, e)) continue env[env_var] = Template(tmpl).render(**namespace) safe_env[env_var] = Template(tmpl).render(**safe_namespace) if 'INVENTORY_UPDATE_ID' not in env: # awx-manage inventory_update does not support extra_vars via -e extra_vars = {} for var_name, tmpl in self.injectors.get('extra_vars', {}).items(): extra_vars[var_name] = Template(tmpl).render(**namespace) def build_extra_vars_file(vars, private_dir): handle, path = tempfile.mkstemp(dir = private_dir) f = os.fdopen(handle, 'w') f.write(safe_dump(vars)) f.close() os.chmod(path, stat.S_IRUSR) return path path = build_extra_vars_file(extra_vars, private_data_dir) if extra_vars: args.extend(['-e', '@%s' % path]) safe_args.extend(['-e', '@%s' % path]) @CredentialType.default def ssh(cls): return cls( kind='ssh', name=ugettext_noop('Machine'), managed_by_tower=True, inputs={ 'fields': [{ 'id': 'username', 'label': ugettext_noop('Username'), 'type': 'string' }, { 'id': 'password', 'label': ugettext_noop('Password'), 'type': 'string', 'secret': True, 'ask_at_runtime': True }, { 'id': 'ssh_key_data', 'label': ugettext_noop('SSH Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True }, { 'id': 'ssh_key_unlock', 'label': ugettext_noop('Private Key Passphrase'), 'type': 'string', 'secret': True, 'ask_at_runtime': True }, { 'id': 'become_method', 'label': ugettext_noop('Privilege Escalation Method'), 'type': 'become_method', 'help_text': ugettext_noop('Specify a method for "become" operations. This is ' 'equivalent to specifying the --become-method ' 'Ansible parameter.') }, { 'id': 'become_username', 'label': ugettext_noop('Privilege Escalation Username'), 'type': 'string', }, { 'id': 'become_password', 'label': ugettext_noop('Privilege Escalation Password'), 'type': 'string', 'secret': True, 'ask_at_runtime': True }], 'dependencies': { 'ssh_key_unlock': ['ssh_key_data'], } } ) @CredentialType.default def scm(cls): return cls( kind='scm', name=ugettext_noop('Source Control'), managed_by_tower=True, inputs={ 'fields': [{ 'id': 'username', 'label': ugettext_noop('Username'), 'type': 'string' }, { 'id': 'password', 'label': ugettext_noop('Password'), 'type': 'string', 'secret': True }, { 'id': 'ssh_key_data', 'label': ugettext_noop('SCM Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True }, { 'id': 'ssh_key_unlock', 'label': ugettext_noop('Private Key Passphrase'), 'type': 'string', 'secret': True }], 'dependencies': { 'ssh_key_unlock': ['ssh_key_data'], } } ) @CredentialType.default def vault(cls): return cls( kind='vault', name=ugettext_noop('Vault'), managed_by_tower=True, inputs={ 'fields': [{ 'id': 'vault_password', 'label': ugettext_noop('Vault Password'), 'type': 'string', 'secret': True, 'ask_at_runtime': True }, { 'id': 'vault_id', 'label': ugettext_noop('Vault Identifier'), 'type': 'string', 'format': 'vault_id', 'help_text': ugettext_noop('Specify an (optional) Vault ID. This is ' 'equivalent to specifying the --vault-id ' 'Ansible parameter for providing multiple Vault ' 'passwords. Note: this feature only works in ' 'Ansible 2.4+.') }], 'required': ['vault_password'], } ) @CredentialType.default def net(cls): return cls( kind='net', name=ugettext_noop('Network'), managed_by_tower=True, inputs={ 'fields': [{ 'id': 'username', 'label': ugettext_noop('Username'), 'type': 'string' }, { 'id': 'password', 'label': ugettext_noop('Password'), 'type': 'string', 'secret': True, }, { 'id': 'ssh_key_data', 'label': ugettext_noop('SSH Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True }, { 'id': 'ssh_key_unlock', 'label': ugettext_noop('Private Key Passphrase'), 'type': 'string', 'secret': True, }, { 'id': 'authorize', 'label': ugettext_noop('Authorize'), 'type': 'boolean', }, { 'id': 'authorize_password', 'label': ugettext_noop('Authorize Password'), 'type': 'string', 'secret': True, }], 'dependencies': { 'ssh_key_unlock': ['ssh_key_data'], 'authorize_password': ['authorize'], }, 'required': ['username'], } ) @CredentialType.default def aws(cls): return cls( kind='cloud', name=ugettext_noop('Amazon Web Services'), managed_by_tower=True, inputs={ 'fields': [{ 'id': 'username', 'label': ugettext_noop('Access Key'), 'type': 'string' }, { 'id': 'password', 'label': ugettext_noop('Secret Key'), 'type': 'string', 'secret': True, }, { 'id': 'security_token', 'label': ugettext_noop('STS Token'), 'type': 'string', 'secret': True, 'help_text': ugettext_noop('Security Token Service (STS) is a web service ' 'that enables you to request temporary, ' 'limited-privilege credentials for AWS Identity ' 'and Access Management (IAM) users.'), }], 'required': ['username', 'password'] } ) @CredentialType.default def openstack(cls): return cls( kind='cloud', name=ugettext_noop('OpenStack'), managed_by_tower=True, inputs={ 'fields': [{ 'id': 'username', 'label': ugettext_noop('Username'), 'type': 'string' }, { 'id': 'password', 'label': ugettext_noop('Password (API Key)'), 'type': 'string', 'secret': True, }, { 'id': 'host', 'label': ugettext_noop('Host (Authentication URL)'), 'type': 'string', 'help_text': ugettext_noop('The host to authenticate with. For example, ' 'https://openstack.business.com/v2.0/') }, { 'id': 'project', 'label': ugettext_noop('Project (Tenant Name)'), 'type': 'string', }, { 'id': 'domain', 'label': ugettext_noop('Domain Name'), 'type': 'string', 'help_text': ugettext_noop('OpenStack domains define administrative boundaries. ' 'It is only needed for Keystone v3 authentication ' 'URLs. Refer to Ansible Tower documentation for ' 'common scenarios.') }], 'required': ['username', 'password', 'host', 'project'] } ) @CredentialType.default def vmware(cls): return cls( kind='cloud', name=ugettext_noop('VMware vCenter'), managed_by_tower=True, inputs={ 'fields': [{ 'id': 'host', 'label': ugettext_noop('VCenter Host'), 'type': 'string', 'help_text': ugettext_noop('Enter the hostname or IP address that corresponds ' 'to your VMware vCenter.') }, { 'id': 'username', 'label': ugettext_noop('Username'), 'type': 'string' }, { 'id': 'password', 'label': ugettext_noop('Password'), 'type': 'string', 'secret': True, }], 'required': ['host', 'username', 'password'] } ) @CredentialType.default def satellite6(cls): return cls( kind='cloud', name=ugettext_noop('Red Hat Satellite 6'), managed_by_tower=True, inputs={ 'fields': [{ 'id': 'host', 'label': ugettext_noop('Satellite 6 URL'), 'type': 'string', 'help_text': ugettext_noop('Enter the URL that corresponds to your Red Hat ' 'Satellite 6 server. For example, https://satellite.example.org') }, { 'id': 'username', 'label': ugettext_noop('Username'), 'type': 'string' }, { 'id': 'password', 'label': ugettext_noop('Password'), 'type': 'string', 'secret': True, }], 'required': ['host', 'username', 'password'], } ) @CredentialType.default def cloudforms(cls): return cls( kind='cloud', name=ugettext_noop('Red Hat CloudForms'), managed_by_tower=True, inputs={ 'fields': [{ 'id': 'host', 'label': ugettext_noop('CloudForms URL'), 'type': 'string', 'help_text': ugettext_noop('Enter the URL for the virtual machine that ' 'corresponds to your CloudForms instance. ' 'For example, https://cloudforms.example.org') }, { 'id': 'username', 'label': ugettext_noop('Username'), 'type': 'string' }, { 'id': 'password', 'label': ugettext_noop('Password'), 'type': 'string', 'secret': True, }], 'required': ['host', 'username', 'password'], } ) @CredentialType.default def gce(cls): return cls( kind='cloud', name=ugettext_noop('Google Compute Engine'), managed_by_tower=True, inputs={ 'fields': [{ 'id': 'username', 'label': ugettext_noop('Service Account Email Address'), 'type': 'string', 'help_text': ugettext_noop('The email address assigned to the Google Compute ' 'Engine service account.') }, { 'id': 'project', 'label': 'Project', 'type': 'string', 'help_text': ugettext_noop('The Project ID is the GCE assigned identification. ' 'It is often constructed as three words or two words ' 'followed by a three-digit number. Examples: project-id-000 ' 'and another-project-id') }, { 'id': 'ssh_key_data', 'label': ugettext_noop('RSA Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True, 'help_text': ugettext_noop('Paste the contents of the PEM file associated ' 'with the service account email.') }], 'required': ['username', 'ssh_key_data'], } ) @CredentialType.default def azure_rm(cls): return cls( kind='cloud', name=ugettext_noop('Microsoft Azure Resource Manager'), managed_by_tower=True, inputs={ 'fields': [{ 'id': 'subscription', 'label': ugettext_noop('Subscription ID'), 'type': 'string', 'help_text': ugettext_noop('Subscription ID is an Azure construct, which is ' 'mapped to a username.') }, { 'id': 'username', 'label': ugettext_noop('Username'), 'type': 'string' }, { 'id': 'password', 'label': ugettext_noop('Password'), 'type': 'string', 'secret': True, }, { 'id': 'client', 'label': ugettext_noop('Client ID'), 'type': 'string' }, { 'id': 'secret', 'label': ugettext_noop('Client Secret'), 'type': 'string', 'secret': True, }, { 'id': 'tenant', 'label': ugettext_noop('Tenant ID'), 'type': 'string' }, { 'id': 'cloud_environment', 'label': ugettext_noop('Azure Cloud Environment'), 'type': 'string', 'help_text': ugettext_noop('Environment variable AZURE_CLOUD_ENVIRONMENT when' ' using Azure GovCloud or Azure stack.') }], 'required': ['subscription'], } ) @CredentialType.default def insights(cls): return cls( kind='insights', name=ugettext_noop('Insights'), managed_by_tower=True, inputs={ 'fields': [{ 'id': 'username', 'label': ugettext_noop('Username'), 'type': 'string' }, { 'id': 'password', 'label': ugettext_noop('Password'), 'type': 'string', 'secret': True }], 'required': ['username', 'password'], }, injectors={ 'extra_vars': { "scm_username": "{{username}}", "scm_password": "{{password}}", }, }, ) @CredentialType.default def rhv(cls): return cls( kind='cloud', name=ugettext_noop('Red Hat Virtualization'), managed_by_tower=True, inputs={ 'fields': [{ 'id': 'host', 'label': ugettext_noop('Host (Authentication URL)'), 'type': 'string', 'help_text': ugettext_noop('The host to authenticate with.') }, { 'id': 'username', 'label': ugettext_noop('Username'), 'type': 'string' }, { 'id': 'password', 'label': ugettext_noop('Password'), 'type': 'string', 'secret': True, }, { 'id': 'ca_file', 'label': ugettext_noop('CA File'), 'type': 'string', 'help_text': ugettext_noop('Absolute file path to the CA file to use (optional)') }], 'required': ['host', 'username', 'password'], }, injectors={ # The duplication here is intentional; the ovirt4 inventory plugin # writes a .ini file for authentication, while the ansible modules for # ovirt4 use a separate authentication process that support # environment variables; by injecting both, we support both 'file': { 'template': '\n'.join([ '[ovirt]', 'ovirt_url={{host}}', 'ovirt_username={{username}}', 'ovirt_password={{password}}', '{% if ca_file %}ovirt_ca_file={{ca_file}}{% endif %}']) }, 'env': { 'OVIRT_INI_PATH': '{{tower.filename}}', 'OVIRT_URL': '{{host}}', 'OVIRT_USERNAME': '{{username}}', 'OVIRT_PASSWORD': '{{password}}' } }, ) @CredentialType.default def tower(cls): return cls( kind='cloud', name=ugettext_noop('Ansible Tower'), managed_by_tower=True, inputs={ 'fields': [{ 'id': 'host', 'label': ugettext_noop('Ansible Tower Hostname'), 'type': 'string', 'help_text': ugettext_noop('The Ansible Tower base URL to authenticate with.') }, { 'id': 'username', 'label': ugettext_noop('Username'), 'type': 'string' }, { 'id': 'password', 'label': ugettext_noop('Password'), 'type': 'string', 'secret': True, }, { 'id': 'verify_ssl', 'label': ugettext_noop('Verify SSL'), 'type': 'boolean', 'secret': False }], 'required': ['host', 'username', 'password'], }, injectors={ 'env': { 'TOWER_HOST': '{{host}}', 'TOWER_USERNAME': '{{username}}', 'TOWER_PASSWORD': '{{password}}', 'TOWER_VERIFY_SSL': '{{verify_ssl}}' } }, )