swarm: publish an authenticated gateway vhost for VictoriaLogs
Adds services.hyperhive.swarm.victorialogs.domain and a gateway vhost gated by the same auth_request check against authelia that swarm-uis own vhost uses (same swarmAuthRequest shape, copied not shared - see the file top comment for why). The stores own listener stays loopback-only and unauthenticated exactly as before; the collector still writes to it directly, never through this vhost, so this only adds a new authenticated read path. Registers the new domain in swarm.nixs serviceDomains so the swarm-services sub-CA issues for it (a missed entry silently falls back to the hive leaf, which cannot cover a name under a different apex - swarm.nixs own comment on that list documents the incident this caused before). Verified with a throwaway module-eval (same technique as the flake module-eval check): vhost only exists when victorialogs.enable is set, forceSSL/no addSSL, both locations present, domain correctly registered/absent from serviceDomains, links entry present.
This commit is contained in:
parent
2e43205fc3
commit
ed95e692e1
2 changed files with 104 additions and 7 deletions
|
|
@ -11,12 +11,16 @@
|
|||
# It is the collector that feeds this, not the services directly: one ingest
|
||||
# point per swarm, same shape as the metrics path.
|
||||
#
|
||||
# ⚠️ NO GATEWAY VHOST, and that omission is deliberate rather than unfinished.
|
||||
# VictoriaLogs' ingest and query endpoints are unauthenticated, exactly like
|
||||
# the metrics store's — and the metrics store *is* published under a
|
||||
# resolvable name, which is an open security question rather than a settled
|
||||
# design. Publishing this one the same way would repeat that before the first
|
||||
# instance is decided. The reader is Grafana, which is on this host.
|
||||
# Gateway vhost is authenticated, unlike the metrics store's: an operator
|
||||
# reaches this at `https://${cfg.domain}/` gated by the same `auth_request`
|
||||
# check against authelia that swarm-ui's own vhost uses (`swarmAuthRequest`
|
||||
# below, same shape as `swarm-ui.nix`'s) — see that file's copy for the full
|
||||
# rationale (forceSSL is load-bearing there too: authelia answers a plain-http
|
||||
# auth subrequest with 400, which `auth_request` cannot interpret as anything
|
||||
# but a broken check). The store's own listener stays loopback-only and
|
||||
# unauthenticated exactly as before — the collector still writes to it
|
||||
# directly, never through this vhost — so this adds a new authenticated front
|
||||
# door without touching the existing write path at all.
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
|
|
@ -27,6 +31,25 @@ let
|
|||
cfg = config.services.hyperhive.swarm.victorialogs;
|
||||
networkCfg = config.services.hyperhive.network;
|
||||
hyperhiveCfg = config.services.hyperhive;
|
||||
gatewayCfg = hyperhiveCfg.gateway;
|
||||
autheliaCfg = hyperhiveCfg.swarm.authelia;
|
||||
swarmDomain = hyperhiveCfg.swarm.domain;
|
||||
|
||||
# Total on a null swarm domain for the same reason every sibling module is:
|
||||
# the required-domain assertion in hive-network.nix should be what an
|
||||
# operator sees, not a coercion error from here.
|
||||
domainBase = if swarmDomain == null then "invalid" else swarmDomain;
|
||||
|
||||
# Copy of `swarm-ui.nix`'s own `swarmAuthRequest` — not shared code because
|
||||
# this vhost needs exactly the two locations that reference it (`/` and the
|
||||
# internal auth-request target below) and pulling in swarm-ui.nix for one
|
||||
# string would couple this module to swarm-ui existing at all, which it
|
||||
# need not. Same three lines, same reasoning as that file's own comment.
|
||||
swarmAuthRequest = ''
|
||||
auth_request /__hive_authelia;
|
||||
auth_request_set $target_url $scheme://$http_host$request_uri;
|
||||
error_page 401 =302 https://${autheliaCfg.domain}/?rd=$target_url;
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.services.hyperhive.swarm.victorialogs = {
|
||||
|
|
@ -59,6 +82,21 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "logs.${domainBase}";
|
||||
defaultText = lib.literalExpression ''"logs.''${services.hyperhive.swarm.domain}"'';
|
||||
description = ''
|
||||
Name the gateway serves this on, behind the same authelia
|
||||
`auth_request` gate as the swarm UI's own vhost. A sibling of the
|
||||
swarm's other service names, so the swarm-services sub-CA can
|
||||
issue for it — see `hive-tls.nix` for why a service name being a
|
||||
sibling rather than a child decides which CA may sign it, and
|
||||
`swarm.nix`'s `serviceDomains'` for where this name has to be
|
||||
registered for that to actually happen.
|
||||
'';
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 9428;
|
||||
|
|
@ -103,6 +141,58 @@ in
|
|||
# hosts are separate evaluations.
|
||||
services.hyperhive.swarm.otel.scrapeTargets.victorialogs = "127.0.0.1:${toString cfg.port}";
|
||||
|
||||
# The gateway name and the quick-link, both inside `cfg.enable` — same
|
||||
# "only the host that runs the service may claim the name" guard every
|
||||
# sibling swarm-service module uses (`swarm-grafana.nix`,
|
||||
# `swarm-victoriametrics.nix`).
|
||||
services.hyperhive.gateway.localNames = [ cfg.domain ];
|
||||
|
||||
services.hyperhive.swarm.controller.links = [
|
||||
{
|
||||
label = "Logs";
|
||||
icon = "📜";
|
||||
url = "https://${cfg.domain}/";
|
||||
}
|
||||
];
|
||||
|
||||
# Authenticated front door onto the loopback-only store — see the
|
||||
# file-top comment for why this is safe to add without touching the
|
||||
# store's own (still unauthenticated, still loopback) listener at all.
|
||||
# `removeAttrs`/`forceSSL`: same asymmetry `swarm-ui.nix` documents —
|
||||
# authelia answers a plain-http auth subrequest with 400, which
|
||||
# `auth_request` cannot read as anything but a broken check, so this
|
||||
# vhost needs `forceSSL` rather than the `addSSL` every unauthenticated
|
||||
# sibling vhost uses.
|
||||
services.nginx.virtualHosts."${cfg.domain}" =
|
||||
(builtins.removeAttrs (gatewayCfg.lib.tlsFor cfg.domain) [ "addSSL" ])
|
||||
// {
|
||||
forceSSL = true;
|
||||
listen = gatewayCfg.lib.listen;
|
||||
extraConfig = gatewayCfg.lib.securityHeaders;
|
||||
locations = {
|
||||
"/" = {
|
||||
proxyPass = "http://127.0.0.1:${toString cfg.port}/";
|
||||
extraConfig = swarmAuthRequest;
|
||||
};
|
||||
# The subrequest itself — same target, same header set, same
|
||||
# reasoning as `swarm-ui.nix`'s own copy (measured against the
|
||||
# pinned authelia binary, not copied from an example).
|
||||
"= /__hive_authelia" = {
|
||||
proxyPass = "http://127.0.0.1:${toString autheliaCfg.port}/api/authz/auth-request";
|
||||
extraConfig = ''
|
||||
internal;
|
||||
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;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
containers.${cfg.machine} = {
|
||||
autoStart = true;
|
||||
ephemeral = false;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,14 @@ let
|
|||
# failed every POST and dropped the samples.
|
||||
++ lib.optional swarmCfg.grafana.enable swarmCfg.grafana.domain
|
||||
++ lib.optional swarmCfg.victoriametrics.enable swarmCfg.victoriametrics.domain
|
||||
++ lib.optional swarmCfg.otel.enable swarmCfg.otel.domain;
|
||||
++ lib.optional swarmCfg.otel.enable swarmCfg.otel.domain
|
||||
# VictoriaLogs' vhost is new (was previously unpublished entirely — see
|
||||
# swarm-victorialogs.nix's file-top comment) and needs the same
|
||||
# membership every gateway-published swarm service needs: absent from
|
||||
# this list, `gateway.lib.tlsFor` falls back to the hive leaf, which
|
||||
# cannot cover a name under a different apex — see the ⚠️ above this
|
||||
# list for what that looked like the last time a name was missed here.
|
||||
++ lib.optional swarmCfg.victorialogs.enable swarmCfg.victorialogs.domain;
|
||||
|
||||
# Hives whose entry still carries the removed `certFingerprint`. Scanned
|
||||
# here, at top level, because that is the only place an assertion about a
|
||||
|
|
|
|||
Loading…
Reference in a new issue