gateway: serve the hive dashboard on <hive>.hives.<swarm> behind authelia, drop htpasswd
The dashboard, its API, the swagger UI and the agent routes move to their own vhost, `swarm.dashboardDomain` = `<hive>.hives.<swarm.domain>`, under the swarm domain so authelia's session cookie reaches it. `/`, `/api/` and `/api/docs/` ask authelia through `auth_request`, and authelia's `operatorsOnly` rule for each hive's dashboard name lets only the admins group in. The hive domain keeps matrix discovery and redirects every other path to the new name. The name rides as a SAN on the hive's own gateway leaf: the per-hive bao role allows it, and hive-tls asks for it and checks coverage. HTTP Basic auth is gone: the htpasswd writer and its test, the `hivectl gateway` verbs and host-socket requests, the `gateway.auth` options (now a removed-option error), the unauthorized page and the bcrypt dependency. tmpfiles deletes a leftover gateway.htpasswd, which held bcrypt hashes at 0644. c0re hands out dashboard and agent URLs on the new name (HYPERHIVE_DASHBOARD_DOMAIN), and the swarm controller's hive roster carries each hive's dashboard name for the swarm UI's hive list. Closes #3469
This commit is contained in:
parent
09ad7386b2
commit
92559af8e7
47 changed files with 351 additions and 623 deletions
|
|
@ -1,8 +1,10 @@
|
|||
# nginx virtual-host tree for the **hive's own** gateway surface: the `_`
|
||||
# default server, which answers unmatched `Host` with a bare 444, and the
|
||||
# vhost named for the hive domain, which carries the dashboard, per-agent
|
||||
# routing, matrix discovery, the swagger UI and the legacy
|
||||
# `<hive>/matrix/*` redirect.
|
||||
# default server, which answers unmatched `Host` with a bare 444; the
|
||||
# dashboard's vhost (`swarm.dashboardDomain`), which carries the dashboard
|
||||
# behind authelia, per-agent routing and the swagger UI; and the vhost
|
||||
# named for the hive domain, which carries matrix discovery, the legacy
|
||||
# `<hive>/matrix/*` redirect and a redirect of everything else to the
|
||||
# dashboard's name.
|
||||
#
|
||||
# Scope: a swarm service declares its own vhost and dns name in its own
|
||||
# module, built from the kit this file also consumes
|
||||
|
|
@ -18,10 +20,12 @@
|
|||
matrixCfg, # services.hyperhive.swarm.matrix — what the homeserver IS
|
||||
matrixDeployCfg, # services.hyperhive.deploy.matrix — what THIS host runs
|
||||
hyperhiveDomain,
|
||||
dashboardDomain, # services.hyperhive.swarm.dashboardDomain
|
||||
autheliaDomain, # services.hyperhive.swarm.authelia.domain
|
||||
runsHive, # deploy.hive-controller.enable — whether hive-c0re runs here
|
||||
dashboardDist,
|
||||
swaggerUiTheme, # nix/packages/swagger-ui-theme.nix: has index.html + hyperhive-theme.css
|
||||
errorPages, # ./error-pages.nix: { notFound, unreachable, unauthorized, ssoUnavailable }
|
||||
errorPages, # ./error-pages.nix: { notFound, unreachable, ssoUnavailable }
|
||||
gwLib, # `services.hyperhive.gateway.lib` — ./vhost-lib.nix's kit, via the option
|
||||
}:
|
||||
let
|
||||
|
|
@ -126,15 +130,19 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
# Shared auth block — separate locations don't inherit auth_basic, so
|
||||
# each dashboard location (`/`, `/api/`) needs it or that surface is
|
||||
# unauthed.
|
||||
dashboardAuth = lib.optionalString cfg.auth.enable ''
|
||||
auth_basic "${cfg.auth.realm}";
|
||||
auth_basic_user_file /var/lib/hive-gateway/conf/gateway.htpasswd;
|
||||
# `=401` keeps the status 401 so the login dialog shows; the
|
||||
# internal page explains `hivectl gateway create-user`.
|
||||
error_page 401 =401 /__hive_auth_unauthorized;
|
||||
# Repeated verbatim by every operator-gated location (`/`, `/api/`,
|
||||
# `/api/docs/`): nginx's `auth_request` does NOT inherit across sibling
|
||||
# locations, so a location without it is reachable with no session.
|
||||
#
|
||||
# ⚠️ `auth_request` answers "is there a session", not "is this an
|
||||
# operator". authelia's `operatorsOnly` rule for this name
|
||||
# (../swarm-authelia.nix) is what refuses an agent's own account.
|
||||
dashboardAuth = ''
|
||||
auth_request /__hive_authelia;
|
||||
# Captured BEFORE the error_page jump: inside the 401 handler
|
||||
# `$request_uri` is the internal one.
|
||||
auth_request_set $target_url $scheme://$http_host$request_uri;
|
||||
error_page 401 =302 https://${autheliaDomain}/?rd=$target_url;
|
||||
'';
|
||||
|
||||
# Dashboard: nginx static-serves the dist, c0re is API-only. Routing
|
||||
|
|
@ -167,8 +175,8 @@ let
|
|||
'';
|
||||
};
|
||||
"/health/" = {
|
||||
# No dashboardAuth here: an external uptime monitor generally can't do
|
||||
# interactive HTTP Basic. The endpoints themselves are scoped to
|
||||
# No dashboardAuth here: an external uptime monitor can't do an
|
||||
# interactive login. The endpoints themselves are scoped to
|
||||
# status + warning kind/message (see hive-c0re/src/dashboard/
|
||||
# health.rs) — no tokens, no agent detail — so exposing them
|
||||
# unauthenticated isn't a new secret surface.
|
||||
|
|
@ -206,6 +214,29 @@ let
|
|||
'';
|
||||
};
|
||||
};
|
||||
|
||||
# The subrequest `dashboardAuth` makes, the same as the swarm UI's
|
||||
# (../swarm-ui.nix), which documents the header set.
|
||||
autheliaLocation = {
|
||||
"= /__hive_authelia" = {
|
||||
proxyPass = "https://${autheliaDomain}/api/authz/auth-request";
|
||||
# nixpkgs appends its OWN `Host $host` after extraConfig, which would
|
||||
# override verifiedProxyTo's — see the comment on verifiedProxyTo in
|
||||
# ./vhost-lib.nix.
|
||||
recommendedProxySettings = false;
|
||||
extraConfig = ''
|
||||
internal;
|
||||
${gwLib.verifiedProxyTo autheliaDomain}
|
||||
proxy_pass_request_body off;
|
||||
proxy_set_header Content-Length "";
|
||||
proxy_set_header X-Original-Method $request_method;
|
||||
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
'';
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
virtualHosts = {
|
||||
|
|
@ -237,32 +268,26 @@ in
|
|||
'';
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs runsHive {
|
||||
# The hive domain's own vhost, reachable by NAME, so the catch-all can
|
||||
# reject everything else. Only where a hive runs: the dashboard, agent
|
||||
# routes and swagger UI proxy to hive-c0re, which runs only with the hive
|
||||
# controller, and the certificate naming the hive domain is the hive
|
||||
# leaf, which exists only there.
|
||||
${hyperhiveDomain} = (vhostTlsFor hyperhiveDomain) // {
|
||||
// lib.optionalAttrs (runsHive && dashboardDomain != null) {
|
||||
# The dashboard, reachable by its own NAME under the swarm domain, where
|
||||
# authelia's session cookie reaches. Only where a hive runs: the
|
||||
# dashboard, agent routes and swagger UI proxy to hive-c0re, which runs
|
||||
# only with the hive controller, and the hive leaf carrying this name
|
||||
# exists only there.
|
||||
#
|
||||
# The agent routes live here and not on the hive domain because the
|
||||
# agent page calls the dashboard's `/api/` on its own origin
|
||||
# (frontend/packages/agent/src/lib/dashboardBase.ts), and the dashboard
|
||||
# links to them by path. They carry no `auth_request`.
|
||||
#
|
||||
# `forceSSL` for the reason ../swarm-ui.nix gives: authelia answers the
|
||||
# subrequest for an `http://` target with 400, which `auth_request`
|
||||
# cannot read. `removeAttrs` because nixos asserts on a vhost declaring
|
||||
# both.
|
||||
${dashboardDomain} = (builtins.removeAttrs (vhostTlsFor dashboardDomain) [ "addSSL" ]) // {
|
||||
forceSSL = true;
|
||||
listen = vhostListen;
|
||||
locations =
|
||||
matrixRedirectLocations
|
||||
// wellKnownLocations
|
||||
// agentLocations
|
||||
// dashboardProxyLocation
|
||||
// swaggerUiLocations
|
||||
// lib.optionalAttrs cfg.auth.enable {
|
||||
# Internal-only target for the dashboard's 401 error_page.
|
||||
# `internal` prevents direct client access; `alias` serves
|
||||
# the pre-built HTML from the Nix store.
|
||||
"= /__hive_auth_unauthorized" = {
|
||||
extraConfig = ''
|
||||
internal;
|
||||
alias ${errorPages.unauthorized};
|
||||
default_type text/html;
|
||||
'';
|
||||
};
|
||||
};
|
||||
locations = agentLocations // dashboardProxyLocation // swaggerUiLocations // autheliaLocation;
|
||||
# Per-agent location blocks, generated at runtime by
|
||||
# hive-c0re and written to /var/lib/hive-gateway/conf/agents.conf
|
||||
# on the host — the same machine nginx runs on. nginx parses
|
||||
|
|
@ -275,5 +300,21 @@ in
|
|||
include /var/lib/hive-gateway/conf/agents.conf;
|
||||
'';
|
||||
};
|
||||
|
||||
# The hive domain's own vhost: matrix discovery, which federation
|
||||
# reads off the server name, and a redirect of every other path to the
|
||||
# dashboard's name, so a bookmarked dashboard or agent link still lands.
|
||||
${hyperhiveDomain} = (vhostTlsFor hyperhiveDomain) // {
|
||||
listen = vhostListen;
|
||||
locations =
|
||||
matrixRedirectLocations
|
||||
// wellKnownLocations
|
||||
// {
|
||||
"/".extraConfig = ''
|
||||
return 301 ${publicScheme}://${dashboardDomain}${publicPortSuffix}$request_uri;
|
||||
'';
|
||||
};
|
||||
extraConfig = securityHeaders;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue