ACME Accounts

To work with ACME servers, you need an account. For an account, you always need a private key. We always assume it is stored at a place where the acme_certificate_acme_account variable points to, or it is provided with the acme_certificate_acme_account_content variable (more information on general parameters). The next subsections describe how to create or convert one.

For some ACME servers such as the ones by Let’s Encrypt and Buypass, an account can be created on-the-fly while using for example the felixfontein.acme.acme_certificate role. The following options are of interest: - acme_certificate_acme_email: Your email address which shall be associated to the ACME account. - acme_certificate_terms_agreed: Whether the terms of services are accepted or not. Default value is false, usually needs to be set explicitly to true to allow creating an ACME account. This is only used for ACME v2. - acme_certificate_modify_account: Whether the ACME account should be created (if it doesn’t exist) and the contact data (email address) should be updated. Default value is true. Set to false if you want to use the community.crypto.acme_account module to manage your ACME account manually, and prevent accidental modification of the contact information.

The following shows how to create an account manually with two contact email addresses, which is not possible when using the role:

- name: Create ACME account
  community.crypto.acme_account:
    acme_directory: "{{ acme_certificate_acme_directory }}"
    acme_version: "{{ acme_certificate_acme_version }}"
    account_key: "{{ acme_certificate_acme_account | default(omit) }}"
    account_key_content: "{{ acme_certificate_acme_account_content | default(omit) }}"
    state: present
    contact:
    - "mailto:me@example.com"
    - "mailto:me@example.org"
    terms_agreed: yes

For ACME servers that need External Account Binding, for example for ZeroSSL or Sectigo, you must always use community.crypto.acme_account module to set up the account manually. This can look for example like this:

- acme_account:
    acme_directory: "{{ acme_certificate_acme_directory }}"
    acme_version: "{{ acme_certificate_acme_version }}"
    account_key: "{{ acme_certificate_acme_account | default(omit) }}"
    account_key_content: "{{ acme_certificate_acme_account_content | default(omit) }}"
    state: present
    contact:
    - "mailto:me@example.com"
    - "mailto:me@example.org"
    terms_agreed: true
    external_account_binding:
      kid: abcdef0123456789abcdef
      alg: HS256
      key: aBzFf13298sadsfalkj345nnsfaflkj5lkj245lkj324lkjlkjasflklkjatlkj354lkj43lkj54

The values for kid and key will be provided by the ACME server operator, for example in the ZeroSSL account interface. The value for alg is usually HS256 if not explicitly mentioned.

Account key setup

You can create an account key using the openssl binary as follows:

# RSA 4096 bit key
openssl genrsa 4096 -out keys/acme-account.key
# ECC 256 bit key (P-256)
openssl ecparam -name prime256v1 -genkey -out keys/acme-account.key
# ECC 384 bit key (P-384)
openssl ecparam -name secp384r1 -genkey -out keys/acme-account.key

With Ansible, you can use the community.crypto.openssl_privatekey module as follows:

- name: Generate RSA 4096 key
  community.crypto.openssl_privatekey:
    path: keys/acme-account.key
    type: RSA
    size: 4096
- name: Generate ECC 256 bit key (P-256)
  community.crypto.openssl_privatekey:
    path: keys/acme-account.key
    type: ECC
    curve: secp256r1
- name: Generate ECC 384 bit key (P-384)
  community.crypto.openssl_privatekey:
    path: keys/acme-account.key
    type: ECC
    curve: secp384r1

Make sure you store the account key safely. As opposed to certificate private keys, there is no need to regenerate it frequently, and it makes recovation of certificates issued with it very simple if you no longer have the certificate’s private key.

Account key setup with sops-encrypted account key

For this, you need Mozilla sops installed and a .sops.yaml file present in the key directory, or somewhere up the directory hierarchy.

With Ansible, you can use the community.crypto.openssl_privatekey module as follows:

- block:
    - name: Generate RSA 4096 key
      community.crypto.openssl_privatekey_pipe:
        type: RSA
        size: 4096
      register: account_key_data

    - community.sops.sops_encrypt:
        path: keys/acme-account.key.sops
        content_text: "{{ account_key_data.privatekey }}"

  always:
    # Make sure to wipe the account_key_data variable
    - set_fact:
        account_key_data: ''

Account key conversion

Note that the Ansible ACME modules expect the Let’s Encrypt account key to be in PEM format and not in JWK format, which is used by the official Let’s Encrypt client Certbot. If you have created an account key with the official client and now want to use this key with this ansible role, you have to convert it. One tool which can do this is pem-jwk.