swarm-authelia: admit only the collector anywhere on the metrics store's name
No access rule covered the push route on `metrics.<swarm>`, so a request there fell to `default_policy = one_factor`: any logged-in session cookie for the swarm could write samples into VictoriaMetrics. The name now gets two rules with no `resources`: the collector's client at `one_factor`, then `deny`. Only the push route and `/metrics` on that vhost send an authelia subrequest (the read API, Grafana's datasource included, is served with no check), and the collector is the one caller of both, so a domain-wide pair covers them with no path or query regex to slip past. It replaces the per-path `/metrics` pair the published target would otherwise derive for that name. Both rules sit ahead of the apex and store-UI rules, and authelia matches `domain` exactly, so they shadow no other name. Authelia 4.39.20, read from source: a rule with no `domain` wildcard matches with `strings.EqualFold` (access_control_domain.go), an empty `resources` or `subject` list matches everything (access_control_rule.go), and the first matching rule wins (authorizer.go). Fixes #5218
This commit is contained in:
parent
338a767a61
commit
223cca2b56
4 changed files with 108 additions and 39 deletions
|
|
@ -288,6 +288,57 @@ let
|
|||
&& locs."/".return == "403"
|
||||
&& locs."/".proxyPass == null;
|
||||
}
|
||||
{
|
||||
# The metrics store's name admits the collector's client on every path
|
||||
# and refuses every other subject. The rules carry no `resources`, so no
|
||||
# path or query spelling misses them and reaches `default_policy`, and
|
||||
# they come before the apex rule. That is only right while the push
|
||||
# route and `/metrics` are the vhost's sole authelia-gated locations,
|
||||
# which the last clause pins.
|
||||
name = "authelia admits only the collector anywhere on the metrics store's name";
|
||||
ok =
|
||||
let
|
||||
h = allLocal.services.hyperhive;
|
||||
collector = "oauth2:client:${h.swarm.otel.clientId}";
|
||||
rules =
|
||||
allLocal.containers.swarm-authelia.config.services.authelia.instances.swarm.settings.access_control.rules;
|
||||
indexed = lib.imap0 (i: r: r // { inherit i; }) rules;
|
||||
storeRules = lib.filter (r: r.domain == "metrics.t.local") indexed;
|
||||
apex = lib.findFirst (r: r.domain == "t.local") null indexed;
|
||||
policy = subject: autheliaPolicy allLocal subject "metrics.t.local";
|
||||
paths = [
|
||||
"/opentelemetry/api/v1/push"
|
||||
"/metrics"
|
||||
"/metrics?x=1"
|
||||
"/api/v1/query"
|
||||
];
|
||||
locs = allLocal.services.nginx.virtualHosts.${h.swarm.victoriametrics.domain}.locations;
|
||||
in
|
||||
map (r: removeAttrs r [ "i" ]) storeRules == [
|
||||
{
|
||||
domain = "metrics.t.local";
|
||||
policy = "one_factor";
|
||||
subject = [ collector ];
|
||||
}
|
||||
{
|
||||
domain = "metrics.t.local";
|
||||
policy = "deny";
|
||||
}
|
||||
]
|
||||
&& apex != null
|
||||
&& lib.all (r: r.i < apex.i) storeRules
|
||||
&& lib.all (p: policy collector p == "one_factor") paths
|
||||
&& lib.all (p: policy "user:someone" p == "deny") paths
|
||||
&& lib.all (p: policy "group:admins" p == "deny") paths
|
||||
&&
|
||||
lib.hasInfix "auth_request /__metrics_push_authz;"
|
||||
locs."= /opentelemetry/api/v1/push".extraConfig
|
||||
&&
|
||||
lib.attrNames (lib.filterAttrs (_: l: lib.hasInfix "auth_request" (l.extraConfig or "")) locs) == [
|
||||
"= /metrics"
|
||||
"= /opentelemetry/api/v1/push"
|
||||
];
|
||||
}
|
||||
{
|
||||
# Every published target admits the collector's client and refuses any
|
||||
# other authenticated subject, with or without a query string. Read
|
||||
|
|
@ -322,7 +373,7 @@ let
|
|||
forgeMetricsUrl
|
||||
vmMetricsUrl
|
||||
]
|
||||
&& lib.length targets == 4
|
||||
&& lib.length targets == 6
|
||||
&& lib.all (t: policy collector t == "one_factor") targets
|
||||
&& lib.all (t: policy "user:someone" t == "deny") targets
|
||||
&& lib.all (t: policy "group:admins" t == "deny") targets;
|
||||
|
|
|
|||
Loading…
Reference in a new issue