Add an httpreq endpoint for lego and Traefik

jarld can now answer lego's httpreq DNS provider
(POST /api/v1/acme/httpreq/present and /cleanup), so lego and Traefik
publish DNS-01 challenges with only environment variables, without the
jarl CLI. lego's username and API token arrive as HTTP Basic auth,
accepted on these routes only.

The endpoint is off by default (acme.httpreq.enabled) and HTTPS-only by
default: jarld's own TLS, or a trusted proxy that says so with
X-Forwarded-Proto or Forwarded (the last value counts). Plain HTTP can be
allowed from loopback only or everywhere. GET /api/v1/acme/httpreq shows
how jarld sees a request, to check a proxy setup.

jarl acme and the endpoint share their code in internal/acme. Documented
in docs/acme.md, configuration.md, api.md and permissions.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-08 13:48:47 +00:00
1 parent 0221c78c8a
commit 6b44b92975
18 files changed
+1106 -73

No files matched your search

+18
View File
@@ -28,6 +28,9 @@ change something (anything but GET/HEAD) must send it as `X-CSRF-Token`.
`GET /auth/whoami` returns the user, the token (if any), the effective
permissions and whether the caller is an administrator.
**HTTP Basic**, only on the lego `httpreq` routes (below): the username is
the token's user and the password the API token.
## Errors
```http
@@ -286,6 +289,21 @@ Needs `stats:read`. When statistics are disabled, every endpoint but
curl -sf -H "$AUTH" "$API/stats/top?kind=nxdomain&since=7d&limit=10" | jq -r '.[] | "\(.count)\t\(.key)"'
```
### ACME: lego's httpreq provider
Off unless `acme.httpreq.enabled` is set, and by default HTTPS only. The
full description is in [acme.md](acme.md#6-lego-and-traefik-over-http-httpreq-without-the-jarl-cli).
| Method | Path | Body |
|--------|------|------|
| GET | `/acme/httpreq` | No authentication. `{"enabled", "https", "accepted", "transport"}`: whether the endpoint is on and how jarld sees this request (HTTPS or not, and why) |
| POST | `/acme/httpreq/present` | `{"fqdn": "_acme-challenge.adbraun.lol.", "value": "..."}`. Sets the TXT value in the longest matching primary zone, in every view that has it |
| POST | `/acme/httpreq/cleanup` | Same body. Removes that value (succeeds if it's already gone) |
| POST | `/acme/httpreq/view/{view}/present`, `.../cleanup` | The same, in one view only |
Authenticate with HTTP Basic (user and API token) or a Bearer token.
Success is `200 {"ok": true, "fqdn": ..., "changeset_id": ..., "zones": [...]}`.
### Users and tokens
| Method | Path | Body |