docs(swarm): add the swarm-nats-auth package readme
Every other crate has one and Cargo.toml already named it. Follows the sibling shape: what it is, why it is a crate rather than more config, the invariant that is easy to break (issuer_account must be absent, and Token::new_user reintroduces it), and what the tests do NOT cover - the introspection endpoint under test is a stub, so nothing here says anything about the real authelia integration.
This commit is contained in:
parent
b22f0ecaa1
commit
5be2918559
1 changed files with 66 additions and 0 deletions
66
swarm-nats-auth/README.md
Normal file
66
swarm-nats-auth/README.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# swarm-nats-auth
|
||||
|
||||
The auth-callout responder for the swarm's NATS queue — the half that lets
|
||||
the server say *yes*.
|
||||
|
||||
`nix/host-modules/swarm-nats.nix` configures `nats-server` with an
|
||||
`auth_callout` block. **That block with no responder is the fail-closed
|
||||
state**, and it is the measured one rather than the obvious one: on the pinned
|
||||
nats-server, both `authorization { }` and `authorization { users: [] }` answer
|
||||
PONG to an anonymous client, while an `auth_callout` block sets
|
||||
`auth_required` and refuses every client no responder has approved. So the
|
||||
module ships the final config from the start and this crate only adds the
|
||||
ability to approve. No interim hole is ever opened.
|
||||
|
||||
## Why a crate and not more config
|
||||
|
||||
The reply is a **signed NATS user JWT**. Signing needs the account nkey seed
|
||||
and the JWT framing, which is program work — which is why the container and
|
||||
its config landed first and this arrived separately, rather than the pair
|
||||
being one change.
|
||||
|
||||
## The invariant that is easy to break
|
||||
|
||||
⛔ **`issuer_account` must be absent from the issued user token.** It is an
|
||||
operator-mode field. The module renders *server-config* mode (`accounts
|
||||
{ AUTH, APP }`, no operator), where its mere presence makes the server refuse
|
||||
the client — `Error non operator mode account "AUTH": attempted to use
|
||||
issuer_account` — while the responder cheerfully reports `granted=true`. The
|
||||
account is named by the claims' `aud` instead.
|
||||
|
||||
`nats_jwt::Token::new_user` always sets it, so reaching for that constructor
|
||||
reintroduces the bug. `nats-jwt` is a **dev-dependency**: it cannot express
|
||||
`aud` on either token, and its role here is as the encoder's *test oracle*,
|
||||
not part of the path that runs.
|
||||
|
||||
## Rules the code follows
|
||||
|
||||
- **A denial is a signed response carrying an error, never silence.** The
|
||||
server cannot tell an absent responder from a refusing one, so staying quiet
|
||||
turns every rejection into a timeout and hides an outage inside what looks
|
||||
like ordinary denials.
|
||||
- **Anything that is not an explicit `{"active": true}` denies** — including
|
||||
an introspection call that could not be *made*. The failure modes of an HTTP
|
||||
call are exactly the conditions under which an attacker would most like this
|
||||
to fall open.
|
||||
- **Every credential is a path, never a value.** A value in nix config lands
|
||||
in the world-readable store; a value in `argv` is readable via
|
||||
`/proc/<pid>/cmdline`, which is `0444`. Paths are not secrets, so passing
|
||||
them as flags is fine.
|
||||
- **The introspection timeout is pinned below the server's `authorization.
|
||||
timeout`**, with a test asserting the relation — a responder that answers
|
||||
after the server gave up is indistinguishable from one that never answered.
|
||||
|
||||
## What the tests do and do not cover
|
||||
|
||||
Unit tests cover the JWT framing, including byte-equality against `nats-jwt`
|
||||
on the one shape that crate models. They are **not** sufficient on their own:
|
||||
this crate's shape is decided by a server that parses what it emits, so the
|
||||
change was also driven against a real `nats-server` — every refusal repeated
|
||||
*with the responder live*, because a responder that says yes to everyone
|
||||
passes "a client can connect" perfectly. That harness lives outside this repo;
|
||||
the PR that added this crate links it.
|
||||
|
||||
⚠️ **Its introspection endpoint is a stub.** Those runs prove this crate's own
|
||||
behaviour and nothing about the real authelia integration, which needs a
|
||||
deployment.
|
||||
Loading…
Reference in a new issue