Ansible Integration
Ansible Integration
Section titled “Ansible Integration”Automate SSH key distribution, rotation, and compliance enforcement across your infrastructure using Ansible playbooks integrated with the SSH-KLM API.
Architecture
Section titled “Architecture”┌─────────────────┐ ┌─────────────┐ ┌──────────────────┐│ 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 │└─────────────────┘ └─────────────┘Step 1: Configure SSH-KLM API Credentials
Section titled “Step 1: Configure SSH-KLM API Credentials”Create an API key in SSH-KLM for Ansible to authenticate.
- Navigate to Settings → API Keys in the SSH-KLM platform
- Click Generate New Key
- Set the scope to
keys:read,keys:write,hosts:read - 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:
ansible-vault encrypt group_vars/all/vault.ymlStep 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.
---- 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: localhostRun the playbook:
ansible-playbook playbooks/distribute_keys.yml --ask-vault-passStep 3: Rotate SSH Keys
Section titled “Step 3: Rotate SSH Keys”This playbook generates new key pairs, deploys them, and updates SSH-KLM inventory.
---- 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:
- Create a Project pointing to your playbook repository
- Create an Inventory synced from SSH-KLM (dynamic inventory script)
- Create a Job Template using
rotate_keys.yml - Create a Schedule for the job template:
- Rotation frequency: every 90 days (or per policy)
- Enable notifications for failures
Dynamic Inventory Script
Section titled “Dynamic Inventory Script”#!/usr/bin/env python3"""SSH-KLM dynamic inventory for Ansible."""import jsonimport osimport 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))Troubleshooting
Section titled “Troubleshooting”| Issue | Cause | Resolution |
|---|---|---|
401 Unauthorized from SSH-KLM API | Invalid or expired API key | Regenerate API key in SSH-KLM Settings |
Connection refused to API | Network/firewall issue | Verify connectivity from Ansible controller to SSH-KLM |
| Keys not appearing on hosts | Playbook targeting wrong inventory | Verify inventory_hostname matches SSH-KLM host records |
| Permission denied on authorized_keys | Incorrect file ownership | Ensure become: true and correct owner/group in tasks |
| Key rotation not updating SSH-KLM | API PUT failing silently | Check register output and add failed_when conditions |
| Dynamic inventory empty | Environment variables not set | Export SSH_KLM_API_URL and SSH_KLM_API_KEY |
| Tower schedule not triggering | Credential misconfiguration | Verify vault credentials are attached to the job template |
Best Practices
Section titled “Best Practices”- Always use
ansible-vaultfor API credentials - Test playbooks in a staging environment before production
- Use
--checkmode for dry runs - Enable Ansible callback plugins for audit logging
- Set
serial: 10for rolling deployments on large inventories - Tag tasks for selective execution (
--tags distribute,--tags rotate)