Files
2026-09-28 17:14:01 +02:00

7.2 KiB

ACME - Automatic HTTPS Certificates

Until there is a custom puppet module to configure ACME via certbot, it is possible to do this with already existing Hiera/Puppet features. Below you find two configuration examples:

  • normal webserver (here nginx)
  • docker service (also nginx)

Note that instead of nginx this can be adapted to any other type of (web)service requiring TLS/SSL certificates and these are the steps needed to configure certbot. Configuration with docker is down below.

Before starting the actual configuration you need to get for your team the "EAB-KID" (account ID) and "EAB-HMAC-KEY" (password) from the Network Team.

Configuration for Webserver/Webservice

  1. Best place the "EAB-KID" and the "EAB-HMAC-KEY" encrypted as variables acme::eab-kid and acme::eab-hmac-key on your top level YAML file so that they are available on all your nodes if needed. Please ensure you encrypt them as string and not as files as newlines which got encrypted will break the script.
  2. Install the required software, at least certbot.
  3. Prepare a renewal script required to roll out new certificates to your (web)server and place it in /etc/letsencrypt/renewal-hooks/post/
  4. Create a service which initializes certbot with all the required configuration. If you want to use more alias domain names, add them at the end of the certbot command as additional --domain parameters. At the end this initial command should also call the renewal-hook script to prepare the (web)server.
  5. Enable the certbot-renew timer which automatically keeps the certificates updated and will call the renewal-hook when required.

Full Hiera example:

# these variables are best configured in the top level YAML file of your Hiera environment
acme::eab-kid: >
  ENC[PKCS7,MIIBmQYJ ...

acme::eab-hmac-key: >
  ENC[PKCS7,MIIBygYJ ...


# Install certbot and nginx packages
base::pkg_group::extra:
  - "nginx"
  - "certbot"

base::package_groups:
  - "extra"

files::files:
  # Post hook to reload nginx after certificate renewal
  /etc/letsencrypt/renewal-hooks/post/nginx.sh:
    mode: "0544"
    content: |
      #!/bin/bash
      systemctl reload nginx
  # Initial setup of certbot
  /usr/local/libexec/certbot-initial:
    mode: "0500"
    content: |
      #!/bin/bash
      # initial certificate order and setup on the first run only
      [ -f /etc/letsencrypt/live/%{::fqdn}/fullchain.pem ] && exit 0
      /usr/bin/certbot certonly --standalone \
          --server https://one.digicert.com/mpki/api/v1/acme/v2/directory \
          --non-interactive --agree-tos --register-unsafely-without-email \
          --cert-name %{::fqdn} --new-key \
          --eab-kid %{lookup('acme::eab-kid')} \
          --eab-hmac-key %{lookup('acme::eab-hmac-key')} \
          --domain %{::fqdn} \
          --domain my-alias.psi.ch \
      && /etc/letsencrypt/renewal-hooks/post/nginx.sh


# Service which sets up certbot
base::services:
  'certbot-initial':
    enable: true
    options:
      Unit:
        After: 'network-online.target'
        Wants: 'network-online.target'
      Install:
        WantedBy: 'multi-user.target'
        RequiredBy: 'multi-user.target'
      Service:
        Type: 'oneshot'
        ExecStart: '/usr/local/libexec/certbot-initial'
        RemainAfterExit: 'true'

# Enable certbot renewal timer
base::timers:
  certbot-renew:
    enable: true

!!! note if you are using RHEL8 then --new-key is not supported yet by certbot.

!!! note if you are using RHEL10 then substitutions like %{::fqdn} have to be replaced by StructuredFacts like %{facts.networking.fqdn}.

Keys managed by certbot will be saved in /etc/letsencrypt/live:

Certificate: /etc/letsencrypt/live/<your-domain>.psi.ch/fullchain.pem Key: /etc/letsencrypt/live/<your-domain>.psi.ch/privkey.pem

Either configure your webserver to use the generated certificates and keys from there, or link them to the location that is expected by your webserver configuration. Your renewal script may also copy the files instead and also change permissions so it fits whatever the (web)server needs.

!!! note Important! Best use fullchain.pem as some tools like curl test might not have the necessary intermediate certificates!

Additional aliases can be added to the config by adding additional --domain <name> options in the /usr/local/libexec/certbot-initial file.

If you want to do changes, e.g. to the aliases, then best revoke the old certificate:

certbot revoke --cert-name $FQDN

or whatever was the original --cert-name. Then adapt the configuration in Hiera and run Puppet. By restarting the certbot-initial.service (or rebooting) it will get a new certificate with the new configuration:

puppet agent -t
systemctl restart certbot-initial.service

Configuration with Docker

There are several images that use certbot integrated into whatever service you are using. (nginx for example) These images are not always maintained by the official project therefore I'd not recommend using them unless you know what you are doing and trust the maintainer.

In this documentation the nginx container is called nginx, please be aware of that when copying the documentation!

To get your certificates inside a container you need to volume mount /etc/letsencrypt/live into your container, where you would expect your certificates to be. Be careful, the certificate and key are in the same folder! Also you need to "manually" reload the webserver using the certificate, when "ordering" a certificate and when its renewed by certbot. Below are the steps needed. The code blocks assume that you configured the cli.ini file with puppet module files:files already.

  nginx:
    image: nginx:latest
    container_name: nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /etc/letsencrypt/archive:/etc/letsencrypt/archive:ro
      - /etc/letsencrypt/live:/etc/letsencrypt/live:ro

And point your virtual host configuration to the correct folder:

server {
    listen 80;
    server_name myhost.psi.ch;
    return 301 https://$host$request_uri;
}
server {
    listen 443 ssl;
    server_name myhost.psi.ch;
    ssl_certificate /etc/letsencrypt/live/myhost.psi.ch/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/myhost.psi.ch/privkey.pem;
    location / {
        proxy_pass https://myapp:443;
    }
}

Getting the certificate with reloading the nginx inside the container, create a file (mine is called get-cert.sh) And execute ./get-cert.sh myhost.psi.ch manually for each certificate you need.

  /root/get-cert.sh
    mode: 0744
    content: |
#!/bin/bash

set -e

# Check for domain argument
if [ -z "$1" ]; then
    echo "Usage: $0 <domain>"
    exit 1
fi

DOMAIN="$1"

# Run Certbot with certonly
certbot certonly \
    -d "$DOMAIN" \
    --config /etc/letsencrypt/cli.ini

# Reload Nginx in the Docker container
docker exec nginx nginx -s reload

And create the post renewal hook:

  /etc/letsencrypt/renewal-hooks/post/nginx.sh:
    mode: '0544'
    content: |
      #!/bin/bash
      docker exec nginx nginx -s reload