headscale: acl policy editor, node owner and tags
policy: /_officer/policy GET/PUT. the text goes up byte for byte and headscale's verdict comes back verbatim — it owns the only parser that resolves groups, tags and hosts, so a second one here would disagree with what actually enforces. a file-backed policy is still served over GET and only refuses on PUT, so writability cannot be read: the first save finds out, and a refusal becomes a persistent read-only banner rather than a rejection the owner would go hunting for a syntax error over. nodes: move between users, and a tag editor for the setTags route that had no ui. both sit together in the expanded card because both decide which policy rules apply to a node, and a move says so before it happens.
This commit is contained in:
@@ -100,22 +100,24 @@ it exists in the reference app, so none of it was in scope for parity.
|
||||
|
||||
## Headscale
|
||||
|
||||
Gaps against headscale's own API, found while reading the app on 2026-08-05. None is urgent; the
|
||||
first is the only one you would otherwise SSH in to do.
|
||||
Gaps against headscale's own API, found while reading the app on 2026-08-05 — all closed the same
|
||||
day. Kept here for what the policy work turned up, which is not obvious from the code.
|
||||
|
||||
- [ ] **ACL policy is not wired at all.** `/api/v1/policy` (GET/PUT) has no sidecar route and no
|
||||
screen. Editing the policy means SSHing to the host, which is the one thing this app exists to
|
||||
avoid. Wants a text editor with the server's own validation error surfaced on save, not a
|
||||
form — the policy is HuJSON and headscale is the authority on whether it parses.
|
||||
- [x] **ACL policy.** `/api/v1/policy` GET/PUT behind `/_officer/policy`, with a plain HuJSON textarea
|
||||
that sends the text byte for byte and shows headscale's verdict verbatim (line and column
|
||||
included). Officer does not pre-validate: headscale owns the only parser that resolves groups,
|
||||
tags and hosts, and a second weaker one would disagree with the thing that actually enforces.
|
||||
**The mode cannot be read.** A file-backed policy is still served over GET; only a PUT refuses,
|
||||
with "update is disabled for modes other than 'database'". So the first save is what discovers
|
||||
writability, and a refusal becomes a persistent read-only banner. Verified live, not inferred.
|
||||
|
||||
- [ ] **A node cannot be moved between users.** `/api/v1/node/{id}/user` is missing, so a node can be
|
||||
renamed, tagged, route-approved and expired, but not re-owned.
|
||||
- [x] **A node can be moved between users.** `/api/v1/node/{id}/user`, in the expanded node card next
|
||||
to the tag editor — both being the things that decide which policy rules apply to a node.
|
||||
|
||||
- [ ] **Users are create/delete/list only.** No rename (`/api/v1/user/{id}/rename/{newName}`).
|
||||
- [x] **User rename** — was already shipped end to end (`/users/:id/rename`, `UsersView`); this entry
|
||||
was stale when it was written.
|
||||
|
||||
- [ ] **Nothing refreshes.** No query in `useHeadscaleData.ts` polls, so a node going offline (or
|
||||
coming back) only appears on a manual reload. A modest `refetchInterval` on the nodes list is
|
||||
probably the whole fix.
|
||||
- [x] **Polling** — `useHeadscaleNodes` has had `refetchInterval: 20_000` all along; also stale.
|
||||
|
||||
## Known bugs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user