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

@ -38,18 +38,23 @@ let
inherit (stylixTheme) stylixThemeColors themedColorsCss;
# Repeated verbatim by every location that should be operator-gated
# (`/`, `/api/`, `/api/docs/`) rather than set once on the server:
# (`/`, `/api/docs/`; `/api/` takes `authRequest` with its own 401
# handler) rather than set once on the server:
# nginx's `auth_request` does NOT inherit across sibling locations, so
# setting it only on the page would leave this UI's own API and API
# docs reachable without a session.
swarmAuthRequest = ''
authRequest = ''
auth_request /__hive_authelia;
# Captured BEFORE the error_page jump: inside the 401 handler
# `$request_uri` is the internal one, so building the return
# link there sends the operator back to the auth subrequest
# instead of the page they asked for.
auth_request_set $target_url $scheme://$http_host$request_uri;
error_page 401 =302 https://${autheliaCfg.domain}/?rd=$target_url;
'';
loginRedirect = "https://${autheliaCfg.domain}/?rd=$target_url";
swarmAuthRequest = ''
${authRequest}
error_page 401 =302 ${loginRedirect};
'';
swarmCfg = config.services.hyperhive.swarm;
hiveDomain = config.services.hyperhive.domain;
@ -218,13 +223,24 @@ in
# `Remote-User` from authelia's answer, set here so it replaces
# any the client sent: swarm-controller takes it as the sender of
# the text the operator types to an agent (`agent_input.rs`).
# A refused caller that sent an `Authorization` header (`swarmctl
# --swarm`) gets the 401 itself; a browser still gets the login.
# The bearer token stops here and never reaches the controller.
extraConfig = ''
${swarmAuthRequest}
${authRequest}
error_page 401 = @swarm_api_unauthorized;
auth_request_set $authelia_user $upstream_http_remote_user;
proxy_set_header Remote-User $authelia_user;
proxy_set_header Authorization "";
'';
};
# ⚠️ THE ONE LOCATION ON THIS VHOST WITH NO `swarmAuthRequest`,
"@swarm_api_unauthorized".extraConfig = ''
if ($http_authorization) {
return 401;
}
return 302 ${loginRedirect};
'';
# ⚠️ THE ONE PROXIED LOCATION ON THIS VHOST WITH NO `auth_request`,
# and that is deliberate rather than an omission.
#
# A forge webhook is a machine POST carrying an HMAC signature