ansiblecertificateacme

ACME certificates in Ansible using InCommon/Sectigo CA


I am really struggling with getting a working Ansible playbook that utilizes the ACME protocol and the InCommon/Sectigo external account binding (EAB) and the "no challenge" option. Lots of websites (including vendors) all say it works, but provide no working examples.

From Ansible documentation:

So far, the ACME modules have only been tested by the developers against Let's Encrypt, Buypass, ZeroSSL, and Pebble testing server. We have got community feedback that they also work with Sectigo ACME Service for InCommon. If you experience problems with another ACME server ...

Here are some pages (I am trying to use/understand):

Is there anyone who has successfully utilized an Ansible playbook, the community.crypto.acme* modules, and successfully interacted with the Sectigo/InCommon ACME service to send a CSR and have a SSL certificate produced?

My current road-block is incorporating the InCommon/Sectigo external account binding (EAB). I can not use “external_account_binding” in the “community.crypto.acme_certificate” calls, it just presents with an error of an invalid parameter given.

 "msg": "Unsupported parameters for (community.crypto.acme_certificate) module: external_account_binding. Supported parameters include: account_email, account_key_content, account_key_passphrase, account_key_src, account_uri, acme_directory, acme_version, agreement, chain_dest, challenge, csr, csr_content, data, deactivate_authzs, dest, force, fullchain_dest, modify_account, remaining_days, request_timeout, retrieve_all_alternates, select_chain, select_crypto_backend, terms_agreed, validate_certs (account_key, cert, chain, fullchain, src).", 

The only module that supports the EAB is “community.crypto.acme_account”.

In that module it refers to the “account_key_content” which is says is the "Content of the ACME account RSA or Elliptic Curve key.” Where do I find that? It is not anywhere in the Sectigo GUI for ACME accounts, and I can find nothing the explains what it is or where to find it or calculate it? In my code below, I just fed it the private key of the certificate I am trying to generate.. but I know that is wrong, and of course it told me as such..

TASK [Get Account URI] *********************************************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "Account does not exist or is deactivated.", "other": {}}

Here is my non-working code:

- name: Get Account URI
  community.crypto.acme_account:
    account_key_content: "{{ cert_privatekey }}"
    acme_directory: "{{ acme_url }}"
    acme_version: "{{ acme_version }}"
    allow_creation: false
    state: present
    external_account_binding:
      alg: HS256
      key: "{{ acme_hmac_key }}"
      kid: "{{ acme_account_id }}"
  register: the_account_uri

# DEBUG: Output the_account_uri?
- name: Output the_account_uri?
  ansible.builtin.debug:
    msg: "{{ the_account_uri }}"


- name: Create a challenge for sample.com using a account key file
  community.crypto.acme_certificate:
    account_key_content: "{{ cert_privatekey }}"
    csr_content: "{{ cert_csr }}"
    account_email: "{{ acme_account_email }}"
    acme_version: "{{ acme_version }}"
    acme_directory: "{{ acme_url }}"
    remaining_days: "{{ cert_term_length }}"
    dest: "/var/tmp/{{ cert_filename }}"
    challenge: no challenge
  register: acme_no_challenge
  no_log: false

# DEBUG: Output challenge?
- name: Output challenge?
  ansible.builtin.debug:
    msg: "{{ acme_no_challenge }}"

- name: Retrieve the cert and intermediate certificate
  community.crypto.acme_certificate:
    account_key_content: "{{ cert_privatekey }}"
    csr_content: "{{ cert_csr }}"
    account_email: "{{ acme_account_email }}"
    acme_version: "{{ acme_version }}"
    challenge: no challenge
    data: "{{ acme_no_challenge }}"
    acme_directory: "{{ acme_url }}"
    remaining_days: "{{ cert_term_length }}"
    dest: "/var/tmp/{{ cert_filename }}"
    chain_dest: "{{ chain_filename }}"
    fullchain_dest: "{{ full_chain_filename }}"
  when: acme_no_challenge is changed
  no_log: false

Solution

  • I have successfully implemented the ACME certificate renewal via Ansible. I was able to figure it out over time and with lots of different people assisting and lots of Google foo.

    Breaking it down: Create a private/public key combination. You first need to call acme_account, and register the private key with the Sectigo EAB credentials.
    With the private key registered, you can now call acme_certificate with the account_key (private key) and all other relevant certificate generation parameters. The certificate is generated, and then proceed to save it or install it using additional playbook plays/statements.

    WORKING CODE:

     - name: Perform Sectigo EAB with private key, get acct URI
          community.crypto.acme_account:
            account_key_content: "{{ acct_privatekey }}"
            acme_directory: "{{ acme_url }}"
            state: present
            terms_agreed: true
            acme_version: "{{ acme_version }}"
            contact:
              - "mailto:{{ cert_group_email }}"
            external_account_binding:
              alg: HS256
              key: "{{ acme_hmac_key }}"
              kid: "{{ acme_account_id }}"
          register: acct_registration
    
        # DEBUG: Output the_account_uri?
        - name: Output the_account_uri?
          ansible.builtin.debug:
            msg: "{{ acct_registration }}"
    
        - name: Setup the account "no challenge" using account private key
          community.crypto.acme_certificate:
            account_key_content: "{{ acct_privatekey }}"
            csr_content: "{{ cert_csr }}"
            account_email: "{{ acme_account_email }}"
            acme_version: "{{ acme_version }}"
            account_uri: "{{ acct_registration.account_uri }}"
            acme_directory: "{{ acme_url }}"
            remaining_days: "{{ cert_term_length }}"
            dest: "/var/tmp/{{ cert_filename }}"
            terms_agreed: true
            validate_certs: true
            challenge: no challenge
          register: acme_no_challenge
          no_log: false
    
        # DEBUG: Output challenge?
        - name: Output challenge?
          ansible.builtin.debug:
            msg: "{{ acme_no_challenge }}"
    
        - name: Retrieve the cert and intermediate certificate
          community.crypto.acme_certificate:
            account_key_content: "{{ acct_privatekey }}"
            csr_content: "{{ cert_csr }}"
            account_email: "{{ acme_account_email }}"
            acme_version: "{{ acme_version }}"
            challenge: no challenge
            data: "{{ acme_no_challenge }}"
            account_uri: "{{ acct_registration.account_uri }}"
            acme_directory: "{{ acme_url }}"
            remaining_days: "{{ cert_term_length }}"
            dest: "/var/tmp/{{ cert_filename }}"
            chain_dest: "{{ chain_filename }}"
            fullchain_dest: "{{ full_chain_filename }}"
          when: acme_no_challenge is changed
          no_log: false