You've got multiple teams, each with their own CyberArk safes (let's say 20+), and secrets in those safes are needed for agent installation across your Linux fleet. If you try to solve this the "obvious" way—creating AAP credential objects for each user × safe combination—you end up with credential sprawl from hell. 200 users × 20 safes = 4,000 credential objects. Nobody wants that.
The thing is, you can't just template variables in AAP credential queries like this:
{
"object_query": "Username={{ service }};Address=foobar.example.dev"
}That doesn't work because AAP evaluates credentials at definition time, not at runtime. Host variables and extra_vars aren't in scope when the credential system runs.
Stop thinking about credentials as "the thing that fetches a specific secret" and start thinking about them as "authenticated connection to CCP." Then use inventory variables to define which secret each host needs, and let your playbook do dynamic lookups at runtime.
Here's the architecture:
- One CCP credential per user (you already have this)
- Host or group variables defining which secret each server needs
- Playbook logic that combines credential + host context for dynamic lookups
Your "CyberArk Central Credential Provider Lookup" credentials are fine as-is. These contain the CCP URL, AppID, and client certs. Don't change them—they provide authenticated CCP access per user, which is exactly what you need for audit trail and least privilege.
In AAP, go to Resources → Inventories → [Your Inventory] → Hosts and define per-host (or per-group) variables.
Option A: Host Variables
For individual hosts:
---
# Host: server001.prod.example.dev
cyberark_safe: "TeamA-Production"
cyberark_service_account: "svc-agent-installer-prod"
cyberark_address: "foobar.example.dev"
# Host: server002.qa.example.dev
cyberark_safe: "TeamB-QA"
cyberark_service_account: "svc-agent-installer-qa"
cyberark_address: "foobar.example.dev"Option B: Group Variables (Recommended)
Create inventory groups by team or environment:
# Group: team_a_prod_servers
cyberark_safe: "TeamA-Production"
cyberark_service_account: "svc-agent-installer-prod"
cyberark_address: "foobar.example.dev"
# Group: team_b_qa_servers
cyberark_safe: "TeamB-QA"
cyberark_service_account: "svc-agent-installer-qa"
cyberark_address: "foobar.example.dev"Group variables scale better—you define once per team instead of per server.
Option C: Pull from Your CMDB
If you've got ServiceNow, Satellite, or another source of truth, inject these as custom attributes on your hosts. Even better if you can automate it.
---
- name: Install agent with dynamic CyberArk secret retrieval
hosts: all
gather_facts: yes
tasks:
- name: Validate CyberArk variables are set
assert:
that:
- cyberark_safe is defined
- cyberark_service_account is defined
fail_msg: "Host missing cyberark_safe or cyberark_service_account variable"
- name: Retrieve installation secret from CyberArk
set_fact:
agent_secret: "{{ lookup('cyberark.pas.cyberark_credential',
api_base_url=lookup('env', 'CCP_URL'),
app_id=lookup('env', 'CCP_APP_ID'),
client_cert=lookup('env', 'CCP_CLIENT_CERT') | default(omit),
client_key=lookup('env', 'CCP_CLIENT_KEY') | default(omit),
query='Safe=' + cyberark_safe + ';Username=' + cyberark_service_account + ';Address=' + cyberark_address,
validate_certs=true
) }}"
no_log: true
delegate_to: localhost
run_once: false
- name: Install agent
include_role:
name: agent_installation
vars:
agent_password: "{{ agent_secret.password }}"
no_log: trueKey points:
delegate_to: localhostkeeps credentials off target hostsrun_once: falseensures each host gets its own lookupno_log: trueprevents secrets from hitting logs
In AAP: Resources → Templates → [Your Job Template]
Credentials: Attach your existing CCP credential (one per user). That's it. Don't create additional credentials for each safe.
Extra Variables (optional): Set defaults if needed:
cyberark_address: "foobar.cyberark.dev"Survey (optional): If you want users to override at runtime:
| Question | Variable | Type |
|---|---|---|
| Target Safe | cyberark_safe | Text |
| Service Account | cyberark_service_account | Text |
If you need more control over how CCP details are exposed, build a custom credential type.
AAP UI: Administration → Credential Types → Add
Name: CyberArk CCP Connection
Input Configuration:
fields:
- id: ccp_url
type: string
label: CCP URL
- id: app_id
type: string
label: Application ID
secret: true
- id: client_cert
type: string
label: Client Certificate
secret: true
multiline: true
- id: client_key
type: string
label: Client Key
secret: true
multiline: true
required:
- ccp_url
- app_idInjector Configuration:
env:
CCP_APP_ID: '{{ app_id }}'
CCP_CLIENT_CERT: '{{ client_cert }}'
CCP_CLIENT_KEY: '{{ client_key }}'
CCP_URL: '{{ ccp_url }}'Now your playbook accesses these as environment variables and constructs queries dynamically. The custom credential type gives you flexibility without exposing the AppID to users.
# group_vars/team_a_servers.yml
cyberark_safe: "TeamA-Safe"
cyberark_service_account: "svc-agent-installer"
# group_vars/team_b_servers.yml
cyberark_safe: "TeamB-Safe"
cyberark_service_account: "svc-agent-installer"Result: 200 users × 1 credential each = 200 credentials total. Not 4,000.
# group_vars/production.yml
cyberark_safe: "{{ team_name | upper }}-Production"
cyberark_service_account: "svc-agent-prod"
# group_vars/development.yml
cyberark_safe: "{{ team_name | upper }}-Dev"
cyberark_service_account: "svc-agent-dev"Pull from ServiceNow or similar:
- name: Get server metadata from CMDB
servicenow.itsm.configuration_item_info:
sys_id: "{{ snow_ci_sys_id }}"
register: cmdb_data
delegate_to: localhost
- name: Set CyberArk variables from CMDB
set_fact:
cyberark_safe: "{{ cmdb_data.record.u_cyberark_safe }}"
cyberark_service_account: "{{ cmdb_data.record.u_service_account }}"This is the most automated approach if your CMDB is accurate.
Security:
- AppID stays hidden from users (embedded in their CCP credential)
- Each user's credential has appropriate safe-level permissions via CyberArk
- Full audit trail since every query uses the user's AppID
- Linear credential growth (200 users = 200 credentials) instead of exponential
Operationally:
- Teams manage their own inventory variables
- No credential object churn when adding new safes or secrets
- Standard playbooks work across all teams
- Aligns with workload identity principles—secrets resolved at runtime based on workload context
"CCP_URL not found"
- Your CCP credential isn't attached to the job template. Check the Credentials section.
"cyberark_safe is undefined"
- Host variables aren't set. Go to your inventory and check host/group vars.
"Object not found" from CyberArk
- Query string doesn't match anything in PVWA. Verify safe name, username, and address are exact matches.
Debug the query:
- name: Show what query will be constructed
debug:
msg: "Query: Safe={{ cyberark_safe }};Username={{ cyberark_service_account }};Address={{ cyberark_address }}"
when: ansible_verbosity >= 2Run your job template with verbosity 2+ to see output.
You've got 500 servers across 25 teams. Here's what it looks like:
Inventory:
└── All Servers (500)
├── Team A (20 servers) → cyberark_safe: "TeamA-Prod"
├── Team B (35 servers) → cyberark_safe: "TeamB-Prod"
├── Team C (18 servers) → cyberark_safe: "TeamC-Prod"
└── ... (22 more teams)
Credentials in AAP:
├── alice-ccp (AppID: App-Alice)
├── bob-ccp (AppID: App-Bob)
└── ... (198 more users)
Result:
- 200 CCP credentials (one per user)
- Zero additional credentials for 25 teams × 20+ secrets
- Users only access safes their AppID permits
- Playbooks construct queries at runtime per host
| Approach | # of Credentials | Complexity | Scalability |
|---|---|---|---|
| Credential per secret | 4,000 | Low | Terrible |
| Host variables + dynamic lookup | 200 | Medium | Excellent |
| Group variables + dynamic lookup | 200 | Low | Excellent |
| Custom credential type | 200 | High | Excellent |
Use host or group variables with the existing CyberArk PAS collection's lookup plugin. You get one credential per user (which you already have) while supporting unlimited safe/secret combinations through inventory metadata.
This approach treats secrets like workload identity should work—resolved at runtime based on the context of what's running where, not pre-baked into static configurations.
- Test with one team and one safe to validate the pattern
- Verify your CCP AppIDs have permissions to all required safes
- Standardize naming conventions for safe names and service accounts
- Document the inventory variable requirements for your teams
- Monitor CCP query logs to spot any issues early
The pattern scales from 10 servers to 10,000. The key is shifting from "credential = specific secret" to "credential = authenticated access + inventory context."