Search code examples
sslansibleansible-inventory

Create a cert with multiple subject alt names with Ansible


I'm using a block like below

- name: Ensure that the existing certificate has a certain domain in its subjectAltName
  openssl_certificate:
    path: /etc/ssl/crt/example.com.crt
    provider: selfsigned
    subject_alt_name:
      - www.example.com
      - test.example.com

To generate a selfsigned cert with Ansible, I'd like to use the ips in my inventory file as subject_alt_names something like

- name: Generate cert
  openssl_certificate:
    path: ssl/mongo-test.crt
    privatekey_path: ssl/mongo-test.pem
    csr_path: ssl/mongo-test.csr
    provider: selfsigned
    subject_alt_name: 
      - IP:{{hostvars[item].ansible_host}}

So that I end up with

- name: Generate cert
  openssl_certificate:
    path: ssl/mongo-test.crt
    privatekey_path: ssl/mongo-test.pem
    csr_path: ssl/mongo-test.csr
    provider: selfsigned
    subject_alt_name: 
      - IP:10.136.31.37
      - IP:10.136.29.52
      - IP:10.136.30.53

How do I get all my inventory ips to come under the subject_alt_name list?

I've tried using with_items but that creates a new cert per ip address and each iteration overwrites the last.


Solution

  • I know that I am not answering to your question directly but I had the same problem and I chose another approach, hopping it could apply to you too.

    I created an openssl.conf file that is templated with Jinja:

    [ req ]
    prompt = no
    distinguished_name = req_distinguished_name
    {% if letsencrypt_sans_domains[item] is defined and letsencrypt_sans_domains[item] | length > 0 %}
    req_extensions     = req_ext
    {% endif %}
    
    string_mask = utf8only
    default_md = sha256
    
    [ req_distinguished_name ]
    O=Organization
    L=Boston
    ST=Massachusetts
    C=US
    CN={{ item }}
    
    {% if letsencrypt_sans_domains[item] is defined and letsencrypt_sans_domains[item] | length > 0 %}
    [ req_ext ]
    subjectAltName          = @alt_names
    
    [alt_names]
    DNS.1 = {{ item }}
    {% set i = 2 %}
    {% for domain in letsencrypt_sans_domains[item] %}
    DNS.{{ i }} = {{ domain }}
    {% set i = i + 1 %}
    {% endfor %}
    {% endif %}
    

    Then I deploy the file using template module and call:

    - name: "Generate CSR"
      command: "openssl req -config openssl_req_{{ item }}.conf -nodes -new -newkey rsa:4096 -out {{ item }}.csr -keyout {{ item }}.key"
      with_items: "{{ letsencrypt_domains | default([]) }}"
    

    The variables letsencrypt_sans_domains and letsencrypt_domains point to:

    letsencrypt_domains: [
      "a.b.com"
    ],
    letsencrypt_sans_domains: {
      "a.b.com": [ "b.b.com", "c.b.com", "d.b.com" ]
    }
    

    }

    Of course if letsencrypt is your use case, you'll need to answer the challenge for all SANs domain