Skip to content

Instantly share code, notes, and snippets.

@infamousjoeg
Last active October 29, 2025 13:49
Show Gist options
  • Select an option

  • Save infamousjoeg/9dd1d0cf47fa37f37b35ffdf20510950 to your computer and use it in GitHub Desktop.

Select an option

Save infamousjoeg/9dd1d0cf47fa37f37b35ffdf20510950 to your computer and use it in GitHub Desktop.
Solving Dynamic CyberArk CCP Lookups in Ansible Automation Platform

Solving Dynamic CyberArk CCP Lookups in Ansible Automation Platform

The Problem

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.

The Solution

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:

  1. One CCP credential per user (you already have this)
  2. Host or group variables defining which secret each server needs
  3. Playbook logic that combines credential + host context for dynamic lookups

Implementation

Step 1: Keep Your Existing CCP Credentials

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.

Step 2: Add Variables to Your AAP Inventory

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.

Step 3: Write Your Playbook with Dynamic Lookups

---
- 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: true

Key points:

  • delegate_to: localhost keeps credentials off target hosts
  • run_once: false ensures each host gets its own lookup
  • no_log: true prevents secrets from hitting logs

Step 4: Configure Your Job Template

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

Alternative: Custom Credential Type

If you need more control over how CCP details are exposed, build a custom credential type.

Create the 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_id

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


Scaling Patterns

Pattern 1: Safe-per-Team

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

Pattern 2: Environment-Based

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

Pattern 3: CMDB-Driven

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.


Why This Works

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

Troubleshooting

"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 >= 2

Run your job template with verbosity 2+ to see output.


Real-World Example

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

Bottom Line

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.


Next Steps

  1. Test with one team and one safe to validate the pattern
  2. Verify your CCP AppIDs have permissions to all required safes
  3. Standardize naming conventions for safe names and service accounts
  4. Document the inventory variable requirements for your teams
  5. 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."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment