Files
jarl/docs/permissions.md
T
abrown38andClaude Opus 5.5 785b0006f9 Multi-server CLI, more reverse zones, and a fix for new view directories
- CLI: name several servers and groups in the config file; select them
  with -s/--server NAME|GROUP|all|URL or JARL_SERVER. A command aimed at
  several servers is dry-run on all of them first and only runs if every
  one accepts it. Named servers always use their own token.
- Reverse zones: /32 and /128 get single-address zones; networks between
  octet or nibble boundaries (e.g. a /22) are covered by several zones
  created together, from the CLI and the web UI.
- Fix: under jarl.service's RestrictSUIDSGID, creating a view directory
  failed on chmod with the setgid bit; fall back to the permission bits.
- Web UI: the review/live switch is a small header button, and the
  pending-changes bar only shows when there are pending changes.
- TSIG key wording; the keys page points to restricted API tokens.
- Docs: several servers (cli.md), a secondary server (operations.md),
  tokens vs TSIG keys (permissions.md); CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:12:03 +00:00

125 lines
5.4 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". |
API tokens are how you give a script, a tool or a person limited access:
a token restricted with grants (below) can, for example, only change TXT
records under `_acme-challenge` in one zone. TSIG keys, on the TSIG keys page,
are something else: shared secrets that BIND servers use to authenticate
each other (zone transfers, NOTIFY); they don't give access to Jarl.
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
```