jarld also listens on /run/jarl/jarl.sock (socket in jarl.yaml). Requests on it from root or jarld's own user need no token and act as an administrator; the kernel's SO_PEERCRED says who is connecting, and anyone else must send a token. The jarl CLI uses the socket when no server is configured, or with -s local, so 'sudo jarl status' works on the server with nothing set up and a lost token or password never locks root out. Changes are audited under the system user's name via "socket". The systemd unit creates /run/jarl (0750). Socket paths over 107 bytes are refused with a clear message. The docs explain the socket and what happens when a token is revoked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
260 lines
16 KiB
Markdown
260 lines
16 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to Jarl are documented here. The format follows
|
||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
|
||
[Semantic Versioning](https://semver.org/).
|
||
|
||
## [Unreleased]
|
||
|
||
The first version, to be released as 0.1.0.
|
||
|
||
### Added
|
||
|
||
- CLI: several servers in one configuration file. Name them
|
||
(`jarl config server add ns1 URL`), pick one with `-s/--server NAME` or
|
||
`JARL_SERVER`, set a default with `jarl config use`, and group them
|
||
(`jarl config group set dns ns1 ns2`). A command aimed at a group (or
|
||
`all`) is first checked on every server as a dry run and only runs if it
|
||
succeeds everywhere, so a change one server rejects changes nothing
|
||
anywhere. Named servers always use their own token.
|
||
- Reverse zones for any network: a /32 (or /128) gets a zone that answers
|
||
for just that address, and a network between octet (or nibble)
|
||
boundaries, such as a /22, is covered by several zones (four /24s) that
|
||
are created together, from the CLI (`jarl zone create 10.0.8.0/22`) and
|
||
the web UI. Networks smaller than a /24 explain that they belong in the
|
||
enclosing /24's zone.
|
||
- docs/operations.md: setting up a secondary server with its own jarld.
|
||
- A local unix socket, `/run/jarl/jarl.sock` (`socket` in jarl.yaml).
|
||
Requests on it from root or jarld's own user need no token and act as
|
||
an administrator (the kernel's `SO_PEERCRED` says who is connecting);
|
||
anyone else must send a token. The `jarl` CLI uses it when no server is
|
||
configured, or with `-s local`, so `sudo jarl status` works on the
|
||
server with nothing set up, and losing every token or password never
|
||
locks root out. Changes are audited under the system user's name via
|
||
`socket`. The systemd unit creates `/run/jarl` (0750).
|
||
- Jarl is released under the MIT License (`LICENSE`), which is installed
|
||
with the docs and is the Debian package's `copyright` file (with the
|
||
bundled Cinzel font's OFL).
|
||
- A Debian package: `make deb` builds `dist/jarl_<version>_<arch>.deb` with
|
||
only `dpkg-deb` (no root), for the host or any other architecture
|
||
(`DEB_ARCH=arm64`). Installing it creates the `jarl` user and directories
|
||
and an example `/etc/jarl/jarl.yaml`; upgrades restart a running jarld and
|
||
never touch `/etc/jarl/jarl.yaml`; removing it stops jarld, and even a
|
||
purge keeps Jarl's database and the files named uses.
|
||
- Bash completion for `jarl` and `jarld`, installed by `make install` and
|
||
the Debian package. Besides commands and flags, it completes server and
|
||
group names from the CLI configuration, zone, view, ACL, TSIG key and
|
||
user names from the server, and record types.
|
||
- `make dist` tarballs include the completion scripts (`completion/`).
|
||
- Token rotation: `jarl token rotate`, the Rotate button in the web UI and
|
||
`POST /api/v1/tokens/{id}/rotate` give a token a new secret while it
|
||
keeps its ID, name, user and grants. The old secret stops at once or after
|
||
a grace period of up to 7 days (`--grace`), during which jarld logs who
|
||
still uses it. A token that expires gets its lifetime again. `--save`
|
||
writes the new secret where the CLI got the old one (its configuration or
|
||
`--token @FILE`), `--write FILE` to any file, and `--current` picks the
|
||
token the CLI runs with, so a restricted token can renew itself.
|
||
`jarl token list` shows when a token was last rotated.
|
||
- An endpoint for lego's `httpreq` DNS provider
|
||
(`/api/v1/acme/httpreq/present` and `/cleanup`), so lego and Traefik can
|
||
publish DNS-01 challenges with only environment variables, without the
|
||
jarl CLI. lego's username and API token arrive as HTTP Basic auth, which
|
||
is accepted on these routes only. 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`.
|
||
Plain HTTP can be allowed from loopback only (`allow_loopback_http`) or
|
||
everywhere (`require_https: false`). `GET /api/v1/acme/httpreq` shows how
|
||
jarld sees a request, to check a proxy setup. `jarl acme` and the endpoint
|
||
share their code (`internal/acme`).
|
||
- Trusted proxies may also send the standard `Forwarded: proto=https`
|
||
header. When a proxy header lists several values, the last one (added by
|
||
the nearest proxy) counts.
|
||
- lego support: `contrib/lego/jarl-lego.sh` is a program for lego's `exec`
|
||
DNS provider that publishes DNS-01 challenges through `jarl acme`,
|
||
including challenge names reached through a CNAME (docs/acme.md).
|
||
- docs/concepts.md: what each zone type does, what to enter as a new zone's
|
||
name servers (and when glue addresses are needed), how reverse zones map
|
||
to networks, and why dynamic updates give way to API tokens.
|
||
|
||
- A logo: a Viking jarl drawn like a woodcut, with a spear-pointed crown
|
||
that holds a DNS tree (a root node and its branches), a stern face and a
|
||
full carved beard. White on a Nordic-blue disc (`assets/logo.svg`), plus an all-white
|
||
variant (`assets/logo-white.svg`). It is shown on the login page, links
|
||
home in the top left of the web UI, and is the favicon.
|
||
- The login page shows an angular, Norse-style "JARL" wordmark drawn as
|
||
SVG paths (`assets/brand.js`) above a rule with a "DNS" node; it follows
|
||
the light and dark themes. The header's "Jarl" uses Cinzel Bold (SIL OFL),
|
||
self-hosted as a 13 KB Latin subset.
|
||
|
||
- Web UI: wherever an address match list is entered (server, view and zone
|
||
options such as allow-query or allow-transfer, view match lists, ACL
|
||
elements) the existing ACLs, TSIG keys and built-in names are offered as
|
||
one-click suggestions, and names that don't exist are flagged as you type.
|
||
|
||
- **Query statistics** (off by default; `stats` in jarl.yaml, docs/stats.md).
|
||
When enabled, Jarl adds a loopback statistics channel and a response log to
|
||
named's configuration (merged into existing `logging` and
|
||
`statistics-channels` statements) and switches response logging with
|
||
`rndc responselog`. jarld aggregates named's counters and the log into a
|
||
separate SQLite database: per-minute, hourly and daily counters (queries,
|
||
result codes, record types, views, zones, cache hits, upstream latency,
|
||
DNSSEC) and hourly and daily top lists (names, clients, NXDOMAIN and
|
||
SERVFAIL names, refused clients), each with its own retention. Clients can
|
||
be anonymized or excluded. It is shown on a new Statistics page in the web
|
||
UI (KPI tiles, queries and failed answers over time, a weekday x hour
|
||
heatmap, top lists and breakdowns, each chart with a table view), in
|
||
`jarl stats status|summary|top|timeline|heatmap|breakdown`, and in the API
|
||
under `/api/v1/stats/`, behind the new `stats` permission. Works with the
|
||
bind9 packages of Debian 13 and Ubuntu 26.04.
|
||
- At startup jarld brings named's configuration up to date with jarl.yaml
|
||
settings that affect it (such as statistics), through the usual checked
|
||
pipeline. Files changed by hand are never overwritten.
|
||
- **jarld daemon**: REST API (`/api/v1`) and embedded web UI, with SQLite as
|
||
the source of truth for the whole BIND configuration. Runs under systemd
|
||
(`Type=notify`, sandboxed, unprivileged `jarl` user in group `bind`).
|
||
SIGHUP and certificate file changes reload TLS.
|
||
- **Configuration model**: views (ordered, match-clients/destinations,
|
||
recursive-only), ACLs (nested, negation, keys, reference tracking with
|
||
rename and delete protection), TSIG keys (kept in a separate `keys.conf`,
|
||
never in diffs), zones of every type (primary, secondary, stub,
|
||
static-stub, forward, mirror, hint, redirect, in-view), reverse zones
|
||
from a CIDR, records of every type, options at server, view, zone and
|
||
top-level scope, and defaults for new zones.
|
||
- **BIND 9.20 option catalog**: all 353 statements with grammar, allowed
|
||
scopes, value kinds, deprecation status and ARM descriptions, used for
|
||
validation and by the UI's option editor.
|
||
- **Changesets**: immediate one-operation changes by default; staging into
|
||
named changesets (`?changeset=`, `X-Jarl-Changeset`, `--changeset`),
|
||
batches (`POST /apply`, `jarl apply -f`), dry runs, previews with diffs,
|
||
and revert of applied changesets. Optimistic locking with object versions.
|
||
- **Apply pipeline**: whole-configuration validation, rendering, `named-checkconf -z`
|
||
and `named-checkzone` in a staging directory, atomic install, `rndc reconfig`
|
||
and per-zone reload, verification with `rndc zonestatus`, and automatic
|
||
rollback when named doesn't load the change. Automatic serials (date,
|
||
increment or unixtime policy, RFC 1982), bumped only when DNS data
|
||
changes. Drift detection for files changed outside Jarl.
|
||
- **Validation**: CNAME/DNAME rules, NS/MX/SRV targets, glue and
|
||
delegations, RRset TTL consistency, BIND 9.20 RRset/type limits, SPF/DMARC
|
||
sanity, ACL/key references and cycles, option syntax and scope, and
|
||
security lints (open resolver, open zone transfers, dynamic updates on
|
||
managed zones).
|
||
- **Records**: TXT entered as plain text (automatic quoting and 255-byte
|
||
splitting), relative and absolute names with detection of doubled
|
||
origins, PTR records kept in step with A/AAAA (`ptr`), RRset replace,
|
||
delete by match, zone file import (merge or replace).
|
||
- **Import and adoption**: `jarld import` reads an existing named.conf tree
|
||
and zone files (comments, `$INCLUDE`, `$GENERATE`, legacy
|
||
`master`/`slave` spellings); `jarld adopt` switches named to Jarl's
|
||
configuration with a backup and automatic fallback.
|
||
- **Authentication**: web logins with argon2id passwords, sessions
|
||
(HttpOnly, SameSite=Strict) and CSRF tokens, rate limiting per user and
|
||
IP; hashed API tokens (`jarl_...`) with optional expiry.
|
||
- **Permissions**: granular grants (resource × read/write, filtered by zone,
|
||
view, record name pattern and type), roles (`readonly`, `dns-editor`,
|
||
`operator`, `acme`), tokens restricted to a subset of their user's
|
||
rights, administrators.
|
||
- **Audit log** of logins, changes, users, tokens and secret reveals, with
|
||
user, token, IP and outcome, searchable and written to the journal.
|
||
- **Web UI**: dashboard, zones and records with inline editing and pending
|
||
markers, views, ACLs, TSIG keys, the option editor, review and apply,
|
||
history with revert, audit log, users, account and tokens, search.
|
||
Review mode by default, with an "apply immediately" switch.
|
||
- **jarl CLI**: zones, records, views, ACLs, TSIG keys, options, settings,
|
||
changesets, batches, history, users, tokens, audit, search, status, check
|
||
and resync. Table, JSON and YAML output, and documented exit codes.
|
||
- **ACME DNS-01**: `jarl acme set|unset`, certbot hooks
|
||
(`jarl acme certbot-auth|certbot-cleanup`), an acme.sh DNS API hook
|
||
(`contrib/acme.sh/dns_jarl.sh`), automatic zone and view discovery,
|
||
idempotent retries, and `--follow-cname` / `--resolver` for a dedicated
|
||
ACME zone.
|
||
- **Operations**: `jarld backup` (online), `jarld resync`, `jarld check`,
|
||
`jarld render`, local user recovery commands, audit retention.
|
||
- **Packaging**: Makefile (build, test, e2e, install, dist), systemd unit,
|
||
sysusers.d and tmpfiles.d files, example configuration
|
||
(`jarld config --example`).
|
||
- **Tests**: unit tests for every package, integration tests against the
|
||
real named 9.20 (checkers, reload and rollback, import and adopt), and an
|
||
end-to-end script (`scripts/e2e.sh`) that drives every CLI command
|
||
against a running named.
|
||
- **Documentation**: concepts, install and adoption, configuration, CLI,
|
||
API, permissions, ACME, validation, operations and development guides, and
|
||
a split-DNS guide for adding views to a server that has none.
|
||
|
||
### Changed
|
||
|
||
- Web UI: the review/live switch is a small "Review edits" / "Live edits"
|
||
button in the header, and the bar below it only appears while there are
|
||
pending changes.
|
||
- TSIG keys: the name help no longer calls `xfer-key` a domain name (any
|
||
letters, digits, hyphens and dots will do; both servers must use the same
|
||
name), and the keys page points to restricted API tokens for giving
|
||
scripts or people limited access to zones and records.
|
||
- Web UI, new zone dialog: choose **Domain** or **Reverse zone**. For a
|
||
reverse zone you give the network (10.10.0.0/24, 2001:db8:1::/48) and the
|
||
zone name is generated. Either way the dialog shows the exact zone name it
|
||
will create and explains problems before submitting: an address or URL
|
||
instead of a name, a host-like name such as www.example.com, a zone that
|
||
already exists in the view, a child or parent of an existing zone, and
|
||
networks that don't follow the octet or nibble boundaries.
|
||
- Web UI, record form: after adding a record every field is cleared except
|
||
the type, and the cursor returns to the name. A "Will add:" line shows the
|
||
record exactly as it will be stored, with warnings for unqualified names
|
||
(e.g. a CNAME to readthedocs.io without the final dot). In reverse zones
|
||
the form asks for the address, defaults to PTR, offers the host names of
|
||
A/AAAA records with that address, warns about a second PTR for an
|
||
address, and the record list shows each record's address.
|
||
- Reverse zones: a record name can be given as an address (10.10.0.20) in
|
||
the API and CLI too, and a PTR target without a final dot that contains a
|
||
dot (nas.adbraun.lol) is read as the full host name (with a warning); a
|
||
bare label is refused.
|
||
|
||
- `jarl resync` / `jarld resync` refuse to overwrite files that were changed
|
||
outside Jarl unless `--force` is given, as documented. Before, they always
|
||
overwrote them.
|
||
|
||
### Fixed
|
||
|
||
- `jarl token create --save` stored the token at the top level of the
|
||
CLI configuration even when a named server was selected; it now goes into
|
||
that server's entry.
|
||
- The web UI showed future times, such as token expiry dates, as "just
|
||
now"; they now read "in 29 days".
|
||
- The dashboard's "Pending changes (1 open)" counted empty changesets (and
|
||
other users'), so the Changes page then showed none. It now counts your
|
||
own changesets that contain changes, and the status API's
|
||
`open_changesets` only counts non-empty ones. In review mode, a draft
|
||
created for an edit that the server rejects is discarded instead of
|
||
being left open and empty.
|
||
- Under the shipped systemd unit (`RestrictSUIDSGID=yes`), the first zone
|
||
in a new view failed with "chmod /var/lib/bind/jarl/VIEW: operation not
|
||
permitted": setting the setgid bit on the new directory is refused there.
|
||
Jarl now falls back to the permission bits, so the directory is
|
||
group-writable for named, and only changes the mode when it needs to.
|
||
- Web UI: the "Jarl 0.1.0" brand link no longer underlines the gap between
|
||
the name and the version on hover.
|
||
- Web UI: creating a zone from the dashboard's "New zone" link no longer
|
||
opens the dialog a second time.
|
||
- Warnings noticed while parsing record data (a name read as absolute) were
|
||
lost before reaching the result; they are shown again.
|
||
|
||
- The negative-caching warning now uses the effective negative TTL, which
|
||
is the smaller of the SOA minimum and the zone's default TTL (RFC 2308).
|
||
It explains the real effect (new names can stay invisible to resolvers
|
||
that already asked) and gives the command that fixes it. Before, it
|
||
fired on the SOA minimum alone and wrongly blamed BIND's resolver cap.
|
||
- Problems printed by `jarld check` show zone names without the trailing dot.
|
||
- `jarl` now shows the warnings a change introduces when it is applied or
|
||
staged, not only on dry runs.
|
||
- `jarl check` and `jarl resync` no longer print named-checkconf's routine
|
||
"loaded serial" lines, only its errors and warnings.
|
||
- `jarl zone list` showed only primary zones: `zone create`'s default
|
||
`--type primary` leaked into the list filter through a shared variable.
|
||
Hint, secondary, forward and other zones are listed again.
|
||
- named-checkconf and named-checkzone output is classified by the tools'
|
||
exit code. Their warnings (e.g. `both "recursion no;" and "allow-recursion"
|
||
active`) were shown as errors. They are now shown as warnings in the
|
||
CLI, `jarld check` and the web UI.
|
||
- The `zone_outside_view` error says how to fix it (`--adopt-zones`).
|
||
- The end-to-end test starts named with `-f` like the Debian unit; with `-g`,
|
||
named sends all logging to stderr.
|