Modern infrastructure automation creates a paradox: the tools that make operations faster also create new attack surfaces. When automation platforms hold long-lived credentials, run without identity verification, and make changes without policy gates, a single compromised account can pivot across every layer of the stack. This guide shows how to integrate Ansible Automation Platform 2.6 as the Policy Enforcement Point (PEP) in a NIST SP 800-207 Zero Trust Architecture – wiring identity (IdM), policy (OPA), secrets (Vault), workload identity (SPIFFE/SPIRE), and network enforcement (Arista) into a single, auditable automation fabric where every job proves it is authorized before a single task runs.
Traditional perimeter-based security assumes that anything inside the network boundary can be trusted. That model fails when attackers phish their way past the firewall, when insiders go rogue, and when workloads are spread across clouds, containers, and partner networks where “inside” no longer means anything. NIST Special Publication 800-207 formalised the alternative: Zero Trust Architecture, where every request – from a person, an application, or an automation job – must prove it should be allowed, every time, with no implicit trust based on network location.
For infrastructure automation, this creates specific demands. Every operational change – patching a server, rotating a credential, updating a firewall rule – needs to pass through identity checks, policy gates, and an audit trail. Wiring each action individually to an identity provider, policy engine, secrets manager, and SIEM produces a tangled mess of one-off integrations. Ansible Automation Platform sits in the middle and ties it all together as the single PEP: the place where policy decisions are enforced for every kind of infrastructure change.
Zero Trust Architecture – redhat.com
NIST SP 800-207: Zero Trust Architecture
The ZTA infrastructure stack maps directly onto the three NIST components:
| Persona | Challenge | What They Gain |
|---|---|---|
| Long-lived credentials, implicit trust across automation layers, no audit trail for “who ran what” | Every job is identity-verified, policy-gated, secrets are ephemeral, and every decision is logged with a chain of evidence from code commit to OPA decision to job output | |
| Maintaining parallel access controls in each tool (Ansible, Vault, LDAP, network) that drift out of sync | A single IdM group change cascades to AAP team membership, OPA policy context, and Vault access – no separate updates in each system | |
| Proving that operational changes were authorized and auditable against NIST 800-207 or CISA ZT Maturity Model | Policy-as-code in Git, OPA decisions logged, Vault leases tracked, and EDA response times measurable – all the evidence a compliance audit requires |
| Collection | Type | Purpose |
|---|---|---|
| ansible.builtin | Certified | URI module for OPA queries; template, copy, assert for policy enforcement tasks |
| ansible.controller | Certified | AAP credential, inventory, project, and job template management via playbooks |
| arista.eos | Certified | Arista EOS switch configuration – ACL and VLAN management for micro-segmentation |
| netbox.netbox | Certified | NetBox IPAM/DCIM – VLAN creation, device inventory, source-of-truth sync |
| ansible.posix | Certified | SSH hardening, audit rules, and filesystem permission enforcement |
| System | Required | Notes |
|---|---|---|
| Identity Provider (LDAP/LDAPS) | Yes | Red Hat IdM (FreeIPA), Active Directory |
| Policy Decision Point | Yes | Open Policy Agent (OPA) with Rego policies |
| Secrets Manager | Yes | HashiCorp Vault (KV v2, database secrets engine, SSH CA) |
| CMDB / IPAM | Yes | NetBox |
| Workload Identity | Yes for network automation | SPIFFE/SPIRE |
| Network fabric | Yes for micro-segmentation | Arista EOS (cEOS for lab), Cisco IOS, or equivalent |
| SIEM | Optional (required for EDA incident response) | Splunk, Wazuh |
| Git server | Yes | Gitea, GitHub, GitLab |
Operational Impact: Medium – initial configuration requires access to LDAP, Vault, and OPA. Day-2 operations (job launches, credential rotations) are non-disruptive.
Zero Trust infrastructure configuration with AAP follows a layered trust establishment pattern. The outer rings (identity and policy) are configured first; the inner rings (secrets, workload identity, segmentation) depend on them.
The pipeline enforces five independent trust decisions on every sensitive operation: (1) Is the user in the right IdM group? (2) Does AAP Policy as Code permit this job template for this team? (3) Does OPA’s in-playbook check confirm the operation context is valid? (4) Does SPIFFE confirm the request originates from the attested automation platform? (5) Does the network fabric permit the traffic path? Each decision is evaluated independently – passing one does not grant implicit trust at any other layer.
graph TD
A([User / IdM Identity]) --> B(AAP Controller)
B --> C{OPA Policy as Code Gateway}
C -- DENY --> X([Job blocked -- never starts])
C -- ALLOW --> D(Job Launch)
D --> E[Playbook: Fetch SPIFFE SVID]
E --> F{OPA Inner-Ring Check}
F -- DENY --> Y([Fail closed -- no secrets, full audit log])
F -- ALLOW --> G(Vault: Issue Short-Lived Credential)
G --> H(Arista: Enforce ACL / Micro-Segment)
G --> I(NetBox: Update CMDB)
H --> J([Splunk / EDA: Monitor + Auto-Remediate])
| Stage | Impact | Description |
|---|---|---|
| 1. Vault credential integration | None | AAP credential objects store Vault paths; secrets resolved at job time |
| 2. LDAP authentication and team mapping | Low | Existing users gain new login path; local admin remains as fallback during cutover |
| 3. OPA policy gateway (Policy as Code) | Low | Audit mode available before enforcement; violations log before blocking |
| 4. In-playbook OPA checks | Low | Added to new templates first; existing templates unchanged during rollout |
| 5. SPIFFE workload identity | Medium | SPIRE agent must be deployed to execution nodes |
| 6. Arista micro-segmentation | High | ACL changes affect live network paths – test in staging with shadow rules before production |
Operational Impact: None
AAP’s Vault lookup plugin allows credential objects to store Vault paths rather than actual secrets. The controller resolves secrets at job runtime and never persists them to the AAP database. This applies the ZTA principle of least privilege: if AAP itself is compromised, an attacker finds Vault paths – not usable credentials.
Three credential types cover the full ZTA stack: machine credentials (SSH to RHEL hosts), network credentials (Arista eAPI), and database admin credentials. Vault SSH CA credentials replace static key distribution entirely – Vault signs short-lived certificates on demand, hosts trust only the CA.
Featured Tasks:
- name: Read machine credential from Vault
community.hashi_vault.vault_kv2_get:
path: machine/rhel
engine_mount_point: secret
register: machine_cred
- name: Read Vault SSH CA configuration
ansible.builtin.command: vault read ssh/config/ca
register: ssh_ca_output
RBAC: Restrict Vault lookup credentials to platform admins only.
The
ZTA Vault Credential(bootstrap Vault auth) should not be assigned to job templates directly – only used by AAP’s credential lookup mechanism. Keep it in theDefaultorg with no team-level execute permissions.
AAP Credential Configuration:
| Credential | Type | Source |
|---|---|---|
ZTA Vault Credential |
HashiCorp Vault | Bootstrap auth (stored in AAP – tighten with AppRole in production) |
ZTA Machine Credential |
Machine | KV secret/data/machine/rhel resolved at job time |
ZTA Arista Credential |
Network | KV secret/data/network/arista resolved at job time |
ZTA Vault SSH Credential |
Vault Signed SSH | AppRole + ssh/roles/ssh-signer CA |
Operational Impact: Low
IdM is the single source of truth for user identity. Configuring AAP to authenticate via LDAP and map group memberships to AAP teams means that revoking an IdM group membership immediately removes the associated AAP team access on next login – no local account changes, no CSV imports, no stale permissions.
AAP LDAP Authentication Settings:
# Settings → Authentication Methods → Create (Type: LDAP)
LDAP Server URI: "ldaps://central.zta.lab"
LDAP Bind DN: "uid=admin,cn=users,cn=accounts,dc=zta,dc=lab"
LDAP Group Type: "MemberDNGroupType"
LDAP Start TLS: false
LDAP Group Search:
- "cn=groups,cn=accounts,dc=zta,dc=lab"
- SCOPE_SUBTREE
- "(objectClass=groupOfNames)"
LDAP User Search:
- "cn=users,cn=accounts,dc=zta,dc=lab"
- SCOPE_SUBTREE
- "(uid=%(user)s)"
Required Authenticator Mappings:
| Mapping | Trigger | IdM Group DN | AAP Role |
|---|---|---|---|
| ZTA-ORG | Always | – | Organization Member (Default org) |
| ZTA-Admins | Groups | cn=zta-admins,... |
Organization Admin |
| Infrastructure team | Groups | cn=team-infrastructure,... |
Team Member (Infrastructure) |
| DevOps team | Groups | cn=team-devops,... |
Team Member (DevOps) |
| Security team | Groups | cn=team-security,... |
Team Member (Security) |
| Applications team | Groups | cn=team-applications,... |
Team Member (Applications) |
Zero Trust principle: Authentication alone is insufficient.
A successful LDAP bind proves identity but grants no permissions. Without authenticator maps, the user lands on a blank platform. The maps translate verified external identity into scoped, least-privilege access – no parallel local accounts to maintain.
Operational Impact: Low (audit mode available before enforcement)
AAP 2.6 introduces a native OPA gateway: the controller sends full job context (template name, launching user, their AAP team memberships) to OPA before enqueuing the job. If OPA returns a deny, the job never starts – not “it fails partway through,” but never begins.
AAP Policy as Code Configuration (Settings → Automation Execution → Policy):
| Field | Value |
|---|---|
| OPA Hostname | 192.168.1.11 (or OPA FQDN) |
| Port | 8181 |
| OPA Request Timeout | 5 |
| OPA Request Retry Count | 2 |
Sample OPA Rego policy (aap/gateway/decision):
package aap.gateway
default decision = {"allow": false, "violations": {}}
decision = {"allow": true} {
not has_violations
}
violations["patch_team"] {
is_patch_template
not user_in_patch_team
}
is_patch_template {
contains(lower(input.template.name), "patch")
}
user_in_patch_team {
team := input.created_by.teams[_]
team == "Infrastructure"
}
Template classification and required teams:
| Template name pattern | Required AAP team |
|---|---|
| Contains “patch” | Infrastructure or Security |
| Contains “vlan” or “network” | Infrastructure |
| Contains “deploy” or “credential” | Applications or DevOps |
| All others | Any authenticated user |
Operational Impact: Low
Platform-level Policy as Code catches “wrong user for this template type.” In-playbook OPA checks add context-specific authorization – validating the exact operation against the exact user, with the full input context available to Rego. Together they form two independent enforcement rings.
Featured Tasks:
- name: Query OPA for database access decision
ansible.builtin.uri:
url: "{{ opa_url }}/v1/data/zta/db_access/decision"
method: POST
body_format: json
body:
input:
user: "{{ awx_user_name }}"
user_groups: "{{ user_idm_groups }}"
target_database: "{{ db_name }}"
requested_permissions: ["SELECT", "INSERT", "UPDATE"]
register: opa_decision
- name: Enforce OPA decision -- fail closed on deny
ansible.builtin.assert:
that: opa_decision.json.result.allow | default(false)
fail_msg: >
ACCESS DENIED by OPA policy.
User '{{ awx_user_name }}' is not authorised to request database credentials.
graph LR
A{OPA DB Policy Check} -- ALLOW --> B(Vault: Create DB Credential)
A -- DENY --> X([Workflow stops])
B --> C(Arista: Configure DB ACL)
C --> D([Deploy Application])
Operational Impact: Medium
User identity proves who is asking. Workload identity proves what is asking. A stolen AAP token replayed from an attacker’s laptop passes every user identity check – SPIFFE closes that gap. The playbook fetches a short-lived X.509 SVID from the SPIRE agent (which only runs on attested AAP nodes), and OPA validates it before any network change proceeds.
SPIRE Agent registration (on AAP execution node):
spire-server entry create \
-spiffeID spiffe://zta.lab/workload/network-automation \
-parentID spiffe://zta.lab/agent/control \
-selector unix:uid:0
Featured Tasks:
- name: Fetch X.509 SVID from SPIRE Agent
ansible.builtin.command: >
/opt/spire/bin/spire-agent api fetch x509
-socketPath /run/spire/agent/api.sock
register: svid_result
- name: Parse SPIFFE ID from SVID output
ansible.builtin.set_fact:
workload_spiffe_id: "{{ svid_result.stdout
| regex_search('SPIFFE ID:\\s+(spiffe://\\S+)', '\\1')
| first }}"
- name: Query OPA with user identity + workload SVID
ansible.builtin.uri:
url: "{{ opa_url }}/v1/data/zta/network/decision"
method: POST
body_format: json
body:
input:
spiffe_id: "{{ workload_spiffe_id }}"
user: "{{ awx_user_name }}"
user_groups: "{{ user_idm_groups }}"
vlan_id: "{{ new_vlan_id | int }}"
action: "create"
register: opa_network_decision
- name: Enforce combined identity decision -- fail closed
ansible.builtin.assert:
that: opa_network_decision.json.result.allow == true
fail_msg: "VLAN CONFIGURATION DENIED. Reason: {{ opa_network_decision.json.result.reason }}"
Launch the Verify ZTA Services job template from the AAP UI or via API. This single job checks IdM reachability, OPA policy responses, Vault KV reads, Vault dynamic database credentials, and Vault SSH CA – confirming every integration before relying on it.
TOKEN="your-aap-token"
CONTROLLER="https://control.zta.lab"
JOB_ID=$(curl -sk -X POST "${CONTROLLER}/api/v2/job_templates/Verify%20ZTA%20Services/launch/" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" | jq -r '.id')
until curl -sk "${CONTROLLER}/api/v2/jobs/${JOB_ID}/" \
-H "Authorization: Bearer ${TOKEN}" \
| jq -e '.status == "successful"' > /dev/null 2>&1; do sleep 5; done
echo "ZTA verification complete -- job ${JOB_ID}"
Expected Result:
TASK [Display Vault integration results] *************************************
ok: [vault] => {
"msg": [
"KV secret read: PASS (secret/network/arista)",
"Dynamic DB username: v-userpass-ztaapp-s-anmlwAfJij4NVbUPFDjj-1776239621",
"Dynamic DB TTL: 300s",
"Short-lived DB user expires after TTL (Zero Trust / least privilege)."
]
}
TASK [OPA policy test summary] ***********************************************
ok: [central] => {
"msg": [
"Test 1 -- Patch (authorised): ALLOW",
"Test 2 -- Patch (wrong group): DENY",
"Test 3 -- VLAN (network-admin): DENY",
"Test 4 -- VLAN (no group): DENY",
"Test 5 -- DB (app-deployer): ALLOW",
"Zero Trust: deny-by-default, policy-driven access."
]
}
curl -sk -X POST "${CONTROLLER}/api/v2/job_templates/Apply%20Security%20Patch/launch/" \
-H "Authorization: Bearer ${TOKEN_FOR_APPDEV}" \
-H "Content-Type: application/json" | jq '.detail'
Expected: Policy violation message – job never enqueued.
"This job cannot be executed due to a policy violation or error. {'Violations': {'Organization': [\"user 'appdev' is not in an authorised team for patching templates (requires: {'Infrastructure', 'Security'}, has: {'Applications'})\"]}}"
| Symptom | Likely Cause | Fix |
|---|---|---|
| LDAP users log in but see an empty platform | Missing ZTA-ORG authenticator map (Always → Organization Member) |
Add the Always trigger map for the Default org first; it is a prerequisite for all other maps |
| Vault credential test fails with 403 | Vault policy does not allow the bootstrap auth path to read the target KV path | Check vault policy read admin-policy and confirm secret/data/* is included with read capability |
| OPA Policy as Code gateway returns 500 | Incorrect OPA hostname or port in AAP settings | Verify OPA is reachable from the AAP controller: curl http://<opa-host>:8181/health |
| SPIFFE SVID fetch fails in playbook | SPIRE agent not running or workload not registered | Check systemctl status spire-agent; verify registration with spire-server entry show |
| Dynamic DB credentials expire before application restarts | TTL shorter than restart window | Increase Vault role TTL or implement credential rotation with EDA before expiry |
| Arista ACL does not take effect | Switch did not save config | Add arista.eos.eos_command: commands: [write memory] after config tasks |
| Maturity | Description |
|---|---|
| Connect AAP to IdM LDAP, Vault KV, and OPA in audit-only mode. Log every policy decision without blocking. Verify Vault credential lookups replace the first set of static passwords. Confirm IdM groups flow into AAP team memberships. | |
| Enable OPA Policy as Code enforcement for high-risk template categories (patching, network changes). Add in-playbook OPA checks for secret issuance. Replace all machine credentials with Vault dynamic secrets. Deploy SPIFFE/SPIRE to execution nodes for network automation. Configure Arista ACLs through AAP-gated automation. | |
| Event-Driven Ansible watches Splunk and Wazuh for security events and triggers automated remediation (credential revocation, host isolation) in under 30 seconds. OPA policies include time-based controls, risk scoring, and break-glass approval workflows. SPIFFE attestation covers the full supply chain – container image hash, Git commit, and CI/CD pipeline. Every policy change is a Git commit, reviewed, tested, and deployed like application code. |
This solution guide shows how to configure Ansible Automation Platform 2.6 as the central Policy Enforcement Point in a NIST SP 800-207-aligned Zero Trust Architecture. By integrating IdM for user identity, OPA for policy decisions, Vault for short-lived secrets, SPIFFE/SPIRE for workload identity, and Arista for network micro-segmentation, every infrastructure change passes through independent, auditable trust gates before a single task executes. The result is a measurable reduction in blast radius: stolen credentials expire within minutes, unauthorized users are blocked before secrets are issued, and rogue workloads are rejected even when they carry valid user tokens. The maturity path from audit-only LDAP integration to fully automated incident response is incremental – each stage delivers real security value while building toward complete ZTA coverage.