Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ansible - managing multiple SSH keys for multiple users & roles

The Problem

I am managing a number of different servers with Ansible. Each server has multiple Linux users, such as readonly, admin, etc.

I also have a number of files inside my Ansible project which contain all SSH keys for a particular group of people - eg. AppDevelopersPublicKeys, DbaPublicKeys, etc.

Different groups of people have different access levels on different servers. Eg. on a WebServer, AppDevelopers have admin access, and DBAs might only have read access. On Database servers, vice versa.

To achieve the above, I have different Ansible roles for different types of server (eg. WebAppServer, DatabaseServer, etc). These roles then have variables readonly_key_files and admin_key_files set up against them, listing appropriate key files for the roles which should have readonly and admin access.

The ideal solution would:

  1. Ensure public keys are exlusive - eg. if a public key is deleted from AppDeveloperPublicKeys file in Ansible, servers will have this key deleted too
  2. Only upload / change the file on the servers when something has actually changed
  3. Show an accurate diff of the files when using --diff option to run Ansible

I am using Ansible 2.2.0.0

Solutions attempted so far

None of the below works exactly as I would like:

authorized_key with_file

- authorized_key: user=readonly exclusive=no key={{item}}
  with_file: {{readonly_key_files}}
  • This does not meet requirement 1, as it is looping over multiple files, so exclusive must be set to no

authorized_key with fact

Solution as per https://github.com/ansible/ansible-modules-core/pull/4167/files

- name: "Generate developer keys from multiple files"
  set_fact: dev_key_list="{{ lookup('file', item) }}"
  register: dev_keys
  with_items: '{{developer_key_files}}'

- name: "Merge developer keys into single list"
  set_fact: dev_keys_string={{ dev_keys.results | map(attribute='ansible_facts.dev_key_list') | join('\n') }}

- authorized_key: user=readonly exclusive=yes key={{dev_keys_string}}
  • This meets requirement 1, but (at least for me) does not meet requirement 2 - it seems the order of the keys generated is not deterministic, so running the playbook multiple times results in the authorized_keys step changing even when no keys have been added / removed from files. It also doesn't seem to meet requirement 3, as when I run with --check --diff I cannot see exactly which lines Ansible believes are changing, it just highlights that the file will be changed.

authorized_key with_template

- authorized_key: user=readonly exclusive=no key={{item}}
  with_template: {{readonly_keys.j2}}

Where readonly_keys.j2 looks like:

{% for key_file in readonly_key_files %}
{%   include '/files/' ~ key_file %}
{% endfor %}
  • This meets requirements 1 and 2, but again fails on requirement 3. When I run using --check --diff it only shows me whether or not the SSH file will be changed, not exactly which lines will be added / removed as I would expect it to.

Conclusion

Is there another way to solve this problem? It seems as though there may be an issue with using --diff with authorized_keys in Ansible... The only other approach I can think of is not using authorized_keys at all, and instead managing this as a regular file / template, which should show me accurate diffs (as well as meeting requirements 1 & 2).

like image 940
asibs Avatar asked Nov 08 '16 09:11

asibs


2 Answers

The way I solved this was to pass an array of filenames in a variable to my user-account role. The role then gets the contents of each of these files, appends them together into a newline-separated string, then finally sets this value to be the ssh-key for the new user.

.

The playbook file:

- hosts: aws-node1
  roles:
    - { role: user-account, username: 'developer1', ssh_public_keyfiles: ['peter-sshkey.pub', 'paul-sshkey.pub'] }

.

The role definition for user-account:

- name: add user
  user:
    name: "{{username}}"


- name: lookup ssh pubkeys from keyfiles and create ssh_pubkeys_list
  set_fact:
    ssh_pubkeys_list: "{{ lookup('file', item) }}"
  with_items:
    "{{ssh_public_keyfiles}}"
  register: ssh_pubkeys_results_list


- name: iterate over ssh_pubkeys_list and join into a string
  set_fact:
    ssh_pubkeys_string: "{{ ssh_pubkeys_results_list.results | map(attribute='ansible_facts.ssh_pubkeys_list') | list | join('\n') }}"


- name: update SSH authorized_keys for user {{ username }} with contents of ssh_pubkeys_string
  authorized_key:
    user: "{{ username }}"
    key: "{{ ssh_pubkeys_string }}"
    state: present
    exclusive: yes
like image 102
DrGecko Avatar answered Sep 29 '22 21:09

DrGecko


I found this question in the list of unanswered questions and did a little research. It looks like the diff functionality was added to the authorized_keys module in ansible not long after your question. The commit was merged early in 2017 and appear to have been included in version 2.3 and later. It looks like your third option should work now, but without your key setup, I can't be sure.

https://github.com/ansible/ansible/commit/b0b7a636d8930a2c7192ea83ff890e4313aaf4d9#diff-e50c31b8374987e7c9dd4795d7f2e89f

https://github.com/ansible/ansible/pull/19277

like image 23
Phil M Avatar answered Sep 29 '22 20:09

Phil M