release / release (push) Successful in 2m13s
0.6.0's first release candidate: reverse zones made easy, comparing views rebuilt, exploring the statistics through a query log, storage on the dashboard, encrypted DNS with several host names and views per listen address, option set matching named statements, forwarding over TLS documented. The upgrade from 0.5.2 packages was tested in a Debian 13 container. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
236 lines
13 KiB
Markdown
236 lines
13 KiB
Markdown
<p align="center"><img src="artwork/banner.svg" width="260" alt="Jarl: a bearded Viking jarl in a crown holding a DNS tree, above the JARL wordmark"></p>
|
|
|
|
# Jarl
|
|
|
|
*A Jarl rules a Hold; this one rules your zones.*
|
|
|
|
Jarl manages a BIND 9 (named) server: zones, records of every type, views,
|
|
ACLs, TSIG keys and any named.conf option. It has a web UI, a REST API and
|
|
a command line client. All three go through the same validated, audited,
|
|
all-or-nothing change pipeline, and nothing reaches named before BIND's own
|
|
checkers have accepted it.
|
|
|
|
```
|
|
$ jarl record add adbraun.lol nas A 192.168.1.20 --ptr
|
|
add nas.adbraun.lol. IN A 192.168.1.20
|
|
add 20.1.168.192.in-addr.arpa. IN PTR nas.adbraun.lol.
|
|
zone 1.168.192.in-addr.arpa serial 2026100700 -> 2026100701
|
|
zone adbraun.lol serial 2026100701 -> 2026100702
|
|
applied (changeset 12)
|
|
|
|
$ jarl record add adbraun.lol www A 192.168.1.99
|
|
error: www.adbraun.lol has a CNAME, so it can't have any other records (found A); a CNAME must be the only record at its name (RFC 1034) [zone adbraun.lol]
|
|
```
|
|
|
|
## Features
|
|
|
|
* **Everything in one place**: primary, secondary, forward, stub, mirror,
|
|
hint, redirect and in-view zones, reverse zones from a CIDR (suggested,
|
|
filled and kept in step from your A records), split DNS with
|
|
views, ACLs, TSIG keys, and every BIND 9.20 option at server, view, zone
|
|
and top level, with the ARM's description and grammar built in.
|
|
* **Safe changes.** Every change is validated as a whole (CNAME rules,
|
|
glue, delegations, RRset TTLs, references, option syntax, open-resolver
|
|
and open-transfer lints), checked with `named-checkconf -z`, installed
|
|
atomically, reloaded, and verified with `rndc zonestatus`. If named
|
|
doesn't load it, the previous files are put back.
|
|
* **Changesets**: apply edits one at a time, or bundle them, review the
|
|
diff, and apply them together. Every applied changeset can be reverted.
|
|
Serials are bumped automatically.
|
|
* **Users and API tokens with granular permissions**, e.g. a token that may
|
|
only write `_acme-challenge` TXT records in one zone. Tokens can be
|
|
rotated (a new secret, same grants), with an optional grace period, and
|
|
Jarl warns two weeks before one expires (dashboard, log, and the CLI
|
|
using it).
|
|
Passwords use argon2id, sessions have CSRF protection, logins are rate
|
|
limited.
|
|
* **Two-factor login** with any authenticator app (TOTP), with recovery
|
|
codes; optional per user, or required for administrators or everyone
|
|
(`totp.require`). With it on, creating API tokens and changing users
|
|
asks for a fresh code, so a stolen password can't be turned into a token.
|
|
* **Audit log** of every login, change and token, with user, token, IP and
|
|
outcome.
|
|
* **Daily backups** of the database and the DNSSEC keys by a systemd timer,
|
|
checked as they are written, with retention that never deletes the last
|
|
few; `jarld restore` puts one back safely, and raises zone serials so
|
|
named and the secondaries take the restored data.
|
|
* **Name server checks**: whether the secondaries serve the current serial,
|
|
and, for zones on the internet, whether the parent zone delegates them
|
|
to the right name servers with the right glue and DS records.
|
|
* **DNSSEC signing** per zone, by BIND itself, with every step that involves
|
|
the parent zone guarded: Jarl says when the DS record can be published,
|
|
refuses to make a zone unsigned while its DS may still be out there, turns
|
|
signing off in safe steps, and can wipe the slate clean.
|
|
* **Porkbun support** (optional): the domain's name servers, glue records
|
|
(any number, IPv4 and IPv6) and DS records at the registrar, set from Jarl
|
|
after checks that the change won't take the domain offline.
|
|
* **No token needed on the server**: root's `sudo jarl ...` talks to jarld
|
|
over a local unix socket, so a lost token or password never locks you
|
|
out.
|
|
* **Query statistics** (optional, off by default): queries over time,
|
|
busiest hours of the week, top names and clients, NXDOMAIN and refused
|
|
queries, record types, cache hit rate. Shown in the web UI and the CLI,
|
|
and exportable as JSON. Stored as aggregates in a separate database with
|
|
retention you set.
|
|
* **Internet exposure checks**: Jarl works out which views are reachable
|
|
from the internet (a GUA-addressed internal network still counts as
|
|
yours) and checks them: response rate limiting (set up in log-only mode
|
|
first, with what it would limit shown before you enforce it), open
|
|
resolvers, ANY amplification, cache snooping, version and host name
|
|
leaks, open management interfaces. Fixes are staged for review, changes
|
|
that open a public view up must be confirmed, and deliberate exceptions
|
|
can be acknowledged with a reason.
|
|
* **Prometheus metrics** (optional): health to alert on (named down,
|
|
configuration problems, drift, failing blocklists, stale backups, DNSSEC
|
|
steps waiting at the parent, expiring tokens) and query counters per
|
|
result code, view, zone and blocklist, with example alerts. Set up in
|
|
jarl.yaml, with an address allow list and/or a bearer token.
|
|
* **Blocking** like Pi-hole, done by named itself: blocklists (Adblock,
|
|
hosts or plain domains) downloaded and kept up to date, your own block
|
|
and allow rules that beat every list, per view, with "why is this
|
|
blocked?" checks and blocked counts in the statistics.
|
|
* **Let's Encrypt DNS-01** built in: `jarl acme` plus certbot, acme.sh and lego
|
|
hooks, an HTTPS-only endpoint for lego's and Traefik's `httpreq` provider,
|
|
and CNAME delegation to a dedicated ACME zone.
|
|
* **Certificates for the server itself**: Jarl obtains and renews TLS
|
|
certificates from Let's Encrypt (or any ACME CA), answering the DNS-01
|
|
challenge in its own zones or through Porkbun's API while Porkbun still
|
|
serves the domain, decided at every renewal. Use them for the web UI
|
|
(HTTPS without a restart) and for named's DNS over TLS and HTTPS (named
|
|
rereads them on renewal), or bring your own.
|
|
* **DNS over TLS and HTTPS** for your own devices in one step: the
|
|
certificate, named's listeners on the addresses you choose (after
|
|
checking the ports are free) and the host name in your private views.
|
|
* **Linked zones for split DNS**: the same zone in several views, with the
|
|
shared records kept in step by Jarl and a few records of each view's own
|
|
(an internal address for a public name, say).
|
|
* **Adopts an existing server**: imports named.conf and zone files
|
|
(comments included), then switches named over with an automatic
|
|
fallback.
|
|
* One static Go binary for the daemon (web UI embedded, SQLite built in) and
|
|
one for the CLI, run by a sandboxed systemd service as an unprivileged
|
|
user. Releases come as Debian packages in an apt repository (server and
|
|
CLI separately) and as standalone CLI downloads for Linux, macOS and
|
|
Windows; both binaries have bash completion.
|
|
|
|
## Quick start
|
|
|
|
On a Debian/Ubuntu BIND 9.20 server ([install.md](docs/install.md) has the
|
|
details):
|
|
|
|
```sh
|
|
sudo apt install jarl # from the Gitea package registry (install.md shows adding it)
|
|
# or build it: make deb && sudo apt install ./dist/jarl*_amd64.deb, or make && sudo make install
|
|
sudo -u jarl jarld init # database + first administrator
|
|
sudo -u jarl jarld import # read the current named.conf and zones
|
|
sudo jarld adopt # switch named over (keeps a backup, rolls back on failure)
|
|
sudo systemctl enable --now jarl
|
|
|
|
sudo jarl status # root on the server needs no token (local socket)
|
|
```
|
|
|
|
Then open the web UI at `http://127.0.0.1:8053/`, or set `listen` and `tls`
|
|
in `/etc/jarl/jarl.yaml` to reach it from your management network.
|
|
|
|
## Documentation
|
|
|
|
| | |
|
|
|-|-|
|
|
| [docs/concepts.md](docs/concepts.md) | How Jarl works: source of truth, changesets, the apply pipeline, authentication. Read this first |
|
|
| [docs/install.md](docs/install.md) | Installing, importing and adopting an existing server, TLS, upgrading |
|
|
| [docs/configuration.md](docs/configuration.md) | `/etc/jarl/jarl.yaml` reference |
|
|
| [docs/cli.md](docs/cli.md) | The `jarl` command line client |
|
|
| [docs/api.md](docs/api.md) | REST API reference |
|
|
| [docs/permissions.md](docs/permissions.md) | Users, tokens, grants and roles, two-factor login |
|
|
| [docs/views.md](docs/views.md) | Split DNS: adding views to a server that has none |
|
|
| [docs/dnssec.md](docs/dnssec.md) | DNSSEC signing, going public with your own name servers, Porkbun |
|
|
| [docs/acme.md](docs/acme.md) | Let's Encrypt DNS-01 with certbot, acme.sh, lego and Traefik |
|
|
| [docs/encrypted-dns.md](docs/encrypted-dns.md) | DNS over TLS and HTTPS for your clients: setup, ports, client settings |
|
|
| [docs/certificates.md](docs/certificates.md) | TLS certificates Jarl obtains and renews for its web UI and named's DNS over TLS/HTTPS |
|
|
| [docs/stats.md](docs/stats.md) | Query statistics: enabling, what is collected, retention |
|
|
| [docs/reverse.md](docs/reverse.md) | Reverse zones: suggested from your records, created in the right view and filled, PTR records kept in step |
|
|
| [docs/exposure.md](docs/exposure.md) | Internet exposure: which views are public, hardening checks, response rate limiting |
|
|
| [docs/metrics.md](docs/metrics.md) | Prometheus metrics: access, what is exported, example alerts |
|
|
| [docs/blocking.md](docs/blocking.md) | Blocking ads, trackers and malware: blocklists, your own rules, views, memory |
|
|
| [docs/validation.md](docs/validation.md) | Every check Jarl makes |
|
|
| [docs/operations.md](docs/operations.md) | Backups, drift, failures, recovery, monitoring |
|
|
| [docs/development.md](docs/development.md) | Code layout, tests against real BIND, conventions |
|
|
| [CHANGELOG.md](CHANGELOG.md) | Changes per release |
|
|
|
|
## The web UI
|
|
|
|
The UI is built for getting things done rather than for looks. It has
|
|
these pages:
|
|
|
|
* **Dashboard**: named status, adoption, drift, recent changes, open
|
|
problems.
|
|
* **Statistics** (when enabled): queries over time, failed answers, a
|
|
weekday x hour heatmap, top names and clients, and breakdowns. Every chart
|
|
has a table view.
|
|
* **Zones**: list, create (choose a domain or a network, and the dialog shows
|
|
the exact zone name it will create and warns about likely mistakes;
|
|
fields adapt to the zone type), per-zone records
|
|
with inline editing, type-specific hints and a preview of the exact record
|
|
that will be added (reverse zones take addresses and suggest host names), settings (SOA, TTL, serial
|
|
policy, view), options, the generated zone file, pasting a zone file
|
|
to merge or replace records, name server checks, DNSSEC (keys, the DS
|
|
record to copy, and each step with what it needs first) and, with a
|
|
Porkbun key, the registrar.
|
|
* **Views, ACLs, TSIG keys, Server options**: wherever an address match list
|
|
is entered, the existing ACLs and keys are offered and unknown names are
|
|
flagged. The options editor knows every
|
|
BIND 9.20 statement for the scope and shows its description, grammar and
|
|
a value field that fits it.
|
|
* **Changes**: pending edits with the diff, named-checkconf output and
|
|
problems, applied as one changeset. History lists applied changesets with
|
|
their diffs and a revert button.
|
|
* **Audit, Users, Account** (password, two-factor login, API tokens) and **Search**.
|
|
|
|
By default, edits are collected as pending changes. Pending rows are shown
|
|
inline (green = new, yellow = changed, struck out = deleted) until you
|
|
review and apply them. Switch on "Apply edits immediately" to skip the
|
|
review step.
|
|
|
|
## Requirements
|
|
|
|
* BIND 9.20, with `named-checkconf`, `named-checkzone` and `rndc`. The
|
|
option catalog follows 9.20. 9.18 will probably work, because
|
|
named-checkconf still checks every change, but it is untested.
|
|
* Linux with systemd for the packaged service. Defaults match
|
|
Debian/Ubuntu, and other layouts are configurable.
|
|
* Go 1.27+ to build.
|
|
|
|
## Status
|
|
|
|
Version 0.5.2 is the current release, and 0.6.0 is in testing
|
|
(0.6.0-rc.1): it makes reverse zones easy (suggested from your records,
|
|
created in the right view and filled, PTR records kept in step), rebuilds
|
|
comparing a zone's views, lets you explore the statistics down to each
|
|
client and name (a query log), shows the databases' size, and gives
|
|
encrypted DNS several host names and views per listen address. 0.5 added Prometheus metrics,
|
|
address ranges in ACLs and other address match lists, safer changeset
|
|
handling, internet exposure checks with response rate limiting, TLS
|
|
certificates Jarl obtains and renews itself, linked zones for split DNS,
|
|
DNS over TLS and HTTPS, and statistics per view. 0.4.0 added blocking (blocklists and
|
|
your own rules, like Pi-hole, done by named, with different blocking for
|
|
different devices), disabling zones, server statistics and view keys, on
|
|
top of 0.3.0's disabled records, script-friendly CLI output and client
|
|
names in the statistics, and 0.2.0's DNSSEC signing, Porkbun support,
|
|
two-factor login and view colours. Jarl is feature-complete for one server's configuration.
|
|
Managing several servers from one jarld and dynamic updates are not
|
|
handled yet ([concepts.md](docs/concepts.md#what-jarl-deliberately-doesnt-do-yet)).
|
|
|
|
## License
|
|
|
|
Jarl is released under the [MIT License](LICENSE).
|
|
|
|
## Credits
|
|
|
|
The logo is part of Jarl: a bearded Viking jarl in blue and white, whose
|
|
crown holds a DNS tree (a root node with its branches). The master is
|
|
`artwork/logo.png`, which `scripts/logo.py` traces into the vector
|
|
`assets/logo.svg` and `assets/logo.js`. The "JARL" wordmark is drawn as SVG
|
|
paths in `assets/brand.js`, so the web UI needs no font files. The banner at
|
|
the top of this file, `artwork/banner.svg`, is built from both by
|
|
`scripts/banner.py`.
|