I'm fairly new in using Ansible and have been reading here and google and haven't found an answer yet.
My scenario is that I have 1 user on a server but 2-3 different pub keys that need to put in it's authorized_keys file.
I can successfully remove all keys, or add all keys with this script:
---
- hosts: all
tasks:
- name: update SSH keys
authorized_key:
user: <user>
key: "{{ lookup('file', item) }}"
state: present
#exclusive: yes
with_fileglob:
- ../files/pub_keys/*.pub
With the present
flag it reads and adds all the keys. With the absent
flag it removes all keys listed.
Problem is that I have an old key that is only on the server and I want to remove/overwrite it and for future deployments overwrite any unauthorized keys that might be on the server and not in my playbook.
With the exclusive
flag it only takes the last key and adds it. This would be fantastic if it would loop and recusively add all the keys. If there is a way to do this in Ansible I have not found it.
Is there any way to loop over pub files and use the exclusive
option at the same time?
Is there any way to loop over pub files and use the exclusive option at the same time?
No. There is a note about loops and exclusive in the docs:
exclusive: Whether to remove all other non-specified keys from the authorized_keys file. Multiple keys can be specified in a single key string value by separating them by newlines.
This option is not loop aware, so if you use with_ , it will be exclusive per iteration of the loop, if you want multiple keys in the file you need to pass them all to key in a single batch as mentioned above.
So you need to join all your keys and send all them at once.
Something like this:
- name: update SSH keys
authorized_key:
user: <user>
key: "{{ lookup('pipe','cat ../files/pub_keys/*.pub') }}"
state: present
exclusive: yes
Check this code before running in production!
If you want to avoid the pipe
lookup (e.g., because the path is not relative to the role), you can also use a combination of file
and fileglob
lookups:
- name: update SSH keys
authorized_key:
user: <user>
key: "{% for key in lookup('fileglob', 'pub_keys/*.pub').split(',') %}{{ lookup('file', key) ~ '\n'}}{% endfor %}"
state: present
exclusive: yes
If your keep your users inside a variable you might use this:
---
- hosts: all
vars_files:
- roles/users/vars/main.yml
tasks:
- name: Allow other users to login to the account
authorized_key:
user: user_name
exclusive: yes
key: "{{ developers|map(attribute='publish_ssh_key')|join('\n') }}"
The roles/users/vars/main.yml
looks like this:
---
developers:
- name: user1
publish_ssh_key: ssh-rsa AAAA...
- name: user2
publish_ssh_key: ssh-rsa AAAA...
as I wrote over at this other answer (Ansible - managing multiple SSH keys for multiple users & roles) this is the way that I solved this issue for my use-case. Perhaps it is useful here?
I 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