An Ansible automation controller holds the SSH keys, privilege escalation passwords and cloud credentials for every system it manages, so whoever controls it controls the estate. Ansible itself runs agentless over SSH and WinRM, which moves the trust problem to the place playbooks are launched from: an engineer's laptop, a CI runner or an AWX controller. This guide covers how to secure that control point: Ansible Vault and its limits, AWX credentials and external secret lookups, role-based access, API tokens, execution environments, logging and a hardening baseline. It is for platform engineers, automation teams and security architects who run Ansible at scale. It complements the Infrastructure as Code Security Guide, which covers IaC scanning and pipelines in general. AWX and Ansible behaviour below was checked against the official documentation on 27 October 2026.
Key takeaways
- Vault protects data at rest only. The Ansible documentation states that Vault encryption only protects data at rest; once decrypted, keeping secrets out of output is the job of the play author, using
no_log. - The controller is a tier 0 system. AWX system administrators can change any inventory or automation definition, and sudo for root or the awx user is equivalent to full root, so both groups must be tiny.
- Users should run credentials, not see them. The AWX Use role lets a user launch jobs with a credential without seeing the secret, and AWX never returns secrets through its API.
- Prefer external secret lookups. AWX credential types such as HashiCorp Vault Secret Lookup, CyberArk Conjur and Azure Key Vault fetch secrets at run time, so the controller database stops being the master copy.
- Check where your AWX version stands. The AWX repository states its last release was on 2 July 2024 and releases are paused during a refactoring, which matters for patching plans.
How identity and access work in Ansible and AWX
There are three distinct identities in every Ansible run, and they are often confused.
| Identity | What it is | Where it lives | Main risk |
|---|---|---|---|
| Operator | The person or pipeline that launches a playbook | AWX user (local, LDAP, SAML 2.0 or OAuth provider), or the shell user on a control node | Over-broad roles let one account run any template against any inventory |
| Connection identity | The account Ansible uses to reach managed hosts (SSH key, password, WinRM) | AWX Machine credential, or files on a control node | One shared key reaching every host; keys copied to laptops |
| Escalation identity | The privilege escalation method and password (sudo, su, pbrun, dzdo, runas, enable and others) | Machine credential fields Privilege Escalation Method, Username and Password | Unrestricted sudo to root on every host |
| Integration identity | Cloud keys, source control tokens, registry and vault credentials | AWX credential types (Amazon Web Services, Source Control, Container Registry, GitHub Personal Access Token and others) | Long-lived cloud keys injected as environment variables such as AWS_ACCESS_KEY_ID |
AWX's security best practices recommend connecting to LDAP, SAML 2.0 or a supported OAuth provider instead of maintaining local users, assigning permissions to Teams rather than individuals, and giving non-administrators no local access to the AWX system. The connection and escalation identities are non-human identities: they need owners, rotation and scoping like any service account. See the SSH Key Management Guide.
Ansible Vault
- Purpose: encrypts variables and files so passwords and keys are not stored as plaintext in playbooks and roles, and can be kept in source control.
- Password sources:
--ask-vault-pass(prompt),--vault-password-file(single-line file), or a client script named ending in-clientthat prints the password from a keyring or secret manager, referenced as--vault-id label@path/to/script. - Vault IDs: the label (for example
devorprod) appears in plaintext in the file header. Ansible does not enforce one password per label and by default tries every supplied password on every value; setDEFAULT_VAULT_ID_MATCHso each password only opens content with its own label. - Password files: the documentation is explicit: do not add password files to source control, and set restrictive file permissions.
- Limits: Vault does nothing once a value is decrypted. Mark tasks that handle secrets with
no_log: true, and remember the decrypted value is present in memory on the control node and in any file a task writes on the target.
Admin roles and privileged access
AWX has two singleton roles, System Administrator (full control) and System Auditor (read-only across everything), and object roles that apply to organisations, teams, inventories, projects, job templates and credentials.
| Role | What it allows | Who should hold it |
|---|---|---|
| System Administrator | Full control over every object, including all credentials and inventories | Two or three named people, for low-level configuration and disaster recovery only |
| System Auditor | Read-only view of everything | Security and audit staff |
| Admin (organisation, project, inventory, job template) | Manage the object | Platform owners per organisation |
| Execute | Run an assigned job template | Operators and service desk; the default for most users |
| Ad Hoc | Run ad hoc commands against an inventory | Very few; ad hoc commands bypass reviewed playbooks |
| Use | Apply a credential, inventory, project or instance group in a job template without seeing secrets | Template authors |
| Update | Pull a project from source control or refresh an inventory source | Pipeline identities |
| Owner (credential) | Own and manage a credential | The team accountable for that secret |
- Delegation caveat: admin on a job template covers non-sensitive fields only. Changing fields that affect how a job runs also needs Use on the project, inventory and instance groups. This is a safety property: keep it, and do not grant organisation-wide admin to get round it.
- Host level: AWX warns that sudo limited to specific commands is still dangerous, since any command that runs a shell or changes files is equivalent to full root. Keep the group with root on the AWX hosts or cluster as small as possible.
- Separate credentials: use different credentials for automation and for human administrator access, and different keys per automation tier (for example, separate keys for domain controllers, production Linux and network devices), so one compromised key has limited reach.
- Restrict SSH keys by source: AWX recommends limiting SSH credentials to connections from specific source addresses using OpenSSH configuration, for example a
from=option on the authorised key, so a stolen key is useless outside the controller.
Treat AWX administrators and the Machine credentials as privileged access. See the PAM Maturity Model.
Integrations, API tokens and service accounts
Credentials and external secret lookups
AWX encrypts passwords and keys in its database and never makes them visible through the API. SSH keys are decrypted and passed to SSH through a named pipe, so they are not written to disk. Cloud credentials are injected as environment variables or generated files for the duration of the job. For scheduled jobs, the "prompt on launch" option cannot be used, so those credentials must be stored or looked up.
| Approach | How it works | When to choose it |
|---|---|---|
| Stored AWX credential | Secret encrypted in the AWX database | Small estates; acceptable if the database and its encryption key are protected as tier 0 |
| External lookup (credential plugin) | A credential field is linked to a secret in an external store and fetched before the job runs. Types include HashiCorp Vault Secret Lookup, CyberArk Central Credential Provider Lookup, CyberArk Conjur Secrets Manager Lookup, Microsoft Azure Key Vault, AWS Secrets Manager and Thycotic Secret Server | Most organisations: rotation happens in the vault and AWX never holds the master copy |
| Signed SSH certificates | HashiCorp Vault Signed SSH credential type obtains a short-lived signed certificate | Large Linux estates that want to remove static authorised keys altogether |
| Prompt on launch | Password typed at launch, not stored | Rare, high-risk manual runs; not usable for schedules |
| Vault on a control node | ansible-vault files with a password client script | CLI-only teams without AWX; pair with a keyring or secret manager, never a password file in the repo |
See the HashiCorp Vault Guide and the Secrets Management Guide.
AWX API tokens
- Personal access tokens: created at
/api/v2/users/<userid>/personal_tokens/. The value is shown once. Scopes arereadandwrite, and act as a mask over the user's RBAC permissions: awritetoken carries everything the user can do. - Pipeline identities: CI systems that launch AWX jobs should use a dedicated AWX user with Execute on specific templates and a token scoped to that user, never a System Administrator's token.
- Revocation: delete the token at
/api/v2/tokens/<pk>/, use the revoke endpoint, or therevoke_oauth2_tokensmanagement utility for bulk revocation in an incident. - External users:
ALLOW_OAUTH2_FOR_EXTERNAL_USERSis disabled by default, so SSO users cannot create tokens. Turning it off later does not revoke tokens already created, so revoke them explicitly.
Execution environments and content
Execution environments are container images that hold Ansible, collections and system dependencies for a job. They are part of your supply chain.
- Image source: reference images by full registry path and tag, pull from a private registry with a Container Registry credential, and build them from reviewed definitions.
- Pull policy: "Always pull" picks up registry changes, including a tampered image; "Only pull if not present" and "Never pull" keep a known image. Pair "Always pull" with immutable tags or digests in your registry.
- Host mounts: keep exposed host paths as specific as possible, and leave "Expose host paths for Container Groups" off unless a job needs it.
- Playbook source: AWX treats projects and inventories as the source of truth once a job runs, so require branch protection and mandatory code review on playbook repositories, and use approval workflows for production. See the CI/CD Pipeline Identity Security Guide.
Logging and detection
AWX sends logs to Logstash, Splunk (HTTP Event Collector), Loggly, Sumologic or the Elastic stack over HTTPS, TCP or UDP, configured through /api/v2/settings/logging/ and the LOG_AGGREGATOR_* settings. Certificate verification is on by default for HTTPS: leave it on.
| Logger | Contents | Detection use |
|---|---|---|
activity_stream | Changes to AWX objects with actor, operation, changes (old and new values), object1, object2 | New credentials, role grants, credential reassignment, changes to job templates and inventories |
job_events | Callback data from each job run | Ad hoc commands, jobs run outside change windows, unusual target hosts |
system_tracking | Facts from the setup module when the fact cache is enabled | Inventory drift; may contain sensitive host data, so control access |
awx | Generic server logs | Authentication failures, service errors |
On managed hosts, log sudo and SSH sessions from the automation account, and alert when that account signs in from anywhere other than the controller. Relevant ATT&CK techniques: T1072 Software Deployment Tools (an attacker using the controller to push code), T1552.004 Private Keys and T1552.001 Credentials In Files (keys and vault password files on control nodes), T1021.004 SSH and T1098.004 SSH Authorized Keys (persistence through keys the playbooks deploy).
Metrics:
- Number of System Administrators and users with Ad Hoc roles (target: single figures, reviewed quarterly).
- Percentage of Machine and cloud credentials served by an external lookup rather than stored.
- Number of active personal access tokens with
writescope, and their age. - Percentage of production job templates that require an approval node or run from a protected branch.
- Number of automation SSH keys without a source address restriction.
- Count of vault password files or plaintext keys found by repository secret scanning.
Hardening baseline
- Network: do not expose AWX to the internet; place it in a management zone reachable only from administrator networks and the CI systems that call its API.
- Authentication: connect to SAML 2.0 or LDAP, keep the local admin account for break-glass only with a strong password, and enforce password policy through
AUTH_PASSWORD_VALIDATORSin/etc/awx/conf.d/custom.pyfor any local accounts. - Platform: keep SELinux enabled and do not disable AWX's multi-tenant containment.
- Roles: map Teams to directory groups, give most users Execute only, and grant Use on credentials rather than credential ownership.
- Secrets: move Machine and cloud credentials to external lookups, one credential per tier, with SSH keys restricted to the controller's source addresses.
- Content: protected branches and code review for projects, private registry for execution environments, approval nodes for production workflows.
- Logging: ship
activity_streamandjob_eventsto the SIEM and review the activity stream for administrative actions. - Lifecycle: because AWX releases are paused, decide whether your AWX deployment is supported for your risk appetite, track the project's communications, or move to a supported downstream distribution. Do not leave an unpatched controller in place by default.
Testing and rollback: make RBAC and credential changes in a non-production AWX first, migrate one inventory tier at a time to external lookups, and keep the stored credential disabled but present until the lookup has run cleanly through a full schedule cycle. For CLI teams, introduce the vault client script alongside the old method, then remove password files once every pipeline uses it.
Small teams running Ansible from the command line should focus on four things: vault password from a keyring or secret manager, no keys in repositories, a dedicated automation account per environment with constrained sudo, and code review. Large organisations should treat AWX as tier 0: separate clusters or organisations per security zone, external secrets, and SIEM detection on the activity stream.
Common misconfigurations
- Vault password file committed next to the vault: the encryption is then decorative. Scan for
.vault_passstyle files and forvault_password_fileentries inansible.cfg. - Secrets printed in job output: decrypted variables appear in verbose logs and job events when
no_logis missing, and those logs are shipped to the SIEM and kept for years. - One key for every host: a single automation key with root everywhere turns one leaked key into estate-wide compromise. Split by tier and restrict by source address.
- Everyone is an organisation admin: broad admin grants let users change job templates to run their own playbooks with production credentials.
- Unreviewed playbook repositories: anyone who can push to the project branch can run code as root on every host in the inventory. This is the pipeline poisoning pattern seen in CI/CD attacks such as the tj-actions/changed-files Compromise, where a modified build component exposed the secrets of every pipeline that ran it.
- Controller reachable from the internet: AWX guidance is explicit that it should not be exposed.
Framework mapping
| Control area | NIST SP 800-53 Rev 5 | NIST CSF 2.0 | CIS Controls v8.1 | ISO/IEC 27001:2022 Annex A |
|---|---|---|---|---|
| AWX roles and admin reduction | AC-2, AC-3, AC-6 | PR.AA-05 | 5, 6 | A.5.15, A.8.2 |
| Machine credentials, SSH keys, vault | IA-5, SC-28 | PR.AA-01, PR.DS-01 | 3, 5 | A.5.17, A.8.24 |
| Playbook review and approvals | CM-3, CM-5 | PR.PS-01 | 4, 16 | A.8.32 |
| Execution environment provenance | SR-3, SR-11 | GV.SC-07 | 16 | A.5.21 |
| Activity stream and job logging | AU-2, AU-6 | PR.PS-04, DE.CM-03 | 8 | A.8.15 |
Practitioner checklist
- List every System Administrator, organisation admin and Ad Hoc role holder in AWX and cut the list to the minimum.
- Connect AWX to SAML 2.0 or LDAP and map Teams to directory groups.
- Move Machine and cloud credentials to external lookups, one credential per tier, and restrict automation SSH keys to the controller's addresses.
- Set
DEFAULT_VAULT_ID_MATCH, source vault passwords from a client script, and remove password files from repositories. - Add
no_log: trueto every task that handles secrets and check job output for leaks. - Revoke personal access tokens with
writescope that have no named owner; give pipelines dedicated Execute-only users. - Require code review and protected branches on projects, and approval nodes on production workflows.
- Ship
activity_streamandjob_eventsto the SIEM with certificate verification on. - Confirm the support and patching status of your AWX version and decide on a supported path.
Standards and references
- AWX Documentation: Security best practices
- AWX Documentation: Credentials
- AWX Documentation: Security and role-based access
- AWX Documentation: Token-based authentication
- AWX Documentation: Logging
- Ansible Documentation: Managing vault passwords
- Ansible Documentation: Ansible Vault
- Ansible: AWX project repository
Related NHI Mgmt Group resources: Infrastructure as Code Security Guide · SSH Key Management Guide · HashiCorp Vault Guide · Secrets Management Guide