Creating a certificate authority in Ansible

Posted on Thu 26 March 2020 in ansible

As part of my continuing work on Cluster in the Cloud, I had a situation where I needed to create a TLS certificate for a non-poblic host. These days for anything internet-facing with a DNS name I would normally just use Let's Encrypt but this was an internal host on a cloud platform.

It's possible to use ACME I suppose to create a local CA for issuing certificates or use something like DogTag but I only needed to create a single host certificate for a single service and so I wanted a very simple solution. Lucking Ansible comes with support for generating OpenSSL certificates.

There were two main steps that needed to be performed:

  1. Create a root certificate authority (CA) certificate. This must be self-signed as no-one else is going to sign it for me.
  2. Create a host certificate, signed by the CA root certificate.

The first step has been supported for a while in Ansible using openssl_certificate and its selfsigned provider. The second step however only became easy to do in Ansible 2.7 (release October 2018) when the ownca provider was added.

We start by creating a key, a certificate signing request (CSR) and then self-signing it using the selfsigned provider:

- name: create CA key
  openssl_privatekey:
    path: /root/CA_key.pem
  register: ca_key

- name: create the CA CSR
  openssl_csr:
    path: /root/CA.csr
    privatekey_path: "{{ ca_key.filename }}"
    common_name: "my-ca"
  register: ca_csr

- name: sign the CA CSR
  openssl_certificate:
    path: /root/CA.crt
    csr_path: "{{ ca_csr.filename }}"
    privatekey_path: "{{ ca_key.filename }}"
    provider: selfsigned
  register: ca_crt

This has created the CA root certificate (the path to which is now stored in ca_crt.filename) and the key which can be used with it to sign other certificates (at ca_key.filename). We can then go ahead and generate a key and CSR for the host:

- name: create host CSR signing key
  openssl_privatekey:
    path: /root/example_com_host_key.pem
  register: example_com_key

- name: create the CSR for the LDAP server
  openssl_csr:
    path: /root/example_com.csr
    privatekey_path: "{{ example_com_key.filename }}"
    common_name: example.com
    subject_alt_name: 'DNS:www.example.com'
  register: example_com_csr

Finally, we can now sign that CSR with the CA certificate and key using the ownca provider:

- name: sign the CSR for the LDAP server
  openssl_certificate:
    path: /root/example_com.crt
    csr_path: "{{ example_com_csr.filename }}"
    provider: ownca
    ownca_path: "{{ ca_crt.filename }}"
    ownca_privatekey_path: "{{ ca_key.filename }}"
  register: example_com_crt

The CA certificate can then be placed on the other hosts in my system and be used to authenticate that they are indeed talking to example.com.

In my case I also then had to convert this into a PKCS #12 file for use with NSS and the 389 Directory Server. To use this feature, you must make sure that pyOpenSSL is installed:

- name: Generate PKCS #12 file
  openssl_pkcs12:
    action: export
    path: /root/example_com.p12
    friendly_name: example.com
    privatekey_path: "{{ example_com_key.filename }}"
    certificate_path: "{{ example_com_crt.filename }}"
    other_certificates: "{{ example_com_crt.ca_cert }}"
    state: present
  register: example_com_crt_p12