A Retirement Party and a Week Rebuilding rust-proxy's Access Control
Sunday was a friend's retirement party right after church — a good excuse to slow down for an afternoon. The rest of the stretch since the last post went almost entirely into rust-proxy: a real production certificate bug, a genuinely usable admin UI, a full rewrite of how hosts get access control, standalone wildcard certificate issuance, and logging that's finally split into something you can actually debug from.
Sunday was church, then straight over to a friend's retirement party in the afternoon — the good kind of day where nothing on the list was work. Everything else below happened across the days around it.
rust-proxy: a real production cert failure, and an admin UI that finally makes sense
Production logs from the proxy host turned up two certificate failures — one host rejected
outright by Let's Encrypt, the other never even offering the challenge type it needed. The
second was a real config gap. The first was more interesting: the CSR generation library
(rcgen) defaults its certificate's Subject Common Name to the literal string
"rcgen self signed cert" unless you explicitly override it — and nothing was, so
every CSR was going out with that placeholder baked in, which Let's Encrypt correctly rejected
as an invalid domain name. One line fixed it, plus a regression test that checks the raw CSR
bytes never contain that string again.
The admin UI got a real redesign on top of that: tabs instead of one long scrolling page, every field grouped into labeled sections instead of placeholder-only inputs, and hosts can finally be edited after creation instead of only created and deleted. Testing that in an actual browser (not just reading the code) caught a real trap: editing a login-gated host with the password field left blank — exactly what the UI's own placeholder told you to do — used to get rejected with a 400. Per-provider DNS credential fields came next, replacing a single "paste a colon-delimited string in the right order" box with real labeled inputs per provider, built and verified by reading the packed value straight back out of the database.
Also found and fixed a firewall bug that would've bitten every fresh install forever: the
generated nftables ruleset started with flush table inet rust_proxy, which errors
outright if that table has never existed — meaning a genuinely new host would fail its very
first firewall sync and never recover, since the script aborts on that first error before the
block that actually creates the table gets to run. Reproduced it for real against an installed
nft binary, confirmed the exact failure, then fixed it by making table creation
idempotent and always run first.
Replacing per-host access control with reusable ACLs, wildcard certs without a host, and logs you can actually use
The biggest piece: what used to be an IP allow list and a login gate typed directly onto each host is now a proper access-control system. Named lists — IP-allow or Login, each holding multiple entries — get created once and selected by any number of hosts, instead of retyping (and re-hashing) the same office IP range or credentials on every host that needs them. Select several lists of the same kind on one host and they combine by union — any matching IP range or any matching credential pair is enough — while the IP check and the login check stay two separate, sequential gates, same as before. Existing hosts' inline access data migrates automatically the first time the daemon starts on the new schema, so nothing was lost on upgrade. Verified the whole thing end-to-end in a real browser: created both kinds of list, selected several on one host, confirmed editing that host re-selects them correctly, and confirmed deleting a list still in use gets refused with a clear message instead of silently breaking the host that depends on it.
Wildcard certificates got a real path too: issuing *.example.com used to mean
creating a throwaway host just to get ACME to issue against that domain. Now a certificate can
be issued standalone, with no host at all, and every subdomain host created under it afterward
just works with no separate "attach this cert" step — TLS already falls back from an
exact-domain match to a covering wildcard, so the new feature just leans on that instead of
inventing something new. Verified against a real Let's Encrypt staging round trip: account
registration reached, a real ACME error came back and displayed correctly, and the form
recovered cleanly afterward.
Last piece was logging, which used to be one interleaved stream with no way to isolate anything. It's now split three ways — a proxy log for the data plane and certificate issuance (with real debug-level detail on connection handling, since that's exactly what's worth having on hand when something's not working), a brand-new access log that's the daemon's first actual per-request admin audit trail (who hit which endpoint, when, how they authenticated), and a server log for everything else — while the existing systemd/journald output keeps working exactly as it always has. Picked up one more small thing along the way: the ACME contact email Let's Encrypt needs was config-file-only before; it's now editable straight from the admin UI, with the same "config file wins if it's set" precedence the admin login already uses.