Watch
0
0
Fork
You've already forked hyperhive
0

swarmctl: log in with the operator's own SSO account and reach the controller through the swarm domain

`swarmctl --swarm https://<swarm> login` runs the OAuth 2.0
authorization-code flow with PKCE against the swarm's authelia as a new
public `swarmctl` client: a pushed authorization request, the browser login
and explicit consent, and the code delivered by form_post to a loopback
port. The access and refresh tokens are cached at mode 0600 under
$XDG_STATE_HOME/swarmctl/ and refreshed under a file lock, since authelia
rotates the refresh token and revokes the login when a used one is
presented again. With --swarm (or SWARMCTL_SWARM), the agent, schedule and
forge verbs send their requests to https://<swarm>/api/ with the bearer
token instead of over the controller's unix socket, which stays the default.

authelia registers the client on every authelia host with a swarm domain:
`kind = "native"`, public, no minted secret, scopes offline_access and
authelia.bearer.authz, audience https://<swarm>/api/ (matched as a URL
prefix), redirect http://127.0.0.1/callback (any loopback port). Every
field is one authelia 4.39.20's validator requires for a public client
holding the bearer scope.

The swarm apex's /api/ answers a refused caller that sent an
Authorization header with the 401 itself instead of the login redirect,
so the CLI can tell "log in again" apart from an answer; a browser still
gets the redirect. The bearer token is not forwarded to the controller.

Refs #4409 (mara: c94764, c94782).
This commit is contained in:
atlas 2026-10-11 23:43:29 +02:00 • committed by mara
commit 2df19e4899
17 changed files with 1118 additions and 122 deletions

View file

@ -1,7 +1,9 @@
# swarmctl
Swarm-level operator CLI. Runs as **root on the host running
`swarm-controller`**, and acts on that host directly.
`swarm-controller`**, and acts on that host directly; or, for the verbs
that talk to the controller, from anywhere as your own SSO account (see
[Remote use](#remote-use-swarmctl-login)).
Distinct from `hivectl`, which drives one hive's `hive-c0re` over its
admin socket. This crate does not link `swarm-controller`, for the same
@ -19,15 +21,11 @@ allocated inside it, at activation — see the uid-assignment issue) or
world-readable password hashes. Both are worse than root.
So this binary **serves** no socket, publishes no HTTP route and has no
privileged helper. 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.
privileged helper.
That rules out a way in, not a way out: `agent create` is a _client_ of
the controller's socket, because the work it asks for is a job graph only
the controller can queue (see below). Nothing here becomes reachable by
that.
the controller, because the work it asks for is a job graph only the
controller can queue (see below). Nothing here becomes reachable by that.
## One file, two writers
@ -53,6 +51,8 @@ an error.
| `SWARMCTL_AUTHELIA_MACHINE` | container name, for `systemctl -M` |
| `SWARMCTL_AUTHELIA_UNIT` | authelia's unit inside that container |
| `SWARM_CONTROLLER_SOCKET` | the controller's unix socket, for `agent` and `forge` verbs |
| `SWARMCTL_SWARM` | `https://<swarm domain>`: reach the controller there, not the socket |
| `SWARMCTL_CA_FILE` | a PEM CA to trust beside the system roots, for `SWARMCTL_SWARM` |
Every verb and flag: [`docs/tools/swarmctl-cli.md`](../docs/tools/swarmctl-cli.md).
The sections below cover why each verb behaves as it does.
@ -102,8 +102,9 @@ the swarm UI's job view for the rest.
The new agent starts `paused`: it doesn't drive turns until you set it `up`
in the swarm UI.
No approval gate, for the same reason nothing else here has one: running
this binary already means being root on the controller's host.
No approval gate, for the same reason nothing else here has one: the
caller is root on the controller's host, or logged in through the swarm
domain's SSO gate.
`--hive` is required. It is an _address_ — where the deploy message goes
— not an attribute of the agent, so there is no sensible default. The
@ -169,3 +170,27 @@ same socket as `agent create`; each verb waits for the stored result. What a
schedule does once stored is in
[`docs/swarm/schedules.md`](../docs/swarm/schedules.md). The shapes are
mirrored in `src/schedule.rs`, for the same reason as `agent create`'s.
## Remote use: `swarmctl login`
The `agent`, `schedule` and `forge` verbs also work from any machine, as
your own SSO account, through the swarm domain's gateway:
```console
$ export SWARMCTL_SWARM=https://swarm.example.com
$ swarmctl login
Log in to https://swarm.example.com/ in your browser:
…
logged in to https://swarm.example.com/; login kept in ~/.local/state/swarmctl/swarm.example.com.json
$ swarmctl schedule list
```
`login` opens the swarm's SSO login in a browser and receives the result on
a loopback port, so run it on the machine whose browser you use. swarmctl
keeps the login, mode 0600, under `$XDG_STATE_HOME/swarmctl/` (default
`~/.local/state`) and refreshes it as it's used. When it can't be refreshed,
any verb says to run `swarmctl login` again. To log out, delete the file.
A swarm whose gateway serves a certificate from the swarm's own CA needs
that CA: `--ca-file` or `SWARMCTL_CA_FILE`. The `user` verbs edit the local
users file and have no remote form.