Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

..
Author SHA1 Message Date
atlas
fde4a36b93 module-eval: pin the log store's machine query route and its absences
Three cases beside the existing ingest ones, since the read route has the
same trap and one property of its own.

- the route exists and carries `auth_request`;
- it has no login fallback — with the browser location as the positive
  control, so a pass means the two routes differ rather than that
  `error_page` and the login host are absent from the whole vhost;
- it injects no filter, on `extraConfig` and on `proxyPass` both, because
  VictoriaLogs takes its filters as request parameters and those ride an
  upstream URI as easily as a directive. A filter arriving later is then a
  visible diff here rather than a quiet change of rule.

Each absence arm leads with a clause that proves the location resolved and
that `hasInfix` finds what is really in that string, so "not there" cannot
be read off an unreadable path.

Refs #3870
2026-09-13 14:36:15 +02:00
atlas
d275238dc4 swarm-victorialogs: add a machine-facing query location on the gateway
An agent can reach the log store only through the gateway, and the only
location that exists for a reader is `/`, which is the operator's browser
route. That route ends in `error_page 401 =302`, and an unauthenticated
caller which follows the redirect gets authelia's login page as HTTP 200
with an HTML body — so a client that checks the status code records a query
that succeeded and matched no logs. Measured on the live gateway; it is the
root cause behind four operator round-trips already.

So the query API gets its own location, `^~ /select/logsql/`, with bare
`auth_request` and no login fallback: an unauthenticated caller gets a 401
it cannot mistake for an empty result. `^~` keeps it ahead of the `/`
catch-all and of any regex location added later, and it stops short of
`/select/vmui/`, which is the browser UI and stays on the browser route.

The query is forwarded unmodified — no filter parameter is injected, so any
authenticated caller reads the whole swarm's logs. That is the rule mara
set: read permissions are a later thing, and this location is where one
attaches when it exists.

Refs #3870
2026-09-13 14:36:15 +02:00
2 changed files with 89 additions and 0 deletions

View file

@ -24,6 +24,10 @@
# ⚠️ That ingest location deliberately does not carry `swarmAuthRequest` — a # ⚠️ That ingest location deliberately does not carry `swarmAuthRequest` — a
# pusher handed its `error_page 401 =302` follows the redirect and POSTs at a # pusher handed its `error_page 401 =302` follows the redirect and POSTs at a
# login page, which answers 200. See the location itself. # login page, which answers 200. See the location itself.
#
# The query API has a machine route of the same shape at `^~ /select/logsql/`,
# for the same reason, and with no filtering of what an authenticated caller
# may read — see that location.
{ {
pkgs, pkgs,
lib, lib,
@ -202,6 +206,37 @@ in
auth_request /__hive_authelia; auth_request /__hive_authelia;
''; '';
}; };
# The read side of that same split: the route a caller holding a
# bearer token queries, as opposed to the `/` above which is the
# operator's browser. Bare `auth_request` for the ingest route's
# exact reason — `error_page 401 =302` hands an unauthenticated
# machine caller authelia's login page as a **200 with an HTML
# body**, so a client that reads the status code records a
# successful query that returned no rows. 401 is the only answer
# here that a caller cannot mistake for an empty result.
#
# `^~` so it outranks the `/` catch-all and stays ahead of any
# regex location a later change adds. The prefix is the whole
# LogsQL query API and nothing else — `query`, `tail`, `hits`,
# `facets`, the `stats_query*` and the `field_*`/`stream_*`
# routes — while the browser UI sits beside it under
# `/select/vmui`. Both read off the pinned build, not the docs.
#
# The query is forwarded unmodified — no scoping parameter is
# injected, so any authenticated caller reads the whole swarm's
# logs. That is the rule in force, not an omission: read
# permissions are a later thing, and this location is where one
# attaches when it exists.
"^~ /select/logsql/" = {
# No URI part, so the request path and its query string reach
# the store as the caller sent them.
proxyPass = "http://127.0.0.1:${toString cfg.port}";
extraConfig = ''
auth_request /__hive_authelia;
'';
};
# The subrequest itself — same target, same header set, same # The subrequest itself — same target, same header set, same
# reasoning as `swarm-ui.nix`'s own copy (measured against the # reasoning as `swarm-ui.nix`'s own copy (measured against the
# pinned authelia binary, not copied from an example). # pinned authelia binary, not copied from an example).

View file

@ -1785,6 +1785,60 @@ let
&& !(lib.hasInfix "error_page" l) && !(lib.hasInfix "error_page" l)
&& lib.hasInfix "error_page" browser; && lib.hasInfix "error_page" browser;
} }
{
# The read counterpart to the ingest location: an agent queries the log
# store with a bearer token, and the `/` catch-all is the browser's
# route. Riding it would mean inheriting the login redirect the next
# case is about, so the route has to exist separately to be gated
# separately.
name = "the log store's vhost has an authenticated machine query location";
ok =
let
q = allLocal.services.nginx.virtualHosts."logs.t.local".locations."^~ /select/logsql/" or null;
in
q != null && lib.hasInfix "auth_request" q.extraConfig;
}
{
# Same trap as the ingest case, on the read side, where it is worse: a
# redirected pusher at least stores nothing visibly, while a redirected
# *reader* is handed a 200 carrying login HTML and records a query that
# succeeded and matched no logs. The browser clause is the positive
# control — that location really does redirect to the login host — so a
# pass means these two routes differ rather than that the strings are
# absent from the whole vhost.
name = "the machine query location answers 401 instead of redirecting to a login page";
ok =
let
v = allLocal.services.nginx.virtualHosts;
q = v."logs.t.local".locations."^~ /select/logsql/".extraConfig;
browser = v."logs.t.local".locations."/".extraConfig;
in
!(lib.hasInfix "error_page" q)
&& !(lib.hasInfix "auth.t.local" q)
&& lib.hasInfix "error_page" browser
&& lib.hasInfix "auth.t.local" browser;
}
{
# Read access is deliberately unscoped: an authenticated caller reads
# the whole swarm's logs until a permission system exists. Pinned so a
# scoping parameter arriving later is a visible diff here rather than a
# quiet change of rule — and pinned on `proxyPass` too, because
# VictoriaLogs takes its filters as request parameters, which ride an
# upstream URI as easily as a directive. The first clause is the
# control: it proves the location resolved and that `hasInfix` finds
# what is genuinely in this string, so the absences below mean absent
# rather than unreadable.
name = "the machine query location forwards the caller's query unmodified";
ok =
let
q = allLocal.services.nginx.virtualHosts."logs.t.local".locations."^~ /select/logsql/";
in
lib.hasInfix "auth_request" q.extraConfig
&& !(lib.hasInfix "extra_filters" q.extraConfig)
&& !(lib.hasInfix "extra_stream_filters" q.extraConfig)
&& !(lib.hasInfix "$args" q.extraConfig)
&& !(lib.hasInfix "?" q.proxyPass);
}
{ {
# Defining an exporter and REFERENCING it are two separate lists, and # Defining an exporter and REFERENCING it are two separate lists, and
# the second is where the original gate also lived. An exporter no # the second is where the original gate also lived. An exporter no