Distribute ssh public keys among hosts

I'm setting up some machines with Ansible and need to enable password less connections between them. I've got a database master and several slaves. For initial replication the slaves need to ssh into the master and get a copy of the database. I'm not sure what is the best way to dynamically add all the slaves public keys to the masters authorized_keys file.

I already thought about providing the slaves public keys as variables and then add them via the authorized_key module. But then I must maintain the list of keys. I'm looking for an approach where I just add another host the the slaves group and the rest will work automatically.

Any ideas?

Update:

So far I got the following pseudo code:

# collect public keys from slave machines
- name: collect slave keys
  {% for host in groups['databases_slave'] %}
     shell: /bin/cat /var/lib/postgresql/.ssh/id_rsa.pub
     register: slave_keys #how to add to an array here?
  {% endfor %}

# Tasks for PostgreSQL master
- name: add slave public key
  sudo: yes
  authorized_key: user=postgres state=present key={{ item }}
  with_items: slave_keys

The loop with the {% %} only works in template files and not in playbooks directly. Any way to do this in my playbook?


Solution 1:

I've come up with a solution which works for me. I do create the public/private keys on my machine from where Ansible is run and on the first connection I put the keys in place.

Then I do add the keys from all the slaves to the master with the following:

# Tasks for PostgreSQL master
- name: add slave public key
  sudo: yes
  authorized_key: user=postgres state=present key="{{ lookup('file', '../../../keys/' + item + '/id_rsa.pub') }}"
  with_items: groups.databases_slave

The whole playbook can be found on github.com/soupdiver/ansible-cluster.

Solution 2:

I believe the following solution should work in your case. I've been using it for a similar scenario with a central backup server and multiple backup clients.

I have a role (let's say "db_replication_master") associated to the server receiving the connections:

    - role: db_replication_master
      db_slaves: ['someserver', 'someotherserver']
      db_slave_user: 'someuser' # in case you have different users
      db_master_user: 'someotheruser'
      extra_pubkeys: ['files/id_rsa.pub'] # other keys that need access to master

Then we create the actual tasks in the db_replication_master role:

    - name: create remote accounts ssh keys
      user:
        name: "{{ db_slave_user }}"
        generate_ssh_key: yes
      delegate_to: "{{ item }}"
      with_items: db_slaves

    - name: fetch pubkeys from remote users
      fetch:
        dest: "tmp/db_replication_role/{{ item }}.pub"
        src: "~{{db_slave_user}}/.ssh/id_rsa.pub"
        flat: yes
      delegate_to: "{{ item }}"
      with_items: db_slaves
      register: remote_pubkeys
      changed_when: false # we remove them in "remove temp local pubkey copies" below

    - name: add pubkeys to master server
      authorized_key:
        user: "{{ db_master_user }}"
        key: "{{ lookup('file', item) }}"
      with_flattened:
        - extra_pubkeys
        - "{{ remote_pubkeys.results | default({}) | map(attribute='dest') | list }}"

    - name: remove temp local pubkey copies
      local_action: file dest="tmp/db_replication_role" state=absent
      changed_when: false

So we're basically:

  • dynamically creating ssh-keys on those slaves that still don't have them
  • then we're using delegate_to to run the fetch module on the slaves and fetch their ssh pubkeys to the host running ansible, also saving the result of this operation in a variable so we can access the actual list of fetched files
  • after that we proceed to normally push the fetched ssh pubkeys (plus any extra pubkeys provided) to the master node with the authorized_keys module (we use a couple of jinja2 filters to dig out the filepaths from the variable in the task above)
  • finally we remove the pubkey files locally cached at the host running ansible

The limitation of having the same user on all hosts can probably be worked around, but from what I get from your question, that's probably not an issue for you (it's slighly more relevant for my backup scenario). You could of course also make the key type (rsa, dsa, ecdsa, etc) configurable.

Update: oops, I'd originally written using terminology specific to my problem, not yours! Should make more sense now.