cert get: combined file for HAProxy, --local, saved NAME; cert list shows get and last download

- jarl cert get --combined PATH writes the key and full chain in one file
  (mode 600, atomic), kept outside --dir so it can live in HAProxy's crt
  directory; JARL_CERT_COMBINED for the hook. Turning it on, moving it or
  removing it ('') applies from the files on disk and runs the hook.
- jarl cert get --local writes the combined file and runs the hook from
  this host's files, without asking jarld.
- jarl cert saved NAME shows all of one certificate's settings.
- jarl cert list: USES "get" for certificates for other hosts, and a
  PULLED column (last download, "(old)" while a host is behind). Owners
  now see their own downloads (pulled_by), not only administrators.
- jarl cert get without a name refuses per-certificate flags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adamandClaude Opus 5.5 committed 2026-10-25 00:24:33 +01:00
1 parent 3fb159cf4d
commit 42e79af678
9 files changed
+578 -76

No files matched your search

+67 -13
View File
@@ -274,6 +274,7 @@ export JARL_SERVER=https://jarl.adbraun.lol:8053 JARL_TOKEN=@/root/jarl.token
jarl cert get mail -d mail.adbraun.lol -d smtp.adbraun.lol --hook 'systemctl reload postfix dovecot'
sudo jarl cert schedule # a daily systemd timer runs 'jarl cert get'
jarl cert saved # what this host has, and when it expires
jarl cert saved mail # all of mail's settings
```
* **Asking again** with the same names returns the certificate jarld has:
@@ -286,12 +287,26 @@ jarl cert saved # what this host has, and when it exp
expiry, when it was fetched). Each file is replaced at once, never
half written. The key is checked against the certificate before
anything is written.
* **Saved settings**: the names, `--dir`, `--hook`, the server and the
token are saved in `/etc/jarl/cert-get/NAME.json` (as root;
`~/.config/jarl/cert-get` otherwise), readable only by their owner.
`jarl cert get` without a name does every saved certificate. A token
given as `@FILE` is saved as that reference, not copied. `jarl cert
forget NAME` stops getting one (`--delete` deletes it on jarld too).
* **A combined file** (`--combined PATH`): the key, then the full chain,
in one file (mode 600), as HAProxy wants. See [HAProxy](#haproxy).
* **Saved settings**: the names, `--dir`, `--combined`, `--hook`, the
server and the token are saved in `/etc/jarl/cert-get/NAME.json` (as
root; `~/.config/jarl/cert-get` otherwise), readable only by their
owner. `jarl cert get` without a name does every saved certificate. A
token given as `@FILE` is saved as that reference, not copied. `jarl
cert saved NAME` shows them all (the token never). `jarl cert forget
NAME` stops getting one (`--delete` deletes it on jarld too).
* **Changing a setting**: run `jarl cert get NAME` with just that option
(`--hook '...'`, `--combined PATH`, `--dir DIR`); the others stay.
`--hook ''` removes the hook, `--combined ''` stops writing the
combined file and deletes it. The run still asks jarld for the
certificate, but downloads nothing while this host has the current one.
A new or moved combined file is written from the files this host
already has, and the hook runs.
* **`--local`** doesn't ask jarld at all: it writes the combined file and
runs the hook from the files this host has. Use it to apply a changed
setting while jarld is unreachable, or to test a hook. New names still
need jarld.
* **Several Jarl servers on one host**: each server has its own
certificate names, so two servers may both have a `proxy`. The host
keeps one `proxy` per settings directory, though: asking another server
@@ -299,12 +314,14 @@ jarl cert saved # what this host has, and when it exp
holding another certificate's files. Choose another name or `--dir`, or
pass `--replace` to take the name (or directory) over. A `--dir` that
another saved certificate uses is always refused.
* **The hook** runs with `sh` after new files are written, with
`JARL_CERT_NAME`, `JARL_CERT_DIR`, `JARL_CERT_CERT`, `JARL_CERT_CHAIN`,
`JARL_CERT_FULLCHAIN`, `JARL_CERT_KEY`, `JARL_CERT_DOMAINS`,
`JARL_CERT_SERIAL`, `JARL_CERT_NOT_AFTER` and `JARL_CERT_RENEWED` (1 when
it replaced an older certificate). If it fails, `jarl cert get` exits
non-zero and runs it again next time. It has 5 minutes.
* **The hook** runs with `sh` after new files are written or the
combined file changes (and with `--local`), with `JARL_CERT_NAME`,
`JARL_CERT_DIR`, `JARL_CERT_CERT`, `JARL_CERT_CHAIN`,
`JARL_CERT_FULLCHAIN`, `JARL_CERT_KEY`, `JARL_CERT_COMBINED` (empty
without `--combined`), `JARL_CERT_DOMAINS`, `JARL_CERT_SERIAL`,
`JARL_CERT_NOT_AFTER` and `JARL_CERT_RENEWED` (1 when it replaced an
older certificate). If it fails, `jarl cert get` exits non-zero and runs
it again next time. It has 5 minutes.
* **`jarl cert schedule`** (Linux with systemd, as root) writes
`/etc/systemd/system/jarl-cert-get.service` and `.timer`, which run
`jarl cert get` daily at a random time within the hour after midnight,
@@ -314,7 +331,44 @@ jarl cert saved # what this host has, and when it exp
**On the Certificates page**, the administrators see each certificate's
owner and who downloaded it: the token, from where, when, and whether
that host has the current certificate or hasn't fetched the renewed one
yet. `jarl cert show NAME` says the same.
yet. `jarl cert show NAME` says the same. The owner's own tokens see
their own downloads there too. In `jarl cert list`, such a certificate's
USES is `get`, and PULLED is the last download, with `(old)` while a host
hasn't fetched the renewed one.
### HAProxy
HAProxy wants the key and the chain in one file. Give `jarl cert get` a
`--combined` path outside `--dir`:
```sh
jarl cert get proxy -d adbraun.lol -d '*.adbraun.lol' \
--combined /etc/haproxy/certs/adbraun.lol.pem --hook 'systemctl reload haproxy'
jarl cert get webmail -d webmail.adbraun.lol \
--combined /etc/haproxy/certs/webmail.pem --hook 'systemctl reload haproxy'
```
```
frontend https
bind :443 ssl crt /etc/haproxy/certs/
```
HAProxy loads every file in a `crt` directory and picks one by SNI, so
`/etc/haproxy/certs` should hold only combined files. Don't point `crt`
at `--dir` (`/etc/jarl/certs/NAME`): HAProxy would try to load
`cert.pem`, `privkey.pem` and `jarl-cert.json` there too, and refuse
to start. A `--combined` path that would overwrite a file in a saved
certificate's directory, or another certificate's combined file, is
refused.
The combined file is mode 600, owned by the user running `jarl cert get`
(root under the timer). HAProxy reads its certificates as root before it
drops privileges, so that's fine.
To add a combined file to a certificate this host already has, run
`jarl cert get proxy --combined /etc/haproxy/certs/adbraun.lol.pem`: it's
written from the files on disk and the hook runs, with nothing
downloaded.
## Security notes