The swarm-authelia module states that its users database is written by swarm-controller, but nothing ever granted the means. This adds the tool that does it. swarmctl runs as root on the controller's host and acts directly. The rootless alternative was examined and does not work: relocating the users file into a directory the controller owns only turns a write problem into a read problem, because authelia must then reach across the same boundary in the other direction. Making that read work needs either a hand-pinned gid or world-readable password hashes. The user store is two files, one authoritative: users.json is canonical, users.yml is a rendered artifact. That split is what lets the crate work without a YAML parser -- the workspace has none, and adding one costs a crates.io fetch, a lock update and a vendor hash for a schema we fully control and only ever emit. Passwords are generated by authelia rather than passed to it: argv is world-readable, so a password on a command line is readable by any local process for the lifetime of the call. The three derived facts swarmctl needs about the authelia container -- machine, unit and the host-side users path -- become readOnly options on the authelia module rather than literals repeated at the call site.
67 lines
2.9 KiB
Markdown
67 lines
2.9 KiB
Markdown
# swarmctl
|
|
|
|
Swarm-level operator CLI. Runs as **root on the host running
|
|
`swarm-controller`**, and acts on that host directly.
|
|
|
|
Distinct from `hivectl`, which drives one hive's `hive-c0re` over its
|
|
admin socket. This crate does not link `swarm-controller`, for the same
|
|
reason `hivectl` does not link `hive-c0re`.
|
|
|
|
## Why root, and why no socket
|
|
|
|
The first verb writes authelia's users database. Making that write
|
|
rootless was examined and rejected — **relocating the file only turns a
|
|
write problem into a read problem.** Move `users.yml` into a directory
|
|
the controller owns and the controller can write it, but authelia then
|
|
has to read it across the same boundary in the other direction. Making
|
|
*that* work needs either a hand-pinned gid (the container's uids are
|
|
allocated inside it, at activation — see the uid-assignment issue) or
|
|
world-readable password hashes. Both are worse than root.
|
|
|
|
So there is no socket, no HTTP route and no privileged helper here. When
|
|
a verb has to run as a non-root user or from another host, the answer is
|
|
a **group-gated admin socket**, separate from the controller's `0666`
|
|
gateway-facing one — not a widening of what root does here.
|
|
|
|
## Two files, one of them authoritative
|
|
|
|
- `users.json` — canonical, ours, JSON.
|
|
- `users.yml` — a **rendered artifact** for authelia. Written, never read
|
|
back.
|
|
|
|
The split is what lets this crate work without a YAML parser: the
|
|
workspace has none, and adding one costs a crates.io fetch, a lock update
|
|
and a vendor hash for a schema we fully control and only ever emit.
|
|
|
|
The shortcut of writing JSON into the `.yml` (JSON being a subset of
|
|
YAML) is deliberately not taken: authelia refuses to start on a users file
|
|
it cannot parse, so that file fronts the whole SSO provider's boot, and
|
|
"almost certainly parses" is not a claim worth betting a boot on without
|
|
running it.
|
|
|
|
## Configuration
|
|
|
|
Every path comes from the nix module that installs the binary, because
|
|
every one is derived from an option that module owns. They are required
|
|
rather than defaulted — a default would be an address we *hope* points at
|
|
something, and one that resolves cleanly to the wrong place is worse than
|
|
an error.
|
|
|
|
| variable | what |
|
|
|---|---|
|
|
| `SWARMCTL_AUTHELIA_BIN` | the **configured** authelia; argon2 params must match the verifier's |
|
|
| `SWARMCTL_AUTHELIA_USERS_FILE` | host-side path of the users database |
|
|
| `SWARMCTL_AUTHELIA_MACHINE` | container name, for `systemctl -M` |
|
|
| `SWARMCTL_AUTHELIA_UNIT` | authelia's unit inside that container |
|
|
| `SWARMCTL_STORE` | canonical store (defaults to the controller's state dir) |
|
|
|
|
## Usage
|
|
|
|
```console
|
|
# swarmctl user add mara --display-name "Mara" --group admins
|
|
```
|
|
|
|
The password is **generated by authelia** (`crypto hash generate argon2
|
|
--random`) and printed once. It is never passed on a command line:
|
|
`/proc/<pid>/cmdline` is world-readable, so a password in argv is readable
|
|
by any local process for the lifetime of the call.
|