Automating SSH keys with Ansible

I was working with a co-worker to get his SSH key distributed to a large number of systems so we could start managing them with Ansible. For security and logging purposes, we login to the systems with our personal “elevated” account (different from our workstation account), and the elevated accounts are permitted to run “sudo” so their Ansible playbooks can make changes to the system.

Today we were fighting with getting the SSH key generated and distributed to all the systems. I’ve been working in Unix and using SSH for over 20 years, but today we kept making simple typos that kept the keys from working for us. Some of the issues were human error and typos on the command line, others were more subtle involving permissions issues on the files. To help remove this human error, I wrote a two-step playbook to generate an ssh keypair (if they don’t exist), then login to a set of systems and setup that key to permit password-less logins.

Here’s the playbook – named “copykeys.yml” – built to automate the distribution of SSH keys across your environment:

 #!/usr/bin/env ansible-playbook
 ---
 - name: "Create and upload SSH keys"
   hosts: all
   gather_facts: false

   tasks:
   - name: "Ensure ssh key exists"
     openssh_keypair:
       path: "{{ lookup('env','HOME') + '/.ssh/id_ed25519' }}"
       type: ed25519
     delegate_to: localhost
     run_once: true

   - name: "Copy user ssh keys"
     authorized_key:
       user: "{{ lookup('env', 'USER') }}"
       state: present
       key: "{{ lookup('file', lookup('env','HOME') + '/.ssh/id_ed25519.pub') }}" 

This playbook first creates an “ed25519” SSH key if it does not exist on the machine the script is run on (the “delegate_to” argument), and once the SSH key is created the public key is distributed to each of the systems the playbook runs against.

To use this, simply call the playbook with either an inventory file (‘-i inventory.ini’) or with a command-line inventory list (‘-i server1,server2,server3’). Here is the command to run:

ansible-playbook copykeys.yml --ask-pass -i server1,server2,server3 

Assuming your username and password are accepted by all the servers in the inventory, the public portion of your SSH key will be installed on each of the machines.

You can then test this by performing an Ansible “ping” to validate communication works without the “–ask-pass” option:

ansible all -m ping -i server1,server2,server3

The nice thing about Ansible is that it is easy to debug if you break down the communications and realize that all communications between nodes is using basic SSH. If you’re having communication problems, a simple “ssh servername” from the server you’re running Ansible from will often show if the error is communications based.

If this does not work, review the output that Ansible provides (you might have to add “-vvv” to increase the verbosity level for debugging). In nearly all cases the errors are common SSH error messages, not Ansible error messages. Any modern Unix administrator has probably encountered these errors before and is aware of what the underlying issue is that is breaking the SSH connectivity.