0

I'm trying to distribute certificates to its corresponding hosts (I'll just give the example for the private key task):

- name: create certificate private key
  community.crypto.openssl_privatekey:
    path: "/root/client/{{ item }}.key"
    type: Ed25519
    backup: yes
    return_content: yes
  register: privatekey
  loop: "{{ ansible_play_hosts_all }}"
  when: "'prometheus1' in inventory_hostname"

I can invoke the variable for other hosts like this:

{{ hostvars['prometheus1']['privatekey']['results'][0]['privatekey'] }}

The index points to a certain key, so 0 is going to be the first host (prometheus1), 1 the second one and so on.

I suppose templating would be the way to go, but I simply don't know how to compose the template. I think ansible_play_hosts_all is key to the solution, in that its index corresponds to the private key's index, so for instance: ansible_play_hosts_all[2] --> hostvars['prometheus1']['privatekey']['results'][2]['privatekey']

But the logic would be:

for i in index of ansible_play_hosts_all
add the  hostvars['prometheus1']['privatekey']['results'][i]['privatekey']
if ansible_play_hosts_all[i] in inventory_hostname

Something to that effect, I suppose :) Any help is greatly appreciated.


Update

Maybe something slightly more accurate:

{% for i in ansible_play_hosts_all|length) %}
{{ hostvars['prometheus1']['privatekey']['results'][i]['privatekey'] }}
{% endfor %}

and to it add the condition:

{% if ansible_play_hosts_all[i] in inventory_hostname %}
Vladimir Botka
  • 3,791
  • 6
  • 17
Lethargos
  • 396
  • 1
  • 4
  • 16
  • maybe something slightly more accurate: ``{% for i in ansible_play_hosts_all|length) %}`` ``{{ hostvars['prometheus1']['privatekey']['results'][i]['privatekey'] }}`` ``{% endfor %}`` and to it add the condition: ``{% if ansible_play_hosts_all[i] in inventory_hostname %}}`` – Lethargos Mar 27 '21 at 19:05
  • 2
    Don't put the code into the comment. Edit the question and make it [mcve](https://stackoverflow.com/help/minimal-reproducible-example). What is the expected result? – Vladimir Botka Mar 28 '21 at 05:02
  • Thank you for the tip. I'll know next time. In the meantime I've already posted an answer. – Lethargos Mar 29 '21 at 00:46
  • I see. But, I don't understand why you want to create all private keys in one host (prometheus1) and transfer them to other hosts one by one? It's much safer to let the hosts create the private keys on their own. – Vladimir Botka Mar 29 '21 at 03:31
  • Because I also have to generate the CSR based on the private keys, and then I sign the certificates with the help of the CA. I follow your logic, sure, and I thought of that, but it seems much easier to do it on the CA's side and then copy everything that I need to the other hosts - and then delete anything that I don't need anymore. If I distribute the private keys to their respective hosts from the very beginning, how will I be able to relatively easily sign the certificates with the CA, while also keeping it automated? One run should deal with all hosts. – Lethargos Mar 29 '21 at 07:07
  • The best practice is to create the private key and CSR on the host. Put CSR to CA and get CRT back. Don't give the private key away! e.g. https://docs.debops.org/en/master/ansible/roles/pki/internal-ca.html – Vladimir Botka Mar 29 '21 at 08:21
  • I did come across it. Unfortunately I just find it too complicated at this point. I hope I'll be able to learn from it at some point in the future. – Lethargos Mar 29 '21 at 09:25
  • OK, in this case, I've added an answer on how to simplify this. – Vladimir Botka Mar 29 '21 at 09:55

3 Answers3

1

It would be simpler to use delegation to create the keys on prometheus1 while leaving the registered variable associated with the correct host. Then you could just use privatekey.privatekey in your template.

- name: create certificate private key
  community.crypto.openssl_privatekey:
    path: /root/client/{{ inventory_hostname }}.key
    type: Ed25519
    backup: yes
    return_content: yes
  register: privatekey
  delegate_to: prometheus1

If you really want to stick to your current structure, you can find the list element corresponding to the current host by checking the item value in the result, which contains the item from that iteration of the loop.

{{ (hostvars['prometheus1']['privatekey']['results'] | selectattr('item', '==', inventory_hostname) | list | first).privatekey }}
flowerysong
  • 806
  • 3
  • 6
  • So you mean, by using ``delegate`` I won't need to use ``hostvars`` anymore, and I will simply be able to invoke the variable as such, just like it works within the scope of ``prometheus1``, right? – Lethargos Mar 29 '21 at 00:46
1

Create a dictionary with the data, e.g. given the inventory

shell> cat hosts
prometheus1 ansible_host=localhost
test_11     ansible_host=10.1.0.61 ansible_user=admin 
test_12     ansible_host=10.1.0.62 ansible_user=admin
test_13     ansible_host=10.1.0.63 ansible_user=admin

the playbook

- hosts: all
  gather_facts: false
  tasks:
    - community.crypto.openssl_privatekey:
        path: "{{ playbook_dir }}/client/{{ item }}.key"
        type: Ed25519
        backup: yes
        return_content: yes
      register: privatekey
      loop: "{{ ansible_play_hosts_all|difference(['prometheus1']) }}"
      delegate_to: prometheus1
      run_once: true

    - set_fact:
        host_priv: "{{ privatekey.results|
                       json_query('[].{host: item,
                                       priv: privatekey}')|
                       items2dict(key_name='host', value_name='priv') }}"
      run_once: true

generates the private keys for each host, except prometheus1, stores the keys in the files, and, from the registered data, creates a dictionary of the hosts and their keys

shell> tree client/
client/
├── test_11.key
├── test_12.key
└── test_13.key
ok: [prometheus1] => 
  host_priv:
    test_11: |-
      -----BEGIN PRIVATE KEY-----
      MC4CAQAwBQYDK2VwBCIEIM7/BtpiM1EZxrrwtuE2VdSdr++3J/yxm/BnabnMqL3e
      -----END PRIVATE KEY-----
    test_12: |-
      -----BEGIN PRIVATE KEY-----
      MC4CAQAwBQYDK2VwBCIEIPr8VV2RDOggNxo6vpBiXjSTzclJHFHaTVSxlFFVKoU1
      -----END PRIVATE KEY-----
    test_13: |-
      -----BEGIN PRIVATE KEY-----
      MC4CAQAwBQYDK2VwBCIEIJjp2knmccffeEGvTNaP2f+ijXkeLmu89cGgkqFi771/
      -----END PRIVATE KEY-----

Then, you can proceed in the play and copy the keys to the hosts, e.g.

    - name: copy private key
      copy:
        content: "{{ host_priv[inventory_hostname] }}"
        dest: /tmp/private.key
      when: inventory_hostname != 'prometheus1'
      become: true

Fit the paths to your needs and set the ownership and permissions as well.

Vladimir Botka
  • 3,791
  • 6
  • 17
0

I was finally able to come up with the solution:

{%  for i in range(ansible_play_hosts_all|length) %}
{% if ansible_play_hosts_all[i] in inventory_hostname -%}
{{ hostvars['prometheus1']['privatekey']['results'][i]['privatekey'] }}
{%- endif %}
{% endfor %}

This works as expected. I am looping through the length of ansible_play_hosts_all (to know the number of hosts on which the role is running) and inside the loop I'm adding the condition that the index needs to correspond to the hostname in the inventory.

the -% and %- in the if conditional strips the output of white spaces. Without them an empty line is added for every file (which doesn't seem to bother openssl in this instance, still it's better without it, of course)

The template task itself is as follows:

- name: copy certificate through templating
  template:
    src: certs.j2
    dest: "/root/{{ inventory_hostname }}.key"
Lethargos
  • 396
  • 1
  • 4
  • 16