Traefik with Mail-in-a-box as the dns-01 Provider

An image of a blue gopher directing traffic to Let's Encrypt, Cloudflare, and Mail-in-a-Box

Problem

I need to replace Cloudflare as my DNS provider. I run Mail-in-a-Box which can serve DNS and has a good API but Traefik can't directly use it.

Background

Let's Encrypt

Let's encrypt provides free TLS certificates by giving you a token and asking you to post it somewhere authoritative that they can then check to verify your identity.

The two main challenge types are http-01 (you essentially make the token available at http://<DOMAIN>/.well-known/acme-challenge/<TOKEN>) and dns-01 (create a TXT record _acme-challenge.<DOMAIN> with <TOKEN> as the value). The http-01 method is preferred since it doesn't require additional administrative access but it's not feasible for sites that are internal-only.

Traefik

Traefik uses the Lego ACME library for interacting with Let's Encrypt. While Lego doesn't have a dedicated MiaB provider it does have one named exec that can run an arbitrary executable. We can use this to call to any DNS API that's not already supported.

Mail-in-a-Box

Mail-in-a-Box is a great option for self-hosting email, calendar, and contacts functionality. It prefers to be configured as the authoritative DNS for each domain it manages since it can seamlessly handle all the DNS entries needed to run smoothly. Creating and deleting DNS entries using the API using curl is easy:

curl -X POST \
    -d "${value}" \
    --user "${MIAB_Username}:${MIAB_Password}" \
    "https://${MIAB_Server}/admin/dns/custom/${fqdn}/txt"

Solution

Make a shim script that Traefik/Lego can call with the exec provider and that then makes the appropriate API call to Mail-in-a-Box. Here's what I came up with. It uses the same environment variables as acme.sh (MIAB_Username, MIAB_Password, and MIAB_Server).

/usr/local/bin/miab-dns.sh

#!/bin/bash

set -eu -o pipefail

# See https://go-acme.github.io/lego/dns/exec/ for details about this wrapper

# Usage: Set MIAB_Username, MIAB_Password, and MIAB_Server environment variables

action="${1}"; shift
# Remove trailing period since traefik always sends it but the MIAB API only
# accepts it for CNAMEs
# shellcheck disable=SC2001
fqdn="$(echo "${1}" | sed 's/\.$//')"; shift
value="${1}"; shift

case "${action}" in
    present)
        # shellcheck disable=SC2154
        curl -X POST \
            -d "${value}" \
            --user "${MIAB_Username}:${MIAB_Password}" \
            "https://${MIAB_Server}/admin/dns/custom/${fqdn}/txt"
    ;;
    cleanup)
        # shellcheck disable=SC2154
        curl -X DELETE \
            -d "${value}" \
            --user "${MIAB_Username}:${MIAB_Password}" \
            "https://${MIAB_Server}/admin/dns/custom/${fqdn}/txt"
    ;;
esac

systemd Example

There are a hundred ways to configure and run Traefik but I'll share a complete systemd solution as one example.

/etc/systemd/system/traefik.service

[Unit]
Description=Traefik Proxy
Documentation=https://doc.traefik.io/traefik/
After=network-online.target

[Service]
User=traefik
Group=traefik
EnvironmentFile=/root/.traefik.env
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
Environment="EXEC_PATH=/usr/local/bin/miab-dns.sh"
ExecStart=/usr/local/bin/traefik \
    --log.level=INFO \
    --entrypoints.web.address=:80 \
    --entrypoints.web.http.redirections.entrypoint.to=websecure \
    --entrypoints.websecure.address=:443 \
    --entrypoints.websecure.http.tls.certresolver=miab \
    --certificatesresolvers.miab.acme.dnschallenge=true \
    --certificatesresolvers.miab.acme.dnschallenge.provider=exec \
    --certificatesresolvers.miab.acme.email=mark@mrkc.net \
    --certificatesresolvers.miab.acme.storage=%S/traefik/acme.json
ConfigurationDirectory=traefik.d
ProtectSystem=true
ProtectHome=true
PrivateMounts=true
StateDirectory=traefik
StateDirectoryMode=0750
PrivateTmp=true
PrivateDevices=true

[Install]
WantedBy=multi-user.target

/root/.traefik.env

Make sure this file is secure (e.g. chmod 0640)!

MIAB_Username=REDACTED
MIAB_Password=REDACTED
MIAB_Server=REDACTED