Skip to content

Ansible Integration

Automate SSH key distribution, rotation, and compliance enforcement across your infrastructure using Ansible playbooks integrated with the SSH-KLM API.


┌─────────────────┐ ┌─────────────┐ ┌──────────────────┐
│ SSH-KLM API │◄─────►│ Ansible │──────►│ Target Hosts │
│ (Inventory) │ │ Controller │ │ (authorized_keys)│
└─────────────────┘ └─────────────┘ └──────────────────┘
│ │
│ • Key inventory │ • Push authorized_keys
│ • Policy rules │ • Rotate key pairs
│ • Audit events │ • Report status back
▼ ▼
┌─────────────────┐ ┌─────────────┐
│ Key Policies │ │ Ansible │
│ & Compliance │ │ Tower / AWX │
└─────────────────┘ └─────────────┘

Create an API key in SSH-KLM for Ansible to authenticate.

  1. Navigate to Settings → API Keys in the SSH-KLM platform
  2. Click Generate New Key
  3. Set the scope to keys:read, keys:write, hosts:read
  4. Copy the generated API key

Store the credentials in your Ansible vault:

# group_vars/all/vault.yml (encrypted with ansible-vault)
ssh_klm_api_url: "https://ssh-klm.example.com/api/v1"
ssh_klm_api_key: "sk_live_abc123..."

Encrypt the vault file:

Terminal window
ansible-vault encrypt group_vars/all/vault.yml

Step 2: Distribute Authorized Keys from SSH-KLM

Section titled “Step 2: Distribute Authorized Keys from SSH-KLM”

This playbook fetches the authorized keys for each host from SSH-KLM and deploys them.

playbooks/distribute_keys.yml
---
- name: Distribute SSH authorized keys from SSH-KLM
hosts: all
become: true
vars:
ssh_klm_api_url: "{{ vault_ssh_klm_api_url }}"
ssh_klm_api_key: "{{ vault_ssh_klm_api_key }}"
tasks:
- name: Fetch authorized keys for host from SSH-KLM
ansible.builtin.uri:
url: "{{ ssh_klm_api_url }}/hosts/{{ inventory_hostname }}/authorized-keys"
method: GET
headers:
Authorization: "Bearer {{ ssh_klm_api_key }}"
Content-Type: "application/json"
return_content: true
status_code: 200
register: klm_keys
delegate_to: localhost
- name: Ensure .ssh directory exists for each user
ansible.builtin.file:
path: "/home/{{ item.username }}/.ssh"
state: directory
owner: "{{ item.username }}"
group: "{{ item.username }}"
mode: "0700"
loop: "{{ klm_keys.json.users }}"
- name: Deploy authorized_keys
ansible.builtin.copy:
content: "{{ item.authorized_keys | join('\n') }}\n"
dest: "/home/{{ item.username }}/.ssh/authorized_keys"
owner: "{{ item.username }}"
group: "{{ item.username }}"
mode: "0600"
loop: "{{ klm_keys.json.users }}"
- name: Report deployment status to SSH-KLM
ansible.builtin.uri:
url: "{{ ssh_klm_api_url }}/hosts/{{ inventory_hostname }}/sync-status"
method: POST
headers:
Authorization: "Bearer {{ ssh_klm_api_key }}"
Content-Type: "application/json"
body_format: json
body:
status: "synced"
timestamp: "{{ ansible_date_time.iso8601 }}"
status_code: 200
delegate_to: localhost

Run the playbook:

Terminal window
ansible-playbook playbooks/distribute_keys.yml --ask-vault-pass

This playbook generates new key pairs, deploys them, and updates SSH-KLM inventory.

playbooks/rotate_keys.yml
---
- name: Rotate SSH keys and update SSH-KLM
hosts: all
become: true
vars:
ssh_klm_api_url: "{{ vault_ssh_klm_api_url }}"
ssh_klm_api_key: "{{ vault_ssh_klm_api_key }}"
key_algorithm: "ed25519"
tasks:
- name: Generate new SSH key pair
ansible.builtin.openssh_keypair:
path: "/home/{{ item }}/.ssh/id_{{ key_algorithm }}"
type: "{{ key_algorithm }}"
owner: "{{ item }}"
group: "{{ item }}"
mode: "0600"
force: true
loop: "{{ ssh_users }}"
register: new_keys
- name: Read new public key
ansible.builtin.slurp:
src: "/home/{{ item.item }}/.ssh/id_{{ key_algorithm }}.pub"
loop: "{{ new_keys.results }}"
register: public_keys
- name: Update SSH-KLM with new public key
ansible.builtin.uri:
url: "{{ ssh_klm_api_url }}/keys"
method: PUT
headers:
Authorization: "Bearer {{ ssh_klm_api_key }}"
Content-Type: "application/json"
body_format: json
body:
hostname: "{{ inventory_hostname }}"
username: "{{ item.item.item }}"
public_key: "{{ item.content | b64decode | trim }}"
algorithm: "{{ key_algorithm }}"
rotated_at: "{{ ansible_date_time.iso8601 }}"
status_code: [200, 201]
loop: "{{ public_keys.results }}"
delegate_to: localhost
- name: Remove old key files
ansible.builtin.file:
path: "/home/{{ item }}/.ssh/id_rsa"
state: absent
loop: "{{ ssh_users }}"
when: key_algorithm != "rsa"

Step 4: Scheduled Rotation via Ansible Tower / AWX

Section titled “Step 4: Scheduled Rotation via Ansible Tower / AWX”

Configure automated key rotation in Ansible Tower or AWX:

  1. Create a Project pointing to your playbook repository
  2. Create an Inventory synced from SSH-KLM (dynamic inventory script)
  3. Create a Job Template using rotate_keys.yml
  4. Create a Schedule for the job template:
    • Rotation frequency: every 90 days (or per policy)
    • Enable notifications for failures
#!/usr/bin/env python3
"""SSH-KLM dynamic inventory for Ansible."""
import json
import os
import requests
API_URL = os.environ.get("SSH_KLM_API_URL")
API_KEY = os.environ.get("SSH_KLM_API_KEY")
def get_inventory():
headers = {"Authorization": f"Bearer {API_KEY}"}
response = requests.get(f"{API_URL}/hosts", headers=headers)
hosts = response.json()["hosts"]
inventory = {"all": {"hosts": {}, "children": {}}, "_meta": {"hostvars": {}}}
for host in hosts:
hostname = host["hostname"]
inventory["all"]["hosts"][hostname] = None
inventory["_meta"]["hostvars"][hostname] = {
"ansible_host": host["ip_address"],
"ssh_users": host.get("users", []),
"ssh_klm_host_id": host["id"],
}
# Group by OS type
os_group = host.get("os_type", "unknown").replace(" ", "_").lower()
if os_group not in inventory["all"]["children"]:
inventory["all"]["children"][os_group] = {"hosts": []}
inventory["all"]["children"][os_group]["hosts"].append(hostname)
return inventory
if __name__ == "__main__":
print(json.dumps(get_inventory(), indent=2))

IssueCauseResolution
401 Unauthorized from SSH-KLM APIInvalid or expired API keyRegenerate API key in SSH-KLM Settings
Connection refused to APINetwork/firewall issueVerify connectivity from Ansible controller to SSH-KLM
Keys not appearing on hostsPlaybook targeting wrong inventoryVerify inventory_hostname matches SSH-KLM host records
Permission denied on authorized_keysIncorrect file ownershipEnsure become: true and correct owner/group in tasks
Key rotation not updating SSH-KLMAPI PUT failing silentlyCheck register output and add failed_when conditions
Dynamic inventory emptyEnvironment variables not setExport SSH_KLM_API_URL and SSH_KLM_API_KEY
Tower schedule not triggeringCredential misconfigurationVerify vault credentials are attached to the job template

  • Always use ansible-vault for API credentials
  • Test playbooks in a staging environment before production
  • Use --check mode for dry runs
  • Enable Ansible callback plugins for audit logging
  • Set serial: 10 for rolling deployments on large inventories
  • Tag tasks for selective execution (--tags distribute, --tags rotate)