1
votes

How can I combine multiple ssh public keys to use with Ansible's authorized_key module?

I have variables file containing users and keys:

ssh_users:
  - name: peter
    keys:
      - 'ssh-rsa AAAAB3NzaC1yc2EAAA peter@key1'
      - 'ssh-rsa AAAABsgsdfgyc2EAAA peter@key2'
    root: yes

  - name: paul
    keys:
      - 'ssh-rsa AAAAB3Nzaafac2EAAA paul@key1'
    root: no

I'd like to go over this list, pick out users (and their keys) which have 'root: yes' and combine them to update root user's authorized_keys file.

This doesn't work:

- name: lookup keys
  set_fact:
    keylist: "{{ item.keys }}"
  with_items: "{{ ssh_users }}"
  when: item.root == true
  register: result

 - name: make a list
   set_fact:
     splitlist: "{{ result.results | 
  selectattr('ansible_facts','defined') | map(attribute='ansible_facts.keylist') | list | join('\n') }}"

 - name: update SSH authorized_keys
   authorized_key:
     user: root
     key: "{{ splitlist }}"
     state: present
     exclusive: yes
2

2 Answers

1
votes

You can get what you want using the Jinja selectattr and map filters, like this:

---
- hosts: localhost
  gather_facts: false

  vars:
    # Here's our data: two users with 'root' access,
    # one without. We expect to see three public keys in
    # the resulting authorized_keys file.
    #
    # Note that I've renamed the "keys" key to "pubkeys", because
    # otherwise it conflicts with the "keys" method of dictionary
    # objects (leading to errors when you try to access something
    # like item.keys).
    ssh_users:
      - name: alice
        pubkeys:
          - 'ssh-rsa alice-key-1 alice@key1'
        root: true

      - name: peter
        pubkeys:
          - 'ssh-rsa peter-key-1 peter@key1'
          - 'ssh-rsa peter-key-2 peter@key2'
        root: true

      - name: paul
        pubkeys:
          - 'ssh-rsa paul-key-1 paul@key1'
        root: false

  tasks:
    - become: true
      authorized_key:
        user: root
        key: "{{ '\n'.join(ssh_users|selectattr('root')|map(attribute='pubkeys')|flatten) }}"
        state: present
        exclusive: true

In the authorized_key task, we first use the selectattr filter to extract those users with root access. We pass that to the map filter to extract just the pubkeys attribute, which would give us two lists (one with one key, the other with two keys). Finally, we pass that to the flatten filter to create a single list, and then join the resulting keys with newlines to match the input format expected by the authorized_key module. The resulting .ssh/authorized_keys file looks like:

ssh-rsa alice-key-1 alice@key1
ssh-rsa peter-key-1 peter@key1
ssh-rsa peter-key-2 peter@key2
0
votes

Is this the code you're looking for?

- name: update SSH authorized_keys
  authorized_key:
    user: root
    key: "{{ item.1 }}"
  loop: "{{ ssh_users | subelements('keys', skip_missing=True) }}"
  when: item.0.root

You do not need parameters exclusive and state. Defaults exclusive: no and state: present are OK, I think.

Keys, where root: False, can be removed

- name: remove SSH authorized_keys
  authorized_key:
    state: absent
    user: root
    key: "{{ item.1 }}"
  loop: "{{ ssh_users | subelements('keys', skip_missing=True) }}"
  when: not item.0.root

To add and remove keys in one task ternary filter might be used

- name: Preen SSH authorized_keys
  authorized_key:
    state: "{{ item.0.root | ternary('present','absent') }}"
    user: root
    key: "{{ item.1 }}"
  loop: "{{ ssh_users | subelements('keys', skip_missing=True) }}"