18

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?

Community
  • 1
  • 1
Valien
  • 1,125
  • 2
  • 17
  • 38

6 Answers6

20

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!

Konstantin Suvorov
  • 65,183
  • 9
  • 162
  • 193
  • Oh man, you're a lifesaver! Works great! I just discovered the `lookup` stuff but was trying to make sense of it. Thank you very much! – Valien Aug 10 '16 at 18:19
  • You need to be careful and put EOL at the end of each pub key file. Otherwise, keys might be concatenated in a single line. – dzieciou May 31 '21 at 11:12
10

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
morxa
  • 3,221
  • 3
  • 27
  • 43
  • 6
    Even better when you use "query" than "lookup" - this way you avoid need for "split" and get protected against not existing paths: `key: "{% for key in query('fileglob', 'pub_keys/*') %}{{ lookup('file', key) ~ '\n'}}{% endfor %}"` – keypress Jun 14 '18 at 18:19
5

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...
czerasz
  • 13,682
  • 9
  • 53
  • 63
  • This only adds the last key, but according to the docs, it should work "Multiple keys can be specified in a single key string value by separating them by newlines" http://docs.ansible.com/ansible/authorized_key_module.html Do you know why it's broken? – goetz Jul 01 '17 at 16:19
  • @goetzc I don't know :-( – czerasz Jul 02 '17 at 23:19
  • Hey, it was that I had test keys which where identical, just with a different comment, and then Ansible only added the last one :) – goetz Jul 03 '17 at 18:43
1

I created simple playbook where I needed specific path for icewarp users.

---

- name: configure icewarp user with proper keys and sudo rights
  hosts: all
  remote_user: root
# tasks file for iw_ssh_keys
  tasks:

  - name: Check that the /opt/icewarp/.ssh/authorized_keys exists
    stat:
      path: /opt/icewarp/.ssh/authorized_keys
    register: stat_result

  - name: Create the file, if /opt/icewarp/.ssh/authorized_keys doesnt exist already
    file:
       path: /opt/icewarp/.ssh/authorized_keys
       state: touch
       mode: '0600'
    when: not stat_result.stat.exists

  - name: Set authorized_keys, removing all undefined ones
    authorized_key:
      user: icewarp
      key: '{{ item }}'
      state: present
      path: /opt/icewarp/.ssh/authorized_keys 
      exclusive: True
    with_file:
      - ../public_keys/authorized_keys

  - name: change user shell to bash 
    become: yes
    user:
      name: icewarp
      shell: /bin/bash

  - name: Configuring /etc/sudoers.d/icewarp
    community.general.sudoers:
      name: icewarp
      state: present
      user: icewarp
      commands: /usr/bin/journalctl *, /usr/bin/less /var/log/*, /usr/bin/grep * /var/log*
Delirium
  • 1,277
  • 3
  • 16
  • 27
0

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
DrGecko
  • 707
  • 7
  • 16
0

Same basic concept as the previous answers but only using variables and pulling the keys from a url like github or gitlab.

#Vars
ssh_users:
  - user1
  - user2
ssh_key_urls: "{{ ssh_users | map('regex_replace', '^(.*)$', 'https://key_server/\\1.keys') | list }}"
authorized_keys: "{% for u in ssh_key_urls %}{{ lookup('url', u, split_lines=False) }}\n{% endfor %}"

#task
- name: setup authorized_keys
  authorized_key:
    key: "{{ authorized_keys }}"
    state: present
    exclusive: true
Joshua
  • 2,079
  • 20
  • 29