Collect SSH Keys with an Ansible Playbook

Articles » Ansible-related content » Collect SSH Keys with an Ansible Playbook

One of the annoyances of running ephemeral virtual labs where you recreate the devices every time you start the lab (I'm looking at you VIRL) are the ever-changing SSH keys. Vagrant neatly solves that problem; here are a few tricks if you're using some other staging infrastructure.

Create SSH Keys on Reload

A major nuisances of running a virtual copy of Cisco IOS in VIRL is the lack of persistent storage of SSH keys (I am positive they are permanently stored in the CSR 1000V disk image). The SSH keys are not stored in router configuration and as the only information that's retained across VIRL simulations is the router configuration the routers usually get started with no SSH keys.

Here's an EEM applet that solves that problem:

event manager applet ssh-keys  
 event syslog occurs 1 pattern "%SYS-5-RESTART: System restarted"  
 action 1.0 cli command "enable"  
 action 2.0 cli command "configure terminal"  
 action 3.0 cli command "crypto key generate rsa modulus 1024"

However, devices geting new keys after every reboot make the ssh clients using default settings extremely unhappy.

Ignore SSH Keys

You could add StrictHostKeyChecking=no to ~/.ssh/config or /etc/ssh/ssh_config to stop ssh client from complaining about ever-changing SSH keys on your network devices... but every security professional would quickly point out how unsafe that is. Let's look for another solution.

Collect SSH Keys from the Devices

Use this Ansible playbook (based on a blog post by Larry Smith) to collect SSH keys from devices listed in your inventory file and store them in ~/.ssh/known_hosts

- name: Get SSH keys  
  hosts: all  
  gather_facts: no  
  connection: local  
    - known_hosts: "~/.ssh/known_hosts"  
    - name: scan and register  
      command: "ssh-keyscan {{ansible_host|default(inventory_hostname)}}"  
      register: "host_keys"  
      changed_when: false  

    - file: path={{known_hosts}} state=touch  
      run_once: true  

    - blockinfile:  
        dest: "{{known_hosts}}"  
        marker: "# {mark} This part managed by Ansible"  
        block: |  
          {% for h in groups['all'] if hostvars[h].host_keys is defined %}  
          {{ hostvars[h].host_keys.stdout }}  
          {% endfor %}  
      run_once: true

The latest version of the playbook correctly handles missing devices and SSH servers running on non-default port numbers. The details of its operation are explained in the Sample Ansible Playbooks section of Ansible for Networking Engineers online course.