Skip to main content

What is a Credential?

A Credential in AWX contains authentication information required to connect to remote systems, cloud providers, or external services. Credentials securely store passwords, SSH keys, API tokens, and other sensitive data, making them available to jobs without exposing them to users.
Credentials are AWX’s secure vault for authentication data - they enable automation to access systems while maintaining security best practices.

Core Concepts

Credential Types

AWX supports multiple credential types (awx/main/models/credential.py:429-442):
KindDescriptionUse Cases
Machine (ssh)SSH credentialsConnecting to Linux/Unix hosts
VaultAnsible Vault passwordsDecrypting encrypted variables
Network (net)Network device credentialsNetwork automation
Source Control (scm)Git/SVN credentialsAccessing private repositories
CloudCloud provider credentialsAWS, Azure, GCP, OpenStack, etc.
Container RegistryRegistry credentialsPulling execution environment images
TokenPersonal access tokensAPI authentication
InsightsRed Hat Insights credentialsInsights integration
ExternalExternal credential pluginsCyberArk, HashiCorp Vault, etc.
KubernetesK8s authenticationOpenShift Virtualization
GalaxyAnsible Galaxy tokensInstalling collections from Galaxy
CryptographyGPG keysContent signing/verification

Credential Structure

From the Credential model (awx/main/models/credential.py:122-175):
class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
    credential_type = models.ForeignKey('CredentialType', related_name='credentials', null=False, on_delete=models.CASCADE)
    managed = models.BooleanField(default=False, editable=False)
    organization = models.ForeignKey('Organization', null=True, default=None, blank=True, on_delete=models.CASCADE)
    inputs = CredentialInputField(blank=True, default=dict)
Key fields:
  • name: Credential name (unique per organization and type)
  • credential_type: Type defining what fields are available
  • organization: Optional organization for scoping
  • inputs: Dictionary of credential-specific values
  • managed: System-managed credentials (not user-editable)

Credential Inputs

Each credential type defines its own input fields. For example, SSH credentials:
{
  "username": "deploy",
  "password": "$encrypted$",
  "ssh_key_data": "-----BEGIN OPENSSH PRIVATE KEY-----\n...",
  "ssh_key_unlock": "$encrypted$",
  "become_method": "sudo",
  "become_username": "root",
  "become_password": "$encrypted$"
}

Secret Fields

Secret fields are automatically encrypted:
# From credential.py:278-290
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]
Users see $encrypted$ instead of actual values when viewing credentials.

Retrieving Input Values

Credentials provide a secure interface for accessing values:
# From credential.py:337-368
def get_input(self, field_name, **kwargs):
    """
    Get an injectable and decrypted value for an input field.
    """
    if field_name in self.credential_type.secret_fields:
        try:
            return decrypt_field(self, field_name)
        except AttributeError:
            for field in self.credential_type.inputs.get('fields', []):
                if field['id'] == field_name and 'default' in field:
                    return field['default']
            if 'default' in kwargs:
                return kwargs['default']
            raise AttributeError(field_name)

Ask at Runtime

Certain credential fields can be marked for prompting:
# From credential.py:199-243
@property
def needs_ssh_password(self):
    return self.credential_type.kind == 'ssh' and self.inputs.get('password') == 'ASK'

@property
def needs_ssh_key_unlock(self):
    if self.credential_type.kind == 'ssh' and self.inputs.get('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.inputs.get('become_password') == 'ASK'

@property
def needs_vault_password(self):
    return self.credential_type.kind == 'vault' and self.inputs.get('vault_password') == 'ASK'
When launching a job with credentials that need passwords, users must provide them.

Credential Types

Credential types define the schema and behavior of credentials (awx/main/models/credential.py:414-576):

Input Schema

Defines what fields a credential has:
{
  "fields": [
    {
      "id": "username",
      "label": "Username",
      "type": "string"
    },
    {
      "id": "password",
      "label": "Password",
      "type": "string",
      "secret": true,
      "ask_at_runtime": true
    },
    {
      "id": "ssh_key_data",
      "label": "SSH Private Key",
      "type": "string",
      "format": "ssh_private_key",
      "secret": true,
      "multiline": true
    }
  ],
  "required": ["username"]
}

Injector Schema

Defines how credentials are injected into jobs:
{
  "env": {
    "AWS_ACCESS_KEY_ID": "{{access_key}}",
    "AWS_SECRET_ACCESS_KEY": "{{secret_key}}"
  },
  "file": {
    "template": "[default]\naws_access_key_id={{access_key}}\naws_secret_access_key={{secret_key}}"
  },
  "extra_vars": {
    "aws_region": "{{region}}"
  }
}
Injectors can provide:
  • Environment variables: Set in job execution environment
  • Files: Written to temporary files
  • Extra variables: Passed as Ansible extra vars

Managed vs Custom Types

AWX includes managed credential types that cannot be modified:
# From credential.py:445-446
managed = models.BooleanField(default=False, editable=False)
namespace = models.CharField(max_length=1024, null=True, default=None, editable=False)
Users can create custom credential types for specific needs:
POST /api/v2/credential_types/
Content-Type: application/json

{
  "name": "Custom API",
  "kind": "cloud",
  "inputs": {
    "fields": [
      {
        "id": "api_key",
        "label": "API Key",
        "type": "string",
        "secret": true
      },
      {
        "id": "api_url",
        "label": "API URL",
        "type": "string",
        "default": "https://api.example.com"
      }
    ]
  },
  "injectors": {
    "env": {
      "MY_API_KEY": "{{api_key}}",
      "MY_API_URL": "{{api_url}}"
    }
  }
}

External Credentials

AWX supports external credential plugins for fetching secrets from external vaults:

Credential Input Sources

From awx/main/models/credential.py:603-680:
class CredentialInputSource(PrimordialModel):
    target_credential = models.ForeignKey('Credential', related_name='input_sources', on_delete=models.CASCADE, null=True)
    source_credential = models.ForeignKey('Credential', related_name='target_input_sources', on_delete=models.CASCADE, null=True)
    input_field_name = models.CharField(max_length=1024)
    metadata = DynamicCredentialInputField(blank=True, default=dict)
Example: Fetch SSH password from CyberArk:
  1. Create CyberArk credential (external type)
  2. Create SSH credential with input source:
    • Target field: password
    • Source: CyberArk credential
    • Metadata: {"object_query": "Safe=MySafe;Object=ssh-password"}
At runtime, AWX fetches the password from CyberArk:
# From credential.py:648-679
def get_input_value(self, context: dict | None = None):
    """
    Retrieve the value from the external credential backend.
    """
    if context is None:
        context = {}
    backend = self.source_credential.credential_type.plugin.backend
    backend_kwargs = {}
    for field_name, value in self.source_credential.inputs.items():
        if field_name in self.source_credential.credential_type.secret_fields:
            backend_kwargs[field_name] = decrypt_field(self.source_credential, field_name)
        else:
            backend_kwargs[field_name] = value
    
    backend_kwargs.update(self.metadata)
    
    with set_environ(**settings.AWX_TASK_ENV):
        return backend(**backend_kwargs)

Multiple Credentials

Jobs can use multiple credentials simultaneously:
# From jobs.py:167-181
@property
def machine_credential(self):
    return self.credentials.filter(credential_type__kind='ssh').first()

@property
def network_credentials(self):
    return list(self.credentials.filter(credential_type__kind='net'))

@property
def cloud_credentials(self):
    return list(self.credentials.filter(credential_type__kind='cloud'))

@property
def vault_credentials(self):
    return list(self.credentials.filter(credential_type__kind='vault'))
Rules:
  • One SSH/machine credential
  • Multiple vault credentials (for different vault IDs)
  • Multiple cloud credentials
  • Multiple network credentials
Some credential types are mutually exclusive (e.g., only one SSH credential per job). AWX validates this during job launch.

Vault Credentials

Vault credentials support multiple vault IDs:
# From credential.py:312-328
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.credential_type.kind == 'vault' and self.has_input('vault_id'):
        if display:
            fmt_str = '{} (id={})'
        else:
            fmt_str = '{}_{}'
        return fmt_str.format(type_alias, self.get_input('vault_id'))
    return str(type_alias)
Multiple vault credentials with different IDs can be used together.

API Endpoints

List Credentials

GET /api/v2/credentials/

Create SSH Credential

POST /api/v2/credentials/
Content-Type: application/json

{
  "name": "Production SSH Key",
  "description": "SSH key for production servers",
  "organization": 1,
  "credential_type": 1,
  "inputs": {
    "username": "deploy",
    "ssh_key_data": "-----BEGIN OPENSSH PRIVATE KEY-----\n...",
    "become_method": "sudo",
    "become_username": "root"
  }
}

Create AWS Credential

POST /api/v2/credentials/
Content-Type: application/json

{
  "name": "AWS Production",
  "organization": 1,
  "credential_type": 3,
  "inputs": {
    "username": "AKIAIOSFODNN7EXAMPLE",
    "password": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
  }
}

Create Vault Credential

POST /api/v2/credentials/
Content-Type: application/json

{
  "name": "Production Vault",
  "organization": 1,
  "credential_type": 2,
  "inputs": {
    "vault_password": "my-vault-password",
    "vault_id": "prod"
  }
}

Test Credential (External)

POST /api/v2/credentials/{id}/test/

Permissions

Credentials have the following roles (credential.py:157-175):
  • Admin Role: Full control over the credential
  • Use Role: Can use credential in jobs
  • Read Role: Can view credential metadata (not secrets)
Credential permissions are scoped to organizations. Users can only assign credentials to resources in the same organization.

Role Validation

AWX validates role assignments:
# From credential.py:397-411
def validate_role_assignment(self, actor, role_definition, **kwargs):
    if self.organization:
        if isinstance(actor, User):
            if actor.is_superuser:
                return
            if Organization.access_qs(actor, 'member').filter(id=self.organization.id).exists():
                return
            
            requesting_user = kwargs.get('requesting_user', None)
            if check_resource_server_for_user_in_organization(actor, self.organization, requesting_user):
                return
        if isinstance(actor, Team):
            if actor.organization == self.organization:
                return
        raise DRFValidationError({'detail': _(f"You cannot grant credential access to a {actor._meta.object_name} not in the credentials' organization")})

Security Best Practices

Integrate with CyberArk, HashiCorp Vault, or other external vaults to avoid storing sensitive credentials in AWX.
For highly sensitive passwords, use “ASK” to prompt at runtime rather than storing them.
Always set the organization field to limit credential access to organization members.
Grant only “use” role to users who need to run jobs. Reserve “admin” for credential managers.
Establish a process for rotating credentials, especially SSH keys and API tokens.
Instead of storing sensitive variables in plain text, use Ansible Vault and vault credentials.

Credential Injection

During job execution, AWX injects credentials using the injector schema:
# From credential.py:572-575
def inject_credential(self, credential, env, safe_env, args, private_data_dir, container_root=None):
    from awx_plugins.interfaces._temporary_private_inject_api import inject_credential
    
    inject_credential(self, credential, env, safe_env, args, private_data_dir, container_root=container_root)
This ensures credentials are:
  • Decrypted only during job execution
  • Injected into the isolated execution environment
  • Never exposed in logs or output
  • Cleaned up after job completion

Build docs developers (and LLMs) love