Watch
0
0
Fork
You've already forked hyperhive
0

swarm-victoriametrics: forward only the store's read API

The metrics vhost's `/` proxied every path to VictoriaMetrics with no
auth_request, so anything that could reach metrics.<swarm> could write
(`/api/v1/import`, `/api/v1/write`, ...) and delete
(`/api/v1/admin/tsdb/delete_series`) series. Only the OTLP push route
was gated.

`/` now answers 403. The read routes Grafana's Prometheus datasource
and vmui use are forwarded by exact or `^~` prefix location, each with
the matched path as its proxy_pass URI. The bare domain, which the
swarm UI's Metrics quick link opens, redirects to vmui instead of the
store's index page. The OTLP push location is unchanged.

Closes #5186
This commit is contained in:
atlas 2026-10-13 01:45:55 +02:00 • committed by mara
commit 53d0d59be0
3 changed files with 94 additions and 8 deletions

View file

@ -25,6 +25,51 @@ let
# Shared host netns, like every sibling swarm container: the gateway
# reaches this at 127.0.0.1:<port>.
privateNetwork = false;
# The store's read API: the only part of it the vhost serves without a
# credential. Each location's `proxy_pass` carries the path, so the store
# receives the path nginx matched, not the caller's unnormalised spelling.
#
# Grafana's Prometheus datasource reads the query, series, label, metadata,
# buildinfo, exemplar, rules and alerts routes; the store answers the last
# three with fixed placeholders. vmui is the static files under `/vmui/`
# plus the query API and the remaining read-only routes here. Paths read off
# the pinned build's `app/vmselect/main.go` and vmui's `src/`.
readPaths = [
"/api/v1/query"
"/api/v1/query_range"
"/api/v1/query_exemplars"
"/api/v1/series"
"/api/v1/labels"
"/api/v1/metadata"
"/api/v1/rules"
"/api/v1/alerts"
"/api/v1/status/buildinfo"
"/api/v1/export"
"/api/v1/export/csv"
"/api/v1/status/tsdb"
"/api/v1/status/metric_names_stats"
"/api/v1/status/top_queries"
"/api/v1/status/active_queries"
"/expand-with-exprs"
"/prettify-query"
"/metric-relabel-debug"
"/vmui"
];
# The store serves only `<name>/values` under `/api/v1/label/`, and only
# static files and UI config under `/vmui/`.
readPrefixes = [
"/api/v1/label/"
"/vmui/"
];
readLocations =
let
to = path: { proxyPass = "http://127.0.0.1:${toString cfg.port}${path}"; };
in
lib.listToAttrs (
map (p: lib.nameValuePair "= ${p}" (to p)) readPaths
++ map (p: lib.nameValuePair "^~ ${p}" (to p)) readPrefixes
);
in
{
# What the store IS from any hive's point of view, the name it answers on and
@ -90,14 +135,19 @@ in
services.nginx.virtualHosts."${cfg.domain}" = (gatewayCfg.lib.tlsFor cfg.domain) // {
listen = gatewayCfg.lib.listen;
extraConfig = gatewayCfg.lib.securityHeaders;
locations = {
"/" = {
proxyPass = "http://127.0.0.1:${toString cfg.port}/";
};
locations = readLocations // {
# Nothing reaches the store unless a location here names it. The
# store serves writes and admin calls (`/api/v1/import`,
# `/api/v1/admin/tsdb/delete_series`, ...) on the same listener as
# its queries, and authenticates none of them.
"/".return = "403";
# The swarm UI's quick link opens the bare domain. The store's own
# index page links mostly to paths refused above, so send it to vmui.
"= /".return = "302 /vmui/";
# The swarm collector's ingest route, and the only authenticated thing
# on this vhost. `=` so it outranks the `/` prefix above, which would
# otherwise carry these writes with no check at all.
# on this vhost. `=` so it outranks the `/` prefix above.
#
# ⚠️ No `error_page 401 =302` here, and its absence is the point: a
# redirect is right for a browser and wrong for a pusher, which would