Ansible - SSH Key Distribution For Password-less SSH

3 minute read

Ansible - SSH Key Distribution For Password-less SSH

When setting up massive scale environments you will likely run into this scenario. How can I distribute a specific user account’s SSH keys for all of my hosts to allow password-less SSH logins between them? I have done this previously by using the following Ansible role which means I have to fetch and keep SSH keys in a GIT repo. But what if I want to do this more simplistic and more dynamic and flexible? That is what I will be showing in this post. The process is actually quite simple and works very well.

First I need to identify the account(s) in which I would like to distribute. So for this example let’s say you have user1 and user2 which have been added on every host in your environment.

We will first define these as variables.

group_vars/all/ssh_key_distribution.yml:

users_ssh_key_distribution:
  - 'user1'
  - 'user2'

Next we need to create a jinja2 template to use as a vars_files definition in our playbook which will contain our SSH Public Keys.

ssh_keys_distribution.yml.j2:

---
_ssh_keys_distribution:
{% for host in play_hosts %}
  - host: '{{ host }}'
    keys:
{%   for _keys in hostvars[host]['_ssh_pub_key']['results'] %}
      - user: '{{ _keys['item'] }}'
        key: '{{ _keys['stdout'] }}'
{%   endfor %}
{% endfor %}

ssh_keys_distribution.yml.j2

NOTE: If you were to look at the template generated file from the above jinja2 template it would look similar to below:

---
_ssh_keys_distribution:
  - host: 'node0'
    keys:
      - user: 'user1'
        key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDANbXQsS+VOhJa4p4vipJneaPXNmfcjU7Hwpgo1lj01Vmv+xKViVf3HLb15JRMS6ozuK0dmHQ9CLFVP8zC3tZe4b5CqIKR4l6HVI1n5MC++NnXQ2pUqaaJeS4Tr2ixsfYwVIYtL+eTrpH4L8Sihn6W7XrHmVelVHncMeI+hnvlWSyFhKybZ2csFG3mgEi+13+OWhgvN2cxVhg0PYMtsa6F+5ByRLarpJMF6Z5vZOlggu4G1cHTRdHAJm7Fx5G0oo2jqxGxuXlbYv8irp0IxZHJ3EhxOCzwKq9IpEot6zzl0szDe2HEXW6QRokyKydzMoCAL0LPq+FctJ+Ar0jhSrzP ansible-generated on node0'
      - user: 'user2'
        key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDXC+vM8Ezsuh742Y8PThAW8eX3+nzd9GW937vNrIjTIb+xPIlz/9Nc6LsZIH7H77BgMuUQSm48s1rfNKytgaj4dX0RLtBsya/92/OdhiGoPOE2vHfC23dF1W8n8ytTWmv/IcDIk9JA411PpmHQ0wZi/pcQelEl+TEYFqBQXtMWb2ambWB8DA6DXVHqnsSpbIIk1s47rCEzQ6VTWAjqqWt0iJ1Uip0kyVh/OCVOdkmklxVQiPsq1fcGbZ/4m3ajDZetCF+4wNt2CHXhJhXjKOkeBaFNWdJB7gjueF31aWhskwmZzSKb4jJ5wXxkjUMcHhYQo5bzM431X8/eyZl8WT5h ansible-generated on node0'
  - host: 'node1'
    keys:
      - user: 'user1'
        key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmFdOfbJMMB2gsZcfTNjKGk+qEYRf67Z+9d8ncIaGe1kh1fJjkGj1dn/D80F46qDlMyAho6pamn6a5jOE4E2MpaC08TBIuZAmP3n3/9PGs7PpqHFiKuuJRRzIrmd7KKWBeZQxu/ikMvSoFAmoL+YXHX7emmgrkZaMUOHpd+F/NiHvVNOGmivAxksJ5EwL5xDbtopOHtWi4D8rKkYZLAF/whwjUXN25GmsdUXgRtOhmbfb/C7A2G59NAk3wfeWTBqI8dAh5IxKULNERmhc5SIVODRX82pfIl+xTm7jYUKevb0JPwzJkNPYFZv522D9dEJd2qel5srJB65XqAjGTutKX ansible-generated on node1'
      - user: 'user2'
        key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDzIrGP4L5LjSurrQb6zK14XQJ8pVEj/FYfVzH0mn8LyzWDCRr8Tfrd5Z32dZIcnhlnmDWbvUDM0UGa0gNtQ8Xjf4HaxoR3q3lxiv0R8IJxFeTKSyk3CzffsfOv4emFDpOzu1RCDftwd4Iv7dElCE5E3/eEI7IEbGkcveqjiCR8QsmXT0qytznqqxaDq//M3qtUuxjq4fSvdsB1EXDO1qhrZ5fR42XoiZLA4X3cj8A0JhR2s0KqXKhqnEqmHZERC87Ci+9g4P0nlZLjdv7ygJ+uppgDBvzHk7U6ljq2DWHQtCmJtlQMda4xJ5W3nZ9ofh9lvGyPg0k1xiDt6VAhl+9t ansible-generated on node1'
  - host: 'node2'
    keys:
      - user: 'user1'
        key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDC4gpaSykcdEKhUMAp5hXe0KBT5TJns1qlU7iYP5KpJtZJkOL0Z3SG+tMD7wfSHEyg+bub6YTPhj4VEN8F+HwXm27gWjtX7bir7cFDm+5e2TTYVx1koewylpH1GLVBVftOegyrAO0Y+kpGDWbvr5uM7hi8T59xE5Sr6FFcLP9tXhuoJjowQ00ipCHReyMp4psxQcd34eIAt8/XY1mBFG3cVdXb0iB8zGs0u0/Nw68MN9mNocmpmMpDN2OJWWSccKhZHzNlI9R8M5rYac+ZMje+GCjeGk/KO9aJZD0iKsza3pbi/U2+Ti/+HKPCBLLQ5YK1kTdvQqjLd6SaeCGnE9aR ansible-generated on node2'
      - user: 'user2'
        key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQqKV0GLCh2vB6qKe4pewyTwenso81xilIpnd1nR0eG/w4XqY4P5xh2KH6nfGcwXrGLdjw5XRW6WE+df3VDDYNONxIavW64IO+P9Fn4OH856felM6Efv/4xI9uIXuSfcq1P2qqEytwpobmijf6zdifbj0k1t1f5g+EU/3nvqcr+yRukGKEZC7YLkyaf6j1Xcd1+FUkgAwqG1y4wuGxdXs9E3ZtPGwQJJxuPIa7cJ0XYMadkwoyEWiJOWxzPpODpX/fVZVysvbf/jW7LFxXi1AObyBOhJEKnoFolfjMSCD5aycO0AOtVcV/sQ09Ic0zqCPuT5YrKYs4iqyFhhdnLSHP ansible-generated on node2'

Now we need to create another jinja2 template which we will use to distribute all of our SSH host keys by hostname and IP address to our remote hosts /etc/ssh/ssh_known_hosts. This will ensure that we do not get prompted to accept the SSH host key when logging in.

ssh-hosts.j2:

{% for item in host_keys_hostname['results'] %}
{%   for key in item['stdout_lines'] %}
{{ key }}
{%   endfor %}
{% endfor %}
{% for item in host_keys_ip['results'] %}
{%   for key in item['stdout_lines'] %}
{{ key }}
{%   endfor %}
{% endfor %}

ssh-hosts.j2

And finally we will create the playbook which will handle this distribution for us.

ssh_key_distribution.yml:

---
- hosts: all
  tasks:
    - name: Scan And Register SSH Host Keys (hostname)
      command: "ssh-keyscan {{ item }}"
      register: "host_keys_hostname"
      changed_when: false
      with_items: "{{ groups['all'] }}"
      delegate_to: localhost
      become: false

    - name: Scan And Register SSH Host Keys (IP)
      command: "ssh-keyscan {{ hostvars[item]['ansible_ssh_host'] }}"
      register: "host_keys_ip"
      changed_when: false
      with_items: "{{ groups['all'] }}"
      delegate_to: localhost
      become: false

    - name: Write SSH Host Keys
      template:
        src: "ssh-hosts.j2"
        dest: "/etc/ssh/ssh_known_hosts"
      become: true

    - name: Capturing SSH Keys
      command: "cat /home/{{ item }}/.ssh/id_rsa.pub"
      register: "_ssh_pub_key"
      become: true
      changed_when: false
      with_items: '{{ users_ssh_key_distribution }}'

    - name: Generating SSH Keys
      template:
        src: "ssh_keys_distribution.yml.j2"
        dest: "ssh_keys_distribution.yml"
      become: false
      run_once: true
      delegate_to: localhost

- hosts: all
  vars_files:
    - ./ssh_keys_distribution.yml
  tasks:
    - name: Adding SSH Keys
      authorized_key:
        user: "{{ item[1]['user'] }}"
        key: "{{ item[1]['key'] }}"
        state: "present"
      become: true
      with_subelements:
        - "{{ _ssh_keys_distribution }}"
        - keys
      when: inventory_hostname != item[0]['host']

Now that we have everything in place we are ready to run this playbook against all of ours hosts in our inventory. And enjoy the benefits of not having to run ssh-copy-id manually on each and every host in our environment.

ansible-playbook -i hosts ssh_keys_distribution.yml

And once the playbook completes you should be good to go.

And you can validate everything works as desired by connecting to one of your hosts and test logging into other hosts using one of the defined user accounts.

vagrant@node0:~$ sudo su - user1
user1@node0:~$ ssh node2
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-66-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
Development Environment
Last login: Wed Aug  2 03:19:50 2017 from 192.168.250.11
$ hostname
node2
$ whoami
user1
$ ssh 192.168.250.11
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-66-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
Development Environment
Last login: Wed Aug  2 03:19:36 2017 from 192.168.250.10
$ hostname
node1
$

And there you have it, a nice and easy way to manage your SSH password-less logins across your enivornment.

Enjoy!

Updated:

Leave a comment