otel: a hive always authenticates — drop the unauthenticated mode

mara, reviewing this PR: "hives always require an identity, swarm controller
and auth is not optional."

So `requireHiveIdentity` is gone rather than defaulted, and with it every
branch that had to describe an unauthenticated collector. The swarm tier now
serves per-hive receivers only, and `/` answers 404 because there is no
swarm-wide inbox to route to. A hive with no credential is a build error, not
a quieter mode.

`hivePortBase` goes too: with per-hive receivers unconditional, `port` IS the
base of the range. That keeps one documented knob instead of adding a second,
and its advice ("move it if something else claims that range") still holds.

Two assertions replace the toggle — an empty hive roster, and a null
`authelia.url`. The second matters because a guessed issuer URL evaluates
cleanly, deploys cleanly, and then refuses every hive at runtime.

⚠️ `cfg.port` is deliberately no longer compared against the derived range in
the collision assertion: it is now the range's first element, so listing it
would make that assertion fire on every config.

This also retires the asymmetry guard added earlier in review — the state it
protected against (auth off on one side, credential still set on the other)
is no longer representable.
This commit is contained in:
atlas 2026-08-19 15:22:29 +02:00 committed by mara
commit 9bd2b9e9e6
3 changed files with 107 additions and 192 deletions

View file

@ -255,7 +255,11 @@ in
swarmName = "otlphttp/swarm";
authName = "oauth2client/swarm";
# Holding a credential IS the condition — see `clientSecretFile`.
# A hive always authenticates to the swarm's collector as itself, so
# this is not a mode — it is a precondition, and the assertion below
# is what enforces it. Kept as a name because several places have to
# read "do we have what it takes", and an eval error from a null path
# names this file rather than the option an operator has to set.
senderAuth = otel.clientSecretFile != null && hiveName != null;
# This hive's client id, and also the audience it must ASK for. Both
@ -382,60 +386,28 @@ in
assertions = [
{
# Only checkable on a host that runs BOTH tiers — which is the
# deployment where it can actually go wrong silently. A remote
# hive cannot see the swarm tier's config at all, so its operator
# sets the path explicitly and this says nothing.
assertion = !(swarmOtelCfg.enable && swarmOtelCfg.requireHiveIdentity) || senderAuth;
# A hive authenticates to the swarm's collector as itself — there
# is no unauthenticated path to fall back to, so a missing
# credential is a broken deployment rather than a quieter mode.
# Caught here because the alternative is a collector that starts
# cleanly, retries forever, and reports nothing to anyone.
assertion = senderAuth;
message = ''
This host runs the swarm's telemetry collector with
services.hyperhive.swarm.otel.requireHiveIdentity = true, so
ingest is authenticated per hive but this hive's own
collector has no credential to present:
services.hyperhive.otel.enable is true but this hive has no
identity to present to the swarm's collector:
services.hyperhive.otel.clientSecretFile = ${
if otel.clientSecretFile == null then "null" else otel.clientSecretFile
}
services.hyperhive.hiveName = ${if hiveName == null then "null" else hiveName}
Its samples would be refused with a 401 by the collector
running beside it. Set both, or set requireHiveIdentity =
false to accept unauthenticated ingest.
'';
}
{
# The mirror of the assertion above, and the reason it exists is
# that the failure is SILENT rather than loud. With ingest
# unauthenticated the swarm tier serves one catch-all location
# and passes the URI through unchanged — but this tier still
# appends `/<hive>` whenever it holds a credential, so the
# receiver is asked for `/<hive>/v1/metrics`, a path it does not
# serve. The result is 404s and retries: no 401, no assertion, no
# log anywhere saying telemetry stopped.
#
# Only reachable by overriding one side without the other, since
# both defaults derive from `swarm.authelia.enable` and move
# together. That is exactly why it is worth a build error — an
# operator who flips the escape hatch has no reason to suspect
# the sending half.
assertion = !(swarmOtelCfg.enable && !swarmOtelCfg.requireHiveIdentity && senderAuth);
message = ''
This host accepts unauthenticated telemetry ingest
(services.hyperhive.swarm.otel.requireHiveIdentity = false),
but its own collector still holds a credential:
Every hive authenticates as itself that is what makes the
`hive` label on its metrics mean anything so both are
required.
services.hyperhive.otel.clientSecretFile = ${
if otel.clientSecretFile == null then "null" else otel.clientSecretFile
}
A collector that authenticates also addresses its hive's own
path, and an unauthenticated swarm tier serves no per-hive
paths so this hive's samples would 404 rather than be
refused, which no log names as an auth problem.
Set services.hyperhive.otel.clientSecretFile = null to send
unauthenticated too, or drop the requireHiveIdentity
override.
On a host that runs the swarm's identity provider, the default
already points at the secret authelia minted. On a hive that
does not, copy that hive's secret across and name it here.
'';
}
];