> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ansible/awx/llms.txt
> Use this file to discover all available pages before exploring further.

# Credentials

> Understanding AWX Credentials and authentication management

## 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.

<Info>
  Credentials are AWX's secure vault for authentication data - they enable automation to access systems while maintaining security best practices.
</Info>

## Core Concepts

### Credential Types

AWX supports multiple credential types (`awx/main/models/credential.py:429-442`):

| Kind | Description | Use Cases |
| - | - | - |
| **Machine (ssh)** | SSH credentials | Connecting to Linux/Unix hosts |
| **Vault** | Ansible Vault passwords | Decrypting encrypted variables |
| **Network (net)** | Network device credentials | Network automation |
| **Source Control (scm)** | Git/SVN credentials | Accessing private repositories |
| **Cloud** | Cloud provider credentials | AWS, Azure, GCP, OpenStack, etc. |
| **Container Registry** | Registry credentials | Pulling execution environment images |
| **Token** | Personal access tokens | API authentication |
| **Insights** | Red Hat Insights credentials | Insights integration |
| **External** | External credential plugins | CyberArk, HashiCorp Vault, etc. |
| **Kubernetes** | K8s authentication | OpenShift Virtualization |
| **Galaxy** | Ansible Galaxy tokens | Installing collections from Galaxy |
| **Cryptography** | GPG keys | Content signing/verification |

### Credential Structure

From the Credential model (`awx/main/models/credential.py:122-175`):

```python theme={null}
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:

```json theme={null}
{
  "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:

```python theme={null}
# 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:

```python theme={null}
# 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:

```python theme={null}
# 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:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

```python theme={null}
# 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:

```http theme={null}
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`:

```python theme={null}
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:

```python theme={null}
# 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:

```python theme={null}
# 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

<Warning>
  Some credential types are mutually exclusive (e.g., only one SSH credential per job). AWX validates this during job launch.
</Warning>

## Vault Credentials

Vault credentials support multiple vault IDs:

```python theme={null}
# 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

```http theme={null}
GET /api/v2/credentials/
```

### Create SSH Credential

```http theme={null}
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

```http theme={null}
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

```http theme={null}
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)

```http theme={null}
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)

<Info>
  Credential permissions are scoped to organizations. Users can only assign credentials to resources in the same organization.
</Info>

### Role Validation

AWX validates role assignments:

```python theme={null}
# 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

<AccordionGroup>
  <Accordion title="Use External Credentials for Sensitive Secrets">
    Integrate with CyberArk, HashiCorp Vault, or other external vaults to avoid storing sensitive credentials in AWX.
  </Accordion>

  <Accordion title="Leverage Ask at Runtime">
    For highly sensitive passwords, use "ASK" to prompt at runtime rather than storing them.
  </Accordion>

  <Accordion title="Scope to Organizations">
    Always set the organization field to limit credential access to organization members.
  </Accordion>

  <Accordion title="Principle of Least Privilege">
    Grant only "use" role to users who need to run jobs. Reserve "admin" for credential managers.
  </Accordion>

  <Accordion title="Rotate Credentials Regularly">
    Establish a process for rotating credentials, especially SSH keys and API tokens.
  </Accordion>

  <Accordion title="Use Vault for Ansible Secrets">
    Instead of storing sensitive variables in plain text, use Ansible Vault and vault credentials.
  </Accordion>
</AccordionGroup>

## Credential Injection

During job execution, AWX injects credentials using the injector schema:

```python theme={null}
# 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

## Related Resources

* [Job Templates](/concepts/job-templates) - Use credentials in jobs
* [Projects](/concepts/projects) - SCM credentials for projects
* [Inventories](/concepts/inventories) - Credentials for inventory sources
* [Organizations](/guides/organizations-and-teams) - Scope credentials to organizations


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.