labctl breaks behind an SSO/auth proxy — session LAB_URL follows --base-url out through Authelia #30
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab#30
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
labctlfails entirely when lab runs behind an SSO/auth proxy (nginx + Authelia header-auth, as in thelab.cloonar.comproduction deploy). Everylabctlcall inside an agent session gets a 302 to the SSO login page; labctl follows the redirect, receives the HTML login page, and tries to parse it as JSON — failing withinvalid character '<' looking for beginning of value. The run's only tracker surface (D10) is dead in that deployment.Current behavior (confirmed by code trace)
--base-url(LAB_BASE_URL) to the external HTTPS origin — required for Secure cookies and the CSRF Origin check.LAB_URLhanded to every spawned session is derived from--base-url: thelabURL()helper in thelabcommand returns the base URL verbatim when it is set. So sessions getLAB_URL=https://lab.cloonar.com(the public FQDN).labctl issue listtherefore issuesGET https://lab.cloonar.com/agent/v1/issueswith anAuthorization: Bearer lab_run_…header. That request leaves the lab host and hairpins out to the public reverse proxy./agent/v1/) through Authelia (auth_request). Authelia has no concept of lab run tokens, sees no SSO session cookie, and returns 401 → the proxy maps that to a 302 to the Authelia login portal.http.Clientwith noCheckRedirect, so it follows the 302, gets200 OK+ an HTML login page, andjson.Unmarshalchokes on the leading<.Root cause
--base-urldoes double duty: it is both (a) the browser-facing origin (must be the externalhttps://URL) and (b) the source ofLAB_URLfor machine-to-machine traffic (which should never touch the human SSO gateway). Behind an auth proxy these two roles conflict.The agent API (
/agent/v1) is designed to self-authenticate with run tokens only — no cookies, no CSRF — and is deliberately mounted outside the operator auth/CSRF middleware. Routing it through Authelia is therefore wrong by construction: machine traffic should stay on loopback and never reach the proxy.Chosen fix (approach A + C — decided with the maintainer)
Decouple the session-facing URL from
--base-urland default machine traffic to loopback, plus harden labctl so a proxied misconfig fails loudly instead of parsing HTML as JSON.Explicitly rejected alternative: an nginx carve-out that exposes
/agent/v1/unauthenticated on the public vhost. It works but hairpins traffic and widens the public attack surface; loopback is strictly better.Agent Brief
Category: bug
Summary: Give lab a dedicated session-facing URL so
labctltalks to the server directly (loopback by default) instead of being forced out through the external--base-urland its SSO/auth proxy; harden labctl to fail clearly on redirects.Current behavior:
The
LAB_URLvalue injected into every spawned agent session is derived from--base-url: when--base-urlis set, sessions receive that external URL. Behind an authenticating reverse proxy this routes labctl's run-token-authenticated/agent/v1calls through the SSO gateway, which 302-redirects them to a login page. labctl's HTTP client follows redirects and then fails to decode the HTML login page as JSON, so every labctl command breaks.Desired behavior:
--base-url. Precedence for the value handed to sessions asLAB_URLbecomes: the new agent-URL knob if set, else--base-urlif set, elsehttp://127.0.0.1:<listen-port>(unchanged fallback).LAB_URLmay be pointed at an auth proxy — instead ofinvalid character '<'.--base-urlkeeps its existing role for Secure-cookie detection and the CSRF Origin check, unchanged.Key interfaces:
--agent-url/LAB_AGENT_URL, resolved in the config parser with the standard flag > env > default precedence and validated as an absolutehttp(s)URL exactly like--base-url(reject relative/other-scheme values at startup with a clear message). Surface it on theConfigstruct.LAB_URL(currentlylabURL()in thelabcommand): change its precedence to agent-URL → base-URL →http://127.0.0.1:<port>.Client(itsdomethod): sethttp.Client.CheckRedirectto returnhttp.ErrUseLastResponse, and when the response status is a redirect (or the payload clearly isn't the JSON error/OK envelope), return an error that includes theLocationand the "is LAB_URL behind an auth proxy?" hint.services.lab: add anagentUrloption (nullable string). Default it to a loopback URL derived from the listen port already computed in the module (http://127.0.0.1:<listenPort>), and pass it through as--agent-url. Document that it exists so machine traffic bypasses any front proxy; the operator can override it if sessions run off-host.Acceptance criteria:
labaccepts--agent-url/LAB_AGENT_URL; an invalid value (relative or non-http(s)) is rejected at startup with a clear error, mirroring--base-urlvalidation.LAB_URL. With it unset, behavior is unchanged:--base-urlwhen set, elsehttp://127.0.0.1:<port>.Locationand thatLAB_URLmay be behind an auth proxy, rather than a JSON-decode error on HTML.services.lab.agentUrl, defaulting to a loopback URL derived fromlistenAddr, and passes--agent-urlto the unit — so a proxy-fronted deployment keepslabctltraffic on loopback with no extra operator config.nix flake check(go test + golangci-lint) passes.Out of scope:
revinutils/modules/coding-lab/default.nix, and optionally settingservices.lab.agentUrl). That lives in the cloonar-nixos repo and is a follow-up once this change lands and is available at a rev./agent/v1/unauthenticated on the public vhost).--base-url's role in Secure-cookie / CSRF handling.