Off by default (stats in jarl.yaml). When enabled, Jarl adds a loopback statistics channel and a response log to named's configuration (merged into existing logging/statistics-channels statements) and switches response logging with rndc responselog, because the responselog option only counts when named starts. jarld polls named's counters every minute and tails the response log (resuming after restarts and across rotation). It stores aggregates, never individual queries, in a separate SQLite database: per-minute, hourly and daily counters (queries, result codes, record types, views, zones, cache hits, upstream latency, DNSSEC) and hourly/daily top lists (names, clients, NXDOMAIN and SERVFAIL names, refused clients), each with its own retention. Clients can be anonymized or excluded. - Web UI: Statistics page with KPI tiles, queries and failed answers over time, a weekday x hour heatmap, top lists and breakdowns; SVG charts with hover/keyboard readouts, a table view and light/dark palettes validated for colour-vision deficiency. - CLI: jarl stats status|summary|top|timeline|heatmap|breakdown. - API: /api/v1/stats/*, behind a new "stats" permission resource. - jarld brings named's configuration up to date with jarl.yaml at startup (engine.SyncConfig), never overwriting hand-edited files. - resync now refuses to overwrite drifted files without force, as documented. - tmpfiles.d creates /var/log/named (allowed by named's AppArmor profile on Debian 13 and Ubuntu). - The e2e harness runs named with -f like the Debian unit. - docs/stats.md, plus API, CLI, configuration, permissions and README updates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
119 lines
5.0 KiB
Markdown
119 lines
5.0 KiB
Markdown
# Users, tokens and permissions
|
|
|
|
## Ways to authenticate
|
|
|
|
| Who | How | Notes |
|
|
|-----|-----|-------|
|
|
| People in the web UI | Username + password → session cookie | argon2id password hashes. The session cookie is HttpOnly and SameSite=Strict and lasts 12h (2h idle). Every change also needs the session's CSRF token, which the UI sends in `X-CSRF-Token`. Changing a password logs out the user's other sessions. |
|
|
| CLI, scripts, certbot | API token: `Authorization: Bearer jarl_...` | Shown once at creation, stored hashed. Optional expiry. Revocable. Last use time and IP are recorded. |
|
|
| Root on the server | `jarld` subcommands (`init`, `user`, `import`, `adopt`, `check`, `resync`, `backup`) | Work directly on the database. They are for bootstrapping and recovery, and are audited as "local". |
|
|
|
|
Failed logins are rate limited: 8 per username and 20 per client IP within
|
|
15 minutes. Failed token attempts count against the IP too. Every attempt
|
|
is in the audit log.
|
|
|
|
A user **without a password** (`--no-password`) is a service account. It
|
|
can't log in to the web UI and is used only through its tokens, which suits
|
|
certbot, CI jobs and backups.
|
|
|
|
## Grants
|
|
|
|
A user's permissions are a list of grants. Administrators (`admin: true`)
|
|
may do everything, and only they can manage users, see everyone's tokens
|
|
and run `resync`.
|
|
|
|
A grant is written as:
|
|
|
|
```
|
|
RESOURCE:ACCESS [zones=PATTERNS] [views=PATTERNS] [names=PATTERNS] [types=TYPES]
|
|
```
|
|
|
|
| Resource | Covers |
|
|
|----------|--------|
|
|
| `zones` | zone definitions (create, delete, SOA, TTL, view) and zone options |
|
|
| `records` | records in primary zones |
|
|
| `views` | views and view options |
|
|
| `acls` | ACLs |
|
|
| `tsig` | TSIG keys (`write` is needed to see a secret) |
|
|
| `options` | server options, top-level statements, the defaults for new zones, `jarl check` |
|
|
| `audit` | everyone's audit events and changesets (without it, users see only their own) |
|
|
| `stats` | query statistics, including client addresses and the names they look up (read only) |
|
|
| `*` | all of the above |
|
|
|
|
`ACCESS` is `read` or `write` (write includes read). Filters narrow a grant.
|
|
Each takes comma separated patterns where `*` and `?` are wildcards, and a
|
|
missing filter matches everything:
|
|
|
|
* `zones=adbraun.lol,*.adbraun.lol`: zone names, without the final dot
|
|
* `views=internal`: view names (`_default` means zones outside any view)
|
|
* `names=_acme-challenge.*`: record owner names, as FQDNs without the final dot
|
|
* `types=TXT,CNAME`: record types
|
|
|
|
Examples:
|
|
|
|
```
|
|
*:read read everything
|
|
records:write zones=lab.adbraun.lol edit records in one zone
|
|
zones:write zones=*.lab.adbraun.lol create/delete zones below lab
|
|
records:write zones=adbraun.lol views=internal only the internal copy of a split zone
|
|
records:write names=_acme-challenge.* types=TXT ACME challenges anywhere
|
|
records:write zones=acme.adbraun.lol types=TXT only TXT records in a dedicated ACME zone
|
|
```
|
|
|
|
A user who may see some records of a zone may also see that the zone exists
|
|
(its name and type), but not its other records.
|
|
|
|
### Roles
|
|
|
|
Roles are ready-made grant lists. Choosing a role copies its grants, which
|
|
you can then edit.
|
|
|
|
| Role | Grants |
|
|
|------|--------|
|
|
| `readonly` | `*:read` (includes statistics) |
|
|
| `dns-editor` | `zones:write`, `records:write`, `views:read`, `acls:read`, `options:read` |
|
|
| `operator` | `*:write` (everything except user management) |
|
|
| `acme` | `records:write names=_acme-challenge.* types=TXT` |
|
|
|
|
```sh
|
|
jarl user create alice --role dns-editor
|
|
jarl user update alice --grant "records:write zones=adbraun.lol" --grant "zones:read" # replaces all grants
|
|
jarl user update alice --role readonly
|
|
```
|
|
|
|
## Tokens
|
|
|
|
Every token belongs to a user, and every token request acts as that user.
|
|
The audit log shows the user and the token name.
|
|
|
|
* A token **without grants** has all of its user's permissions, and they
|
|
follow the user: if the user loses a permission, so does the token.
|
|
* A token **with grants** is restricted to them. Effective permission is
|
|
*user grants ∩ token grants*: a token can never do more than its user.
|
|
Admin rights don't pass through a restricted token.
|
|
* A restricted token can create further tokens, but only ones no wider than
|
|
itself.
|
|
* Disabling or deleting a user stops all their tokens at once.
|
|
|
|
```sh
|
|
jarl token create laptop # full rights of the user, no expiry
|
|
jarl token create ci --grant "records:write zones=lab.adbraun.lol" --expires 90d
|
|
jarl token create certbot --user certbot --role acme # admin: a token for another user
|
|
jarl token list --all
|
|
jarl token revoke 12
|
|
```
|
|
|
|
In the web UI: Account → API tokens (your own) and Users → *user* →
|
|
Tokens (admin).
|
|
|
|
## Recovering access
|
|
|
|
On the server, as root or the `jarl` user:
|
|
|
|
```sh
|
|
sudo -u jarl jarld user list
|
|
sudo -u jarl jarld user passwd admin # set a new password and re-enable the account
|
|
sudo -u jarl jarld user add --admin rescue # a new administrator
|
|
sudo -u jarl jarld user token admin cli # a new API token for a user
|
|
```
|