TLS certificates: Jarl obtains and renews them over ACME

Jarl is now an ACME client (lego's core, no DNS providers). It answers
DNS-01 challenges in a zone it serves, or through Porkbun's DNS API while
Porkbun serves the domain; "auto" decides from the domain's public name
servers at every renewal, and a CNAME on _acme-challenge is followed.
Certificates are used for jarld's HTTPS (served without a restart; each
port speaks HTTPS and plain HTTP, plain HTTP from loopback and trusted
proxies still served) and/or written for named's DNS over TLS/HTTPS, with
rndc reconfig on renewal. Renewal follows the CA's ARI window or two
thirds of the lifetime, with back-off. Certificates you renew yourself
can be used too. Certificates page and dashboard card, jarl cert,
/api/v1/certificates, Prometheus gauges, docs/certificates.md, and an
e2e section against Pebble.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adamandClaude Opus 5.5 committed 2026-10-12 14:35:53 +01:00
1 parent 88965096bb
commit 88f63ffb0a
43 files changed
+4584 -45

No files matched your search

+33 -7
View File
@@ -20,7 +20,7 @@ The CLI has its own small configuration file, described in [cli.md](cli.md).
|-----|---------|---------|
| `listen` | `["127.0.0.1:8053"]` | Addresses to listen on (`host:port`, several allowed). Keep these on localhost and management networks. Ports below 1024 (`"192.168.10.2:443"`) work because `jarl.service` gives jarld the `CAP_NET_BIND_SERVICE` capability, and only that ([HTTPS on port 443](#example-https-on-port-443)). |
| `socket` | `/run/jarl/jarl.sock` | A local unix socket for the API. Requests on it from root or jarld's own user need no token and have administrator rights; anyone else must send a token ([permissions.md](permissions.md#the-local-socket)). The `jarl` CLI uses it when no server is configured. At most 107 bytes long; `""` turns it off. If it can't be created, jarld logs a warning and serves the network addresses only. |
| `tls.cert_file`, `tls.key_file` | (empty: plain HTTP) | PEM certificate chain and key. Both or neither. The files are rechecked every 10 minutes and on SIGHUP (`systemctl reload jarl`), so renewed certificates are used without a restart. |
| `tls.cert_file`, `tls.key_file` | (empty: plain HTTP) | PEM certificate chain and key. Both or neither. The files are rechecked every 10 minutes and on SIGHUP (`systemctl reload jarl`), so renewed certificates are used without a restart. Easier: let Jarl obtain and renew one ([certificates.md](certificates.md), use `web`), which takes over from these. With a certificate, the `listen` ports serve HTTPS and plain HTTP side by side: plain HTTP is still served to this server itself and to `trusted_proxies`; anyone else is sent to HTTPS. |
| `trusted_proxies` | `[]` | Addresses or prefixes of reverse proxies whose `X-Forwarded-For` and `X-Forwarded-Proto` (or `Forwarded: proto=`) headers are believed. Needed for correct client IPs in the audit log and for rate limiting behind a proxy, and for knowing that a proxied request came over HTTPS (the session cookie's Secure flag, `acme.httpreq`). From other addresses these headers are ignored. |
| `session.lifetime` | `12h` | A web session ends this long after login... |
| `session.idle_timeout` | `2h` | ...or after this long without requests. |
@@ -85,6 +85,23 @@ configuration. Changing it takes effect when jarld restarts.
Query counters need `stats.enabled` as well. Changes take effect when jarld
restarts.
### Certificates (ACME account)
The account Jarl obtains certificates with, and where it writes them for
named. See [certificates.md](certificates.md). Changes take effect when
jarld restarts.
| Key | Default | Meaning |
|-----|---------|---------|
| `acme.account.directory` | `letsencrypt` | `letsencrypt`, `letsencrypt-staging` or an ACME directory URL (`https://...`). |
| `acme.account.email` | `""` | Contact address for the CA (optional). |
| `acme.account.agree_tos` | `false` | You accept the CA's terms of service; no account is registered without it. |
| `acme.account.eab_kid`, `acme.account.eab_hmac` | `""` | External Account Binding, for CAs that need it. Both or neither. Keep the file readable only by root and jarld. |
| `acme.resolvers` | `["1.1.1.1:53", "9.9.9.9:53", "8.8.8.8:53"]` | Recursive resolvers (`address:port`) used to see domains as the CA does: their zone, public name servers, and a CNAME on `_acme-challenge`. Not this server: it may answer from an internal view. |
| `acme.ca_bundle` | `""` | PEM file of extra CA certificates to trust for the ACME server (a private CA). |
| `acme.check_propagation` | `true` | Wait until every name server of the zone serves a challenge before the CA is told to look (they are asked directly, on port 53). |
| `certificates.dir` | `<bind.config_dir>/tls` | Where certificates with the `dns` use are written for named: `<dir>/<name>/fullchain.pem` and `privkey.pem`. |
### ACME (lego httpreq endpoint)
An endpoint for lego's `httpreq` DNS provider, so lego and Traefik can
@@ -126,18 +143,27 @@ there (`ReadWritePaths`). If you change them, update both
ReadWritePaths=/srv/bind/jarl /srv/bind/zones
```
### Porkbun (registrar)
### Porkbun (registrar and DNS host)
With an API key, Jarl can show and set a domain's name servers, glue
records and DNSSEC DS records at Porkbun, after checking that the change
won't break the domain ([dnssec.md](dnssec.md#porkbun)). Without a key
(the default) nothing about Porkbun shows up.
With an API key, Jarl can:
- show and set a domain's name servers, glue records and DNSSEC DS records
at Porkbun, after checking that the change won't break the domain
([dnssec.md](dnssec.md#porkbun));
- publish the DNS-01 challenges of certificates it obtains while Porkbun
still serves the domain's DNS ([certificates.md](certificates.md#where-the-challenge-goes)):
it only adds TXT records at `_acme-challenge` names and removes them
again.
Without a key (the default) nothing about Porkbun shows up, and
certificates for domains Porkbun serves can't be obtained (`jarl cert
check` says so).
| Key | Default | Meaning |
|-----|---------|---------|
| `porkbun.api_key` | `""` | The API key (`pk1_...`). Set both or neither. |
| `porkbun.secret_api_key` | `""` | Its secret (`sk1_...`). |
| `porkbun.domains` | `[]` | Only these domains (default: every primary zone the key can reach). |
| `porkbun.domains` | `[]` | Only these domains, for both uses (default: any domain the key can reach). |
The key can move your domain anywhere, so it is only ever read from this
file: it isn't stored in the database or backups, and there is no API to