Add token rotation
jarl token rotate, a 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, 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. A restricted token can't
rotate a wider token of its user.
Also fixes token create --save for named servers, and future times
(token expiry) shown as "just now" in the web UI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6b44b92975
commit
0b2159b1a4
21 files changed
+755
-46
No files matched your search
@@ -315,6 +315,7 @@ Success is `200 {"ok": true, "fqdn": ..., "changeset_id": ..., "zones": [...]}`.
|
||||
| GET / POST | `/me/tokens` | `{"name": "laptop", "role": "", "grant_strings": [...], "expires_in": "90d"}`. The response's `token` field is the secret, shown once |
|
||||
| GET | `/tokens` | every token (admin); `?all=1` includes revoked tokens |
|
||||
| DELETE | `/tokens/{id}` | revoke |
|
||||
| POST | `/tokens/{id}/rotate` | New secret, same token. Optional body `{"grace": "1h", "expires_in": "90d", "expires_at": "...", "no_expiry": false}`. The response's `token` field is the new secret, shown once; `previous_secret_valid_until` says when the old one stops. A restricted token may rotate itself and tokens no wider than itself |
|
||||
| POST | `/me/password` | `{"current_password": "...", "password": "..."}` (sessions only) |
|
||||
|
||||
User and token changes take effect immediately. They are not changesets,
|
||||
|
||||
+3
-1
@@ -272,6 +272,8 @@ jarl token list [--user alice | --all] [--revoked]
|
||||
jarl token create laptop --expires 365d
|
||||
jarl token create backup-script --grant "*:read"
|
||||
jarl token create acme --user certbot --role acme
|
||||
jarl token rotate laptop --save # new secret, same token and grants
|
||||
jarl token rotate --current --save --grace 10m # the token this CLI uses
|
||||
jarl token revoke 7
|
||||
|
||||
jarl audit [--user alice] [--action auth.login_failed] [--outcome failure] [--since 2026-10-01] [--limit 100]
|
||||
@@ -281,7 +283,7 @@ jarl check # validation + named-checkconf of the current configuration
|
||||
jarl resync [--force] [--dry-run] # rewrite every file from the database
|
||||
```
|
||||
|
||||
Grant syntax and roles are in [permissions.md](permissions.md).
|
||||
Grant syntax, roles and token rotation are in [permissions.md](permissions.md).
|
||||
|
||||
## Query statistics
|
||||
|
||||
|
||||
@@ -113,6 +113,54 @@ jarl token revoke 12
|
||||
In the web UI: Account → API tokens (your own) and Users → *user* →
|
||||
Tokens (admin).
|
||||
|
||||
### Rotating a token
|
||||
|
||||
Rotating gives a token a **new secret** and changes nothing else: it keeps
|
||||
its ID, name, user, grants and audit history, so there is nothing to set
|
||||
up again. Only the programs that use the token need the new secret. Use it
|
||||
on a schedule, when someone who knew the token leaves, or when it may have
|
||||
leaked. Revoking is for a token that is no longer needed.
|
||||
|
||||
```sh
|
||||
jarl token rotate 12 # by ID (see 'jarl token list')
|
||||
jarl token rotate laptop --save # by name, and update the CLI's configuration
|
||||
jarl token rotate deploy --user ci --grace 1h # another user's token (admin), old secret works 1 more hour
|
||||
jarl token rotate --current --save # the token this CLI runs with
|
||||
```
|
||||
|
||||
* **The old secret** stops working at once. With `--grace` (at most 7d)
|
||||
it keeps working for that long, so clients can be switched over without
|
||||
an outage. While it does, jarld logs a warning each time the old secret
|
||||
is used (at most once a minute per token and address), with the client's
|
||||
IP, so you can find what still needs updating:
|
||||
`journalctl -u jarl | grep "old secret"`. `jarl token list` and the web
|
||||
UI show when the old secret stops. Rotating again ends the previous grace
|
||||
period. If the token may have leaked, rotate without `--grace`.
|
||||
* **Expiry**: a token that expires gets its lifetime again, counted from
|
||||
now, so a 90-day token is good for another 90 days. `--expires 30d` sets
|
||||
a new lifetime and `--no-expiry` removes the expiry. A token without an
|
||||
expiry keeps having none.
|
||||
* **Where the new secret goes**: it is printed once, like a new token.
|
||||
`--write FILE` writes it to a file instead (an existing file keeps its
|
||||
owner and mode; a new one gets mode 0600). `--save` puts it where the CLI
|
||||
got its token from: the configuration file (the selected server's entry)
|
||||
or the file named in `--token @FILE`. `--save` refuses before rotating
|
||||
when the token came from a literal `--token` or `JARL_TOKEN`.
|
||||
* **Who may rotate**: administrators any token; users their own tokens. A
|
||||
restricted token may rotate itself and tokens no wider than itself, but
|
||||
not a wider token of its user, since it would learn that token's secret.
|
||||
So automation can renew its own token, for example a monthly timer:
|
||||
|
||||
```sh
|
||||
jarl --token @/etc/jarl/certbot.token token rotate --current --save
|
||||
```
|
||||
* Every rotation is in the audit log (`token.rotate`), with when the old
|
||||
secret stops and the new expiry.
|
||||
|
||||
In the web UI, the **Rotate** button next to each token asks how long the
|
||||
old secret should keep working and how long the token should last, then
|
||||
shows the new secret once.
|
||||
|
||||
## Recovering access
|
||||
|
||||
On the server, as root or the `jarl` user:
|
||||
|
||||
Reference in new issue
Block a user