nix: split swarm-authelia into service and deploy-mode files
`swarm.authelia` (what the SSO provider is to every hive: ports, domain, `url`, the OIDC client register, the published names and the bridge's address) moves to nix/host-modules/swarm-authelia-service.nix. Everything else -- the `deploy.authelia` options, the whole `config` block including `containers.swarm-authelia`, and the helpers only they read -- stays in nix/host-modules/swarm-authelia.nix, which default.nix now imports alongside the new file. Both halves read four `let` bindings. `cfg`, `swarmDomain` and `deployCfg` are option reads, so each file binds them from `config`; the service file has no `hyperhiveCfg`, so it spells the paths out, as swarm-nats-service.nix does. `instance` and `unitName` are literals, not options, so the service file carries its own copy of the two (`unit`'s default reads `unitName`). `deployCfg` is in the service file only for `bridgeUrl`'s default, which is moved as it is. `hyperhiveDomain` had no reader and is dropped rather than carried into either file. A pure move: option paths, option definitions and config are unchanged apart from three comments that pointed "above"/"below" across the new file boundary and now name the file. Authelia's container toplevel, the host toplevel (with `c0re.hyperhiveFlake` pinned, since the flake source path lands in /etc/hyperhive/serve.json), the `swarm.authelia` and `deploy.authelia` values and option set, and the eleven module-eval checks that enable authelia evaluate to the same derivations before and after. Refs #3742
This commit is contained in:
parent
eef7b70c0e
commit
ba56bfe32e
3 changed files with 476 additions and 453 deletions
|
|
@ -40,6 +40,7 @@
|
||||||
./glue-services-issuer-bao-identity.nix
|
./glue-services-issuer-bao-identity.nix
|
||||||
./glue-swarm-bao-otel-oidc-client.nix
|
./glue-swarm-bao-otel-oidc-client.nix
|
||||||
./glue-swarm-otel-oidc-client.nix
|
./glue-swarm-otel-oidc-client.nix
|
||||||
|
./swarm-authelia-service.nix
|
||||||
./swarm-authelia.nix
|
./swarm-authelia.nix
|
||||||
./swarm-bao-service.nix
|
./swarm-bao-service.nix
|
||||||
./swarm-bao.nix
|
./swarm-bao.nix
|
||||||
|
|
|
||||||
464
nix/host-modules/swarm-authelia-service.nix
Normal file
464
nix/host-modules/swarm-authelia-service.nix
Normal file
|
|
@ -0,0 +1,464 @@
|
||||||
|
# The swarm's SSO provider as every hive sees it: where it answers, the OIDC
|
||||||
|
# client register every service checks itself against, and the names published
|
||||||
|
# for consumers acting on it from outside, identical on every host. What the
|
||||||
|
# host running it decides, and the container itself, are in ./swarm-authelia.nix.
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cfg = config.services.hyperhive.swarm.authelia;
|
||||||
|
swarmDomain = config.services.hyperhive.swarm.domain;
|
||||||
|
deployCfg = config.services.hyperhive.deploy;
|
||||||
|
|
||||||
|
# The unit name upstream's `services.authelia.instances.<name>` derives,
|
||||||
|
# which `unit` publishes. ./swarm-authelia.nix binds the same two names for
|
||||||
|
# the instance it declares, and the two must agree.
|
||||||
|
instance = "swarm";
|
||||||
|
unitName = "authelia-${instance}";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# `enable` and both packages moved to `services.hyperhive.deploy.authelia`
|
||||||
|
# — see ./deploy.nix. Whether this host runs the swarm's SSO provider, and
|
||||||
|
# which build it runs, are deployment decisions, in ./swarm-authelia.nix.
|
||||||
|
# Here is what authelia IS, including `url` and the OIDC client registry
|
||||||
|
# every hive needs as a *client* whether or not it runs the container.
|
||||||
|
options.services.hyperhive.swarm.authelia = {
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 9091;
|
||||||
|
description = ''
|
||||||
|
TCP port authelia listens on. 9091 is upstream's default and
|
||||||
|
sits outside hyperhive's claimed ranges (dashboard 7000, forge
|
||||||
|
3000, matrix 8008, every agent in 8100..8999 via FNV-1a hash).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
metricsPort = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 9959;
|
||||||
|
description = ''
|
||||||
|
TCP port authelia serves its Prometheus metrics on, bound to
|
||||||
|
loopback. Upstream's default, kept so an operator reading
|
||||||
|
authelia's documentation finds what they expect.
|
||||||
|
|
||||||
|
A separate port from {option}`port` because it is a separate
|
||||||
|
listener with a different audience: the main one is proxied by
|
||||||
|
the gateway and reachable from the swarm, this one is scraped by
|
||||||
|
the collector on this host and by nothing else.
|
||||||
|
|
||||||
|
⚠️ Every swarm container shares the host network namespace, so
|
||||||
|
two services defaulting to the same port do not conflict at build
|
||||||
|
time — one simply loses at runtime, with nothing in any log. Check
|
||||||
|
a new value against the others before changing this.
|
||||||
|
|
||||||
|
::: {.note}
|
||||||
|
Loopback means this endpoint is only reachable by a collector on
|
||||||
|
the *same host*, so the scrape target is declared only when one is
|
||||||
|
enabled here. Run the swarm's collector elsewhere and authelia's
|
||||||
|
metrics are simply not collected — no error, and nothing in a log
|
||||||
|
to say so. Making them reachable across hosts is a different piece
|
||||||
|
of work: the endpoint would have to be published under a name,
|
||||||
|
with a certificate and an audience.
|
||||||
|
:::
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
domain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
# Under the SWARM domain, like the forge and matrix: a swarm has one
|
||||||
|
# SSO provider, and the session cookie has to reach the swarm's
|
||||||
|
# services.
|
||||||
|
#
|
||||||
|
# Total on a null swarm domain so the required-domain assertion in
|
||||||
|
# hive-network.nix is the thing that fires; see the comment there.
|
||||||
|
default = if swarmDomain == null then "auth.invalid" else "auth.${swarmDomain}";
|
||||||
|
defaultText = lib.literalExpression ''"auth.''${services.hyperhive.swarm.domain}"'';
|
||||||
|
example = "login.example.com";
|
||||||
|
description = ''
|
||||||
|
Public hostname for the SSO provider — the sub-domain shape the
|
||||||
|
forge and matrix already use, under the swarm's domain because a
|
||||||
|
swarm has **one** SSO provider. Must be the name browsers
|
||||||
|
actually visit: it is the `authelia_url` the session cookie is
|
||||||
|
validated against.
|
||||||
|
|
||||||
|
⚠️ Unlike the forge and matrix names, this one carries **no
|
||||||
|
migration pin**: nothing depends on the previous
|
||||||
|
`auth.''${services.hyperhive.domain}` yet, so it moves outright.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
url = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "https://${cfg.domain}";
|
||||||
|
defaultText = lib.literalExpression ''"https://''${domain}"'';
|
||||||
|
example = "https://auth.example.com";
|
||||||
|
description = ''
|
||||||
|
Base URL clients are sent to for authentication — the half of
|
||||||
|
this module that exists on **every** hive, not just the one
|
||||||
|
running the container.
|
||||||
|
|
||||||
|
Names {option}`domain`, and does **not** ask whether this host
|
||||||
|
runs the container: a swarm has one SSO provider, so every hive
|
||||||
|
addresses the same name and resolution decides where it is —
|
||||||
|
dnsmasq locally on the host serving the vhost, the real network
|
||||||
|
anywhere else. There is no loopback-vs-remote branch to get
|
||||||
|
wrong, the same way {option}`services.hyperhive.swarm.otel.domain`
|
||||||
|
has none.
|
||||||
|
|
||||||
|
Not having SSO is not a supported deployment: every swarm has an
|
||||||
|
IdP, so this is never `null`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
oidc.hiveIdentities = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Mint machine clients per hive in
|
||||||
|
{option}`services.hyperhive.swarm.hives`, so each hive can
|
||||||
|
authenticate to swarm services as itself: `hive-<name>` for the
|
||||||
|
hive's own daemons, and `hive-<name>-agent` for the agent containers
|
||||||
|
running on it.
|
||||||
|
|
||||||
|
Two clients rather than one because they are not the same
|
||||||
|
principal — a hive's daemons run on the host and an agent runs
|
||||||
|
in a container the host hands a credential to, so a swarm
|
||||||
|
service has to be able to grant them different things. It is
|
||||||
|
one client per *hive* on the agent side, not per agent: agents
|
||||||
|
are created at runtime, and a per-agent client would make
|
||||||
|
creating one a config change plus an authelia reload. The cost
|
||||||
|
is that agents on a hive are indistinguishable from each other,
|
||||||
|
tracked as a follow-up rather than papered over.
|
||||||
|
|
||||||
|
On by default: a swarm's hives have identities, and that is a
|
||||||
|
fact about the swarm rather than about any one host. It used to
|
||||||
|
default to whether the queue ran on *this* machine, which made
|
||||||
|
the answer differ between two hosts of one swarm — set it false
|
||||||
|
for a swarm whose hives authenticate to nothing.
|
||||||
|
|
||||||
|
The clients are inert until something authenticates with them:
|
||||||
|
each is a client id and a secret sitting on this host. What
|
||||||
|
delivers a secret to a hive that is not this host is a separate
|
||||||
|
problem and deliberately not solved here.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
oidc.clients = lib.mkOption {
|
||||||
|
type = lib.types.listOf (
|
||||||
|
lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
id = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "forgejo";
|
||||||
|
description = ''
|
||||||
|
OAuth2 client id, as the relying party knows itself.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
description = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "HyperHive forge";
|
||||||
|
description = ''
|
||||||
|
Human-readable name, shown on authelia's consent screen.
|
||||||
|
This is the string a person reads when deciding whether
|
||||||
|
to hand an application their identity, so it should name
|
||||||
|
the application rather than the protocol.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
kind = lib.mkOption {
|
||||||
|
type = lib.types.enum [
|
||||||
|
"interactive"
|
||||||
|
"machine"
|
||||||
|
];
|
||||||
|
default = "interactive";
|
||||||
|
example = "machine";
|
||||||
|
description = ''
|
||||||
|
Whether a human logs in through this client, or a daemon
|
||||||
|
authenticates as itself.
|
||||||
|
|
||||||
|
`interactive` is the authorization-code flow: a browser is
|
||||||
|
redirected, a person authenticates, the client receives an
|
||||||
|
id-token. `machine` is `client_credentials`: there is
|
||||||
|
nobody to redirect and no identity to assert but the
|
||||||
|
client's own, so it receives an access token and no
|
||||||
|
id-token.
|
||||||
|
|
||||||
|
This is declared rather than inferred from an empty
|
||||||
|
`redirectUris`, because authelia permits only the grants a
|
||||||
|
client names — omitting `grant_types` yields
|
||||||
|
authorization-code alone, and a daemon then fails at the
|
||||||
|
token endpoint with `unauthorized_client` rather than at
|
||||||
|
evaluation.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
redirectUris = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "https://forge.example.com/user/oauth2/authelia/callback" ];
|
||||||
|
description = ''
|
||||||
|
Exact callback URLs the provider will redirect to.
|
||||||
|
Matched literally by authelia — a trailing-slash
|
||||||
|
difference is a rejected login, not a warning.
|
||||||
|
|
||||||
|
Meaningless for `kind = "machine"`, which is asserted
|
||||||
|
rather than silently ignored.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
tokenEndpointAuthMethod = lib.mkOption {
|
||||||
|
type = lib.types.nullOr (
|
||||||
|
lib.types.enum [
|
||||||
|
"client_secret_basic"
|
||||||
|
"client_secret_post"
|
||||||
|
"client_secret_jwt"
|
||||||
|
"private_key_jwt"
|
||||||
|
"none"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
default = null;
|
||||||
|
example = "client_secret_post";
|
||||||
|
description = ''
|
||||||
|
How this client proves its identity at the token
|
||||||
|
endpoint. `null` leaves authelia on its own default
|
||||||
|
(`client_secret_basic`), which is what every client that
|
||||||
|
does not say otherwise gets.
|
||||||
|
|
||||||
|
Set it when the relying party's implementation differs,
|
||||||
|
because authelia enforces the registered method rather
|
||||||
|
than accepting whatever arrives. tuwunel sends
|
||||||
|
`client_secret_post`, and against a client registered for
|
||||||
|
basic the result is a 401 from `/api/oidc/token` **after
|
||||||
|
a successful consent** — the login looks like it worked
|
||||||
|
right up to the last hop, and neither the redirect nor
|
||||||
|
the secret is at fault.
|
||||||
|
|
||||||
|
⚠️ `null` is NOT accepted on a client with `bearerAuthz`.
|
||||||
|
Measured against authelia 4.39.20: under that scope the
|
||||||
|
method must be *stated*: omitting it is refused with
|
||||||
|
`must be configured as 'client_secret_basic', … but it's
|
||||||
|
configured as` an empty string. The sentence above is
|
||||||
|
true of an ordinary client and false of that one, which
|
||||||
|
is exactly how a reviewer reads this option and concludes
|
||||||
|
the assertion below is wrong.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
audience = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "hive-alpha" ];
|
||||||
|
description = ''
|
||||||
|
Audiences (`aud`) this client is permitted to request a
|
||||||
|
token for. Empty means it asks for none, which is the
|
||||||
|
right answer for a client whose resource server does not
|
||||||
|
distinguish callers.
|
||||||
|
|
||||||
|
⚠️ Registering an audience only *permits* it — the value
|
||||||
|
lands in a token when the client **asks** for it at the
|
||||||
|
token endpoint, and a client that does not send
|
||||||
|
`audience=` receives a token with `aud: []` however
|
||||||
|
complete this list looks. Measured against authelia
|
||||||
|
4.39.20: the config reads exactly right and the resource
|
||||||
|
server rejects every token, because a config that grants
|
||||||
|
and a request that claims are two separate acts.
|
||||||
|
|
||||||
|
Requesting an audience that is *not* listed here is
|
||||||
|
refused with `invalid_target`, which is what makes this
|
||||||
|
usable as a boundary rather than a label: a client cannot
|
||||||
|
mint a token for a resource slot that is not its own.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
bearerAuthz = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
example = true;
|
||||||
|
description = ''
|
||||||
|
Grant this client the `authelia.bearer.authz` scope, so it
|
||||||
|
may present its access token to authelia's authz endpoint
|
||||||
|
and be authorised by an `access_control` rule — how a
|
||||||
|
scraper reaches a service published behind the gateway.
|
||||||
|
|
||||||
|
A named capability rather than a free-form `scopes` list,
|
||||||
|
for the same reason `kind` derives the rest: authelia
|
||||||
|
refuses some scope/grant combinations outright (`openid`
|
||||||
|
with `client_credentials` among them), and a list would
|
||||||
|
make those combinations expressible again. This admits the
|
||||||
|
one value that is legal here and nothing else.
|
||||||
|
|
||||||
|
::: {.note}
|
||||||
|
Setting this obliges two other options, and the assertions
|
||||||
|
below enforce it. Authelia checks the same thing, but only
|
||||||
|
in its `preStart` validator — which means a violation
|
||||||
|
builds and deploys cleanly and then fails to restart,
|
||||||
|
taking swarm SSO down. The assertions move that to
|
||||||
|
evaluation, where a wrong value costs nothing.
|
||||||
|
:::
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
accessTokenSignedResponseAlg = lib.mkOption {
|
||||||
|
type = lib.types.nullOr (
|
||||||
|
lib.types.enum [
|
||||||
|
"none"
|
||||||
|
"RS256"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
default = null;
|
||||||
|
example = "RS256";
|
||||||
|
description = ''
|
||||||
|
Signing algorithm for this client's **access** tokens.
|
||||||
|
`null` leaves authelia on its default, which issues an
|
||||||
|
opaque token (`authelia_at_…`) — a database handle that
|
||||||
|
carries no claims and means nothing to anyone but this
|
||||||
|
provider.
|
||||||
|
|
||||||
|
Set `RS256` when the resource server verifies the token
|
||||||
|
*itself* rather than asking this provider about it: that
|
||||||
|
yields an RFC 9068 JWT (`at+jwt`) carrying `aud`, `iss`
|
||||||
|
and `client_id`, verifiable against `/jwks.json` with no
|
||||||
|
round trip.
|
||||||
|
|
||||||
|
⚠️ This is what makes a token readable by an
|
||||||
|
OIDC-verifying consumer at all. A resource server given
|
||||||
|
an opaque token is not *misconfigured* — it is
|
||||||
|
structurally unable to verify it, and says so in terms
|
||||||
|
that point at the verifier rather than at the token's
|
||||||
|
format.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
OIDC relying parties this provider will issue tokens to.
|
||||||
|
Declaring one turns the provider on; the default empty list
|
||||||
|
leaves this module exactly as it was — a session provider and
|
||||||
|
nothing else.
|
||||||
|
|
||||||
|
⚠️ **There is deliberately no secret here.** A client secret has
|
||||||
|
two holders in two containers (authelia keeps a *hash*, the
|
||||||
|
relying party the *plaintext*), and
|
||||||
|
`services.authelia.instances.<n>.settings` is rendered into the
|
||||||
|
**nix store**, which is world-readable and permanent. So this
|
||||||
|
option carries only the parts that are safe to evaluate: the
|
||||||
|
secret is minted on first boot and never passes through a nix
|
||||||
|
expression. See `docs/swarm/` for what goes where.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Derived facts, exposed for consumers that have to act on this
|
||||||
|
# container **from outside it** — `swarmctl` is the first, and it
|
||||||
|
# needs all three. Read-only options rather than literals repeated at
|
||||||
|
# the call site: the machine and unit names are derived from
|
||||||
|
# `instance` here, so a second copy elsewhere is a second thing to
|
||||||
|
# keep in step, and the one that drifts is the one nobody tests.
|
||||||
|
hiveClientPrefix = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
readOnly = true;
|
||||||
|
default = "hive-";
|
||||||
|
description = ''
|
||||||
|
Prefix of the OAuth2 client id minted for each hive in
|
||||||
|
`services.hyperhive.swarm.hives` — the client for hive `alpha` is
|
||||||
|
`${config.services.hyperhive.swarm.authelia.hiveClientPrefix}alpha`.
|
||||||
|
Read-only for the same reason as `machine` and `unit`: it is what
|
||||||
|
this module produces, published so a consumer does not carry a
|
||||||
|
second copy.
|
||||||
|
|
||||||
|
The consumer that matters is the queue's auth-callout responder,
|
||||||
|
which decides *which hive* a connection is by stripping this
|
||||||
|
prefix off the introspected client id. Split the two spellings and
|
||||||
|
every hive is denied — as a timeout, indistinguishable from a hive
|
||||||
|
that simply has not reported.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
agentClientSuffix = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
readOnly = true;
|
||||||
|
default = "-agent";
|
||||||
|
description = ''
|
||||||
|
Suffix appended to a hive's own client id to name the client its
|
||||||
|
*agent containers* present — agents on hive `alpha` all present
|
||||||
|
`${config.services.hyperhive.swarm.authelia.hiveClientPrefix}alpha${config.services.hyperhive.swarm.authelia.agentClientSuffix}`.
|
||||||
|
Read-only for the same reason as `hiveClientPrefix`, and read by the
|
||||||
|
same consumer with the same failure mode: a split spelling denies
|
||||||
|
every agent as a timeout.
|
||||||
|
|
||||||
|
A suffix on the hive's id rather than a prefix of its own, because
|
||||||
|
`agent-alpha` reads as *the agent named alpha* — which is precisely
|
||||||
|
what this identity does not say.
|
||||||
|
|
||||||
|
One id per hive rather than per agent, because agents are created at
|
||||||
|
runtime and a per-agent client would make creating one a config
|
||||||
|
change plus a reload. The consequence is that this identity says
|
||||||
|
*which hive* an agent belongs to and never *which agent* — the
|
||||||
|
broker cannot tell two agents on one hive apart.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
machine = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
readOnly = true;
|
||||||
|
default = "swarm-authelia";
|
||||||
|
description = ''
|
||||||
|
Name of the nixos-container authelia runs in. Read-only: it is
|
||||||
|
what this module declares, published so callers of
|
||||||
|
`systemctl -M` and `/var/lib/nixos-containers/<name>` do not
|
||||||
|
have to hardcode it.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
unit = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
readOnly = true;
|
||||||
|
default = "${unitName}.service";
|
||||||
|
description = ''
|
||||||
|
authelia's systemd unit *inside* the container. Read-only, and
|
||||||
|
derived from the instance name exactly like the unit itself.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
bridgePort = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 9092;
|
||||||
|
description = ''
|
||||||
|
TCP port `swarm-authelia-bridge` listens on, loopback-bound
|
||||||
|
(`127.0.0.1:''${bridgePort}`) — one above authelia's own default
|
||||||
|
`port` (9091), outside hyperhive's other claimed ranges.
|
||||||
|
|
||||||
|
Reachable directly from this host's other processes (this
|
||||||
|
container shares the host netns, same as authelia's own `port`)
|
||||||
|
without going through the gateway — this is an internal
|
||||||
|
service-to-service endpoint, not something meant to be exposed
|
||||||
|
publicly.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
bridgeUrl = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
readOnly = true;
|
||||||
|
default = if deployCfg.authelia.enable then "http://127.0.0.1:${toString cfg.bridgePort}" else null;
|
||||||
|
defaultText = lib.literalExpression ''if enable then "http://127.0.0.1:''${bridgePort}" else null'';
|
||||||
|
description = ''
|
||||||
|
Where `swarm-authelia-bridge` answers, **as seen from this
|
||||||
|
host** — correct only when a caller (`swarm-controller`) also
|
||||||
|
runs on this host, the same co-location assumption
|
||||||
|
`swarm.nix`'s `clientSecretFile` documents for its own
|
||||||
|
cross-host case. `null` when this host doesn't run
|
||||||
|
`swarm-authelia` at all.
|
||||||
|
|
||||||
|
A split-host swarm has no automated delivery for this address:
|
||||||
|
the operator points `swarm-controller`'s own option at wherever
|
||||||
|
this host has made the bridge reachable (a firewall rule, a
|
||||||
|
different bind address), the same manual-copy shape used
|
||||||
|
throughout this codebase's other cross-host cases.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -32,7 +32,6 @@ let
|
||||||
networkCfg = config.services.hyperhive.network;
|
networkCfg = config.services.hyperhive.network;
|
||||||
hyperhiveCfg = config.services.hyperhive;
|
hyperhiveCfg = config.services.hyperhive;
|
||||||
gatewayCfg = hyperhiveCfg.gateway;
|
gatewayCfg = hyperhiveCfg.gateway;
|
||||||
hyperhiveDomain = hyperhiveCfg.domain;
|
|
||||||
swarmDomain = hyperhiveCfg.swarm.domain;
|
swarmDomain = hyperhiveCfg.swarm.domain;
|
||||||
deployCfg = hyperhiveCfg.deploy;
|
deployCfg = hyperhiveCfg.deploy;
|
||||||
|
|
||||||
|
|
@ -114,13 +113,14 @@ let
|
||||||
|
|
||||||
# The SWARM's domain, because that is where the protected apps now live
|
# The SWARM's domain, because that is where the protected apps now live
|
||||||
# (`forge.<swarm>`, `chat.<swarm>`, `auth.<swarm>`). It moves in the
|
# (`forge.<swarm>`, `chat.<swarm>`, `auth.<swarm>`). It moves in the
|
||||||
# same commit as `domain` below and cannot lag it: authelia validates
|
# same commit as `domain` (./swarm-authelia-service.nix) and cannot lag
|
||||||
# `authelia_url ⊂ cookie domain` at STARTUP, so a half-move does not
|
# it: authelia validates `authelia_url ⊂ cookie domain` at STARTUP, so a
|
||||||
# misbehave at login — it refuses to boot.
|
# half-move does not misbehave at login — it refuses to boot.
|
||||||
#
|
#
|
||||||
# Total on a null swarm domain for the same reason the option defaults
|
# Total on a null swarm domain for the same reason the option defaults in
|
||||||
# below are: the required-domain assertion in hive-network.nix should
|
# ./swarm-authelia-service.nix are: the required-domain assertion in
|
||||||
# be what an operator sees, not a coercion error from here.
|
# hive-network.nix should be what an operator sees, not a coercion error
|
||||||
|
# from here.
|
||||||
cookieDomain = if swarmDomain == null then "invalid" else swarmDomain;
|
cookieDomain = if swarmDomain == null then "invalid" else swarmDomain;
|
||||||
|
|
||||||
# One machine client per hive in the roster. The model — why identity is
|
# One machine client per hive in the roster. The model — why identity is
|
||||||
|
|
@ -390,452 +390,10 @@ let
|
||||||
privateNetwork = false;
|
privateNetwork = false;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
# `enable` and both packages moved to `services.hyperhive.deploy.authelia`
|
# What authelia IS to every hive, where it answers (`url`), the OIDC
|
||||||
# — see ./deploy.nix. Whether this host runs the swarm's SSO provider, and
|
# register every service checks itself against and its port, is
|
||||||
# which build it runs, are deployment decisions; what stays here is what
|
# `swarm.authelia` in ./swarm-authelia-service.nix. What the host running
|
||||||
# authelia IS, including `url` and the OIDC client registry every hive needs
|
# the container decides is here — which two builds it
|
||||||
# as a *client* whether or not it runs the container.
|
|
||||||
options.services.hyperhive.swarm.authelia = {
|
|
||||||
port = lib.mkOption {
|
|
||||||
type = lib.types.port;
|
|
||||||
default = 9091;
|
|
||||||
description = ''
|
|
||||||
TCP port authelia listens on. 9091 is upstream's default and
|
|
||||||
sits outside hyperhive's claimed ranges (dashboard 7000, forge
|
|
||||||
3000, matrix 8008, every agent in 8100..8999 via FNV-1a hash).
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
metricsPort = lib.mkOption {
|
|
||||||
type = lib.types.port;
|
|
||||||
default = 9959;
|
|
||||||
description = ''
|
|
||||||
TCP port authelia serves its Prometheus metrics on, bound to
|
|
||||||
loopback. Upstream's default, kept so an operator reading
|
|
||||||
authelia's documentation finds what they expect.
|
|
||||||
|
|
||||||
A separate port from {option}`port` because it is a separate
|
|
||||||
listener with a different audience: the main one is proxied by
|
|
||||||
the gateway and reachable from the swarm, this one is scraped by
|
|
||||||
the collector on this host and by nothing else.
|
|
||||||
|
|
||||||
⚠️ Every swarm container shares the host network namespace, so
|
|
||||||
two services defaulting to the same port do not conflict at build
|
|
||||||
time — one simply loses at runtime, with nothing in any log. Check
|
|
||||||
a new value against the others before changing this.
|
|
||||||
|
|
||||||
::: {.note}
|
|
||||||
Loopback means this endpoint is only reachable by a collector on
|
|
||||||
the *same host*, so the scrape target is declared only when one is
|
|
||||||
enabled here. Run the swarm's collector elsewhere and authelia's
|
|
||||||
metrics are simply not collected — no error, and nothing in a log
|
|
||||||
to say so. Making them reachable across hosts is a different piece
|
|
||||||
of work: the endpoint would have to be published under a name,
|
|
||||||
with a certificate and an audience.
|
|
||||||
:::
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
domain = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
# Under the SWARM domain, like the forge and matrix: a swarm has one
|
|
||||||
# SSO provider, and the session cookie has to reach the swarm's
|
|
||||||
# services.
|
|
||||||
#
|
|
||||||
# Total on a null swarm domain so the required-domain assertion in
|
|
||||||
# hive-network.nix is the thing that fires; see the comment there.
|
|
||||||
default = if swarmDomain == null then "auth.invalid" else "auth.${swarmDomain}";
|
|
||||||
defaultText = lib.literalExpression ''"auth.''${services.hyperhive.swarm.domain}"'';
|
|
||||||
example = "login.example.com";
|
|
||||||
description = ''
|
|
||||||
Public hostname for the SSO provider — the sub-domain shape the
|
|
||||||
forge and matrix already use, under the swarm's domain because a
|
|
||||||
swarm has **one** SSO provider. Must be the name browsers
|
|
||||||
actually visit: it is the `authelia_url` the session cookie is
|
|
||||||
validated against.
|
|
||||||
|
|
||||||
⚠️ Unlike the forge and matrix names, this one carries **no
|
|
||||||
migration pin**: nothing depends on the previous
|
|
||||||
`auth.''${services.hyperhive.domain}` yet, so it moves outright.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "https://${cfg.domain}";
|
|
||||||
defaultText = lib.literalExpression ''"https://''${domain}"'';
|
|
||||||
example = "https://auth.example.com";
|
|
||||||
description = ''
|
|
||||||
Base URL clients are sent to for authentication — the half of
|
|
||||||
this module that exists on **every** hive, not just the one
|
|
||||||
running the container.
|
|
||||||
|
|
||||||
Names {option}`domain`, and does **not** ask whether this host
|
|
||||||
runs the container: a swarm has one SSO provider, so every hive
|
|
||||||
addresses the same name and resolution decides where it is —
|
|
||||||
dnsmasq locally on the host serving the vhost, the real network
|
|
||||||
anywhere else. There is no loopback-vs-remote branch to get
|
|
||||||
wrong, the same way {option}`services.hyperhive.swarm.otel.domain`
|
|
||||||
has none.
|
|
||||||
|
|
||||||
Not having SSO is not a supported deployment: every swarm has an
|
|
||||||
IdP, so this is never `null`.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
oidc.hiveIdentities = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = true;
|
|
||||||
description = ''
|
|
||||||
Mint machine clients per hive in
|
|
||||||
{option}`services.hyperhive.swarm.hives`, so each hive can
|
|
||||||
authenticate to swarm services as itself: `hive-<name>` for the
|
|
||||||
hive's own daemons, and `hive-<name>-agent` for the agent containers
|
|
||||||
running on it.
|
|
||||||
|
|
||||||
Two clients rather than one because they are not the same
|
|
||||||
principal — a hive's daemons run on the host and an agent runs
|
|
||||||
in a container the host hands a credential to, so a swarm
|
|
||||||
service has to be able to grant them different things. It is
|
|
||||||
one client per *hive* on the agent side, not per agent: agents
|
|
||||||
are created at runtime, and a per-agent client would make
|
|
||||||
creating one a config change plus an authelia reload. The cost
|
|
||||||
is that agents on a hive are indistinguishable from each other,
|
|
||||||
tracked as a follow-up rather than papered over.
|
|
||||||
|
|
||||||
On by default: a swarm's hives have identities, and that is a
|
|
||||||
fact about the swarm rather than about any one host. It used to
|
|
||||||
default to whether the queue ran on *this* machine, which made
|
|
||||||
the answer differ between two hosts of one swarm — set it false
|
|
||||||
for a swarm whose hives authenticate to nothing.
|
|
||||||
|
|
||||||
The clients are inert until something authenticates with them:
|
|
||||||
each is a client id and a secret sitting on this host. What
|
|
||||||
delivers a secret to a hive that is not this host is a separate
|
|
||||||
problem and deliberately not solved here.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
oidc.clients = lib.mkOption {
|
|
||||||
type = lib.types.listOf (
|
|
||||||
lib.types.submodule {
|
|
||||||
options = {
|
|
||||||
id = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
example = "forgejo";
|
|
||||||
description = ''
|
|
||||||
OAuth2 client id, as the relying party knows itself.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
description = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
example = "HyperHive forge";
|
|
||||||
description = ''
|
|
||||||
Human-readable name, shown on authelia's consent screen.
|
|
||||||
This is the string a person reads when deciding whether
|
|
||||||
to hand an application their identity, so it should name
|
|
||||||
the application rather than the protocol.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
kind = lib.mkOption {
|
|
||||||
type = lib.types.enum [
|
|
||||||
"interactive"
|
|
||||||
"machine"
|
|
||||||
];
|
|
||||||
default = "interactive";
|
|
||||||
example = "machine";
|
|
||||||
description = ''
|
|
||||||
Whether a human logs in through this client, or a daemon
|
|
||||||
authenticates as itself.
|
|
||||||
|
|
||||||
`interactive` is the authorization-code flow: a browser is
|
|
||||||
redirected, a person authenticates, the client receives an
|
|
||||||
id-token. `machine` is `client_credentials`: there is
|
|
||||||
nobody to redirect and no identity to assert but the
|
|
||||||
client's own, so it receives an access token and no
|
|
||||||
id-token.
|
|
||||||
|
|
||||||
This is declared rather than inferred from an empty
|
|
||||||
`redirectUris`, because authelia permits only the grants a
|
|
||||||
client names — omitting `grant_types` yields
|
|
||||||
authorization-code alone, and a daemon then fails at the
|
|
||||||
token endpoint with `unauthorized_client` rather than at
|
|
||||||
evaluation.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
redirectUris = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
default = [ ];
|
|
||||||
example = [ "https://forge.example.com/user/oauth2/authelia/callback" ];
|
|
||||||
description = ''
|
|
||||||
Exact callback URLs the provider will redirect to.
|
|
||||||
Matched literally by authelia — a trailing-slash
|
|
||||||
difference is a rejected login, not a warning.
|
|
||||||
|
|
||||||
Meaningless for `kind = "machine"`, which is asserted
|
|
||||||
rather than silently ignored.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
tokenEndpointAuthMethod = lib.mkOption {
|
|
||||||
type = lib.types.nullOr (
|
|
||||||
lib.types.enum [
|
|
||||||
"client_secret_basic"
|
|
||||||
"client_secret_post"
|
|
||||||
"client_secret_jwt"
|
|
||||||
"private_key_jwt"
|
|
||||||
"none"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
default = null;
|
|
||||||
example = "client_secret_post";
|
|
||||||
description = ''
|
|
||||||
How this client proves its identity at the token
|
|
||||||
endpoint. `null` leaves authelia on its own default
|
|
||||||
(`client_secret_basic`), which is what every client that
|
|
||||||
does not say otherwise gets.
|
|
||||||
|
|
||||||
Set it when the relying party's implementation differs,
|
|
||||||
because authelia enforces the registered method rather
|
|
||||||
than accepting whatever arrives. tuwunel sends
|
|
||||||
`client_secret_post`, and against a client registered for
|
|
||||||
basic the result is a 401 from `/api/oidc/token` **after
|
|
||||||
a successful consent** — the login looks like it worked
|
|
||||||
right up to the last hop, and neither the redirect nor
|
|
||||||
the secret is at fault.
|
|
||||||
|
|
||||||
⚠️ `null` is NOT accepted on a client with `bearerAuthz`.
|
|
||||||
Measured against authelia 4.39.20: under that scope the
|
|
||||||
method must be *stated*: omitting it is refused with
|
|
||||||
`must be configured as 'client_secret_basic', … but it's
|
|
||||||
configured as` an empty string. The sentence above is
|
|
||||||
true of an ordinary client and false of that one, which
|
|
||||||
is exactly how a reviewer reads this option and concludes
|
|
||||||
the assertion below is wrong.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
audience = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
default = [ ];
|
|
||||||
example = [ "hive-alpha" ];
|
|
||||||
description = ''
|
|
||||||
Audiences (`aud`) this client is permitted to request a
|
|
||||||
token for. Empty means it asks for none, which is the
|
|
||||||
right answer for a client whose resource server does not
|
|
||||||
distinguish callers.
|
|
||||||
|
|
||||||
⚠️ Registering an audience only *permits* it — the value
|
|
||||||
lands in a token when the client **asks** for it at the
|
|
||||||
token endpoint, and a client that does not send
|
|
||||||
`audience=` receives a token with `aud: []` however
|
|
||||||
complete this list looks. Measured against authelia
|
|
||||||
4.39.20: the config reads exactly right and the resource
|
|
||||||
server rejects every token, because a config that grants
|
|
||||||
and a request that claims are two separate acts.
|
|
||||||
|
|
||||||
Requesting an audience that is *not* listed here is
|
|
||||||
refused with `invalid_target`, which is what makes this
|
|
||||||
usable as a boundary rather than a label: a client cannot
|
|
||||||
mint a token for a resource slot that is not its own.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
bearerAuthz = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = false;
|
|
||||||
example = true;
|
|
||||||
description = ''
|
|
||||||
Grant this client the `authelia.bearer.authz` scope, so it
|
|
||||||
may present its access token to authelia's authz endpoint
|
|
||||||
and be authorised by an `access_control` rule — how a
|
|
||||||
scraper reaches a service published behind the gateway.
|
|
||||||
|
|
||||||
A named capability rather than a free-form `scopes` list,
|
|
||||||
for the same reason `kind` derives the rest: authelia
|
|
||||||
refuses some scope/grant combinations outright (`openid`
|
|
||||||
with `client_credentials` among them), and a list would
|
|
||||||
make those combinations expressible again. This admits the
|
|
||||||
one value that is legal here and nothing else.
|
|
||||||
|
|
||||||
::: {.note}
|
|
||||||
Setting this obliges two other options, and the assertions
|
|
||||||
below enforce it. Authelia checks the same thing, but only
|
|
||||||
in its `preStart` validator — which means a violation
|
|
||||||
builds and deploys cleanly and then fails to restart,
|
|
||||||
taking swarm SSO down. The assertions move that to
|
|
||||||
evaluation, where a wrong value costs nothing.
|
|
||||||
:::
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
accessTokenSignedResponseAlg = lib.mkOption {
|
|
||||||
type = lib.types.nullOr (
|
|
||||||
lib.types.enum [
|
|
||||||
"none"
|
|
||||||
"RS256"
|
|
||||||
]
|
|
||||||
);
|
|
||||||
default = null;
|
|
||||||
example = "RS256";
|
|
||||||
description = ''
|
|
||||||
Signing algorithm for this client's **access** tokens.
|
|
||||||
`null` leaves authelia on its default, which issues an
|
|
||||||
opaque token (`authelia_at_…`) — a database handle that
|
|
||||||
carries no claims and means nothing to anyone but this
|
|
||||||
provider.
|
|
||||||
|
|
||||||
Set `RS256` when the resource server verifies the token
|
|
||||||
*itself* rather than asking this provider about it: that
|
|
||||||
yields an RFC 9068 JWT (`at+jwt`) carrying `aud`, `iss`
|
|
||||||
and `client_id`, verifiable against `/jwks.json` with no
|
|
||||||
round trip.
|
|
||||||
|
|
||||||
⚠️ This is what makes a token readable by an
|
|
||||||
OIDC-verifying consumer at all. A resource server given
|
|
||||||
an opaque token is not *misconfigured* — it is
|
|
||||||
structurally unable to verify it, and says so in terms
|
|
||||||
that point at the verifier rather than at the token's
|
|
||||||
format.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
default = [ ];
|
|
||||||
description = ''
|
|
||||||
OIDC relying parties this provider will issue tokens to.
|
|
||||||
Declaring one turns the provider on; the default empty list
|
|
||||||
leaves this module exactly as it was — a session provider and
|
|
||||||
nothing else.
|
|
||||||
|
|
||||||
⚠️ **There is deliberately no secret here.** A client secret has
|
|
||||||
two holders in two containers (authelia keeps a *hash*, the
|
|
||||||
relying party the *plaintext*), and
|
|
||||||
`services.authelia.instances.<n>.settings` is rendered into the
|
|
||||||
**nix store**, which is world-readable and permanent. So this
|
|
||||||
option carries only the parts that are safe to evaluate: the
|
|
||||||
secret is minted on first boot and never passes through a nix
|
|
||||||
expression. See `docs/swarm/` for what goes where.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# Derived facts, exposed for consumers that have to act on this
|
|
||||||
# container **from outside it** — `swarmctl` is the first, and it
|
|
||||||
# needs all three. Read-only options rather than literals repeated at
|
|
||||||
# the call site: the machine and unit names are derived from
|
|
||||||
# `instance` here, so a second copy elsewhere is a second thing to
|
|
||||||
# keep in step, and the one that drifts is the one nobody tests.
|
|
||||||
hiveClientPrefix = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
readOnly = true;
|
|
||||||
default = "hive-";
|
|
||||||
description = ''
|
|
||||||
Prefix of the OAuth2 client id minted for each hive in
|
|
||||||
`services.hyperhive.swarm.hives` — the client for hive `alpha` is
|
|
||||||
`${config.services.hyperhive.swarm.authelia.hiveClientPrefix}alpha`.
|
|
||||||
Read-only for the same reason as `machine` and `unit`: it is what
|
|
||||||
this module produces, published so a consumer does not carry a
|
|
||||||
second copy.
|
|
||||||
|
|
||||||
The consumer that matters is the queue's auth-callout responder,
|
|
||||||
which decides *which hive* a connection is by stripping this
|
|
||||||
prefix off the introspected client id. Split the two spellings and
|
|
||||||
every hive is denied — as a timeout, indistinguishable from a hive
|
|
||||||
that simply has not reported.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
agentClientSuffix = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
readOnly = true;
|
|
||||||
default = "-agent";
|
|
||||||
description = ''
|
|
||||||
Suffix appended to a hive's own client id to name the client its
|
|
||||||
*agent containers* present — agents on hive `alpha` all present
|
|
||||||
`${config.services.hyperhive.swarm.authelia.hiveClientPrefix}alpha${config.services.hyperhive.swarm.authelia.agentClientSuffix}`.
|
|
||||||
Read-only for the same reason as `hiveClientPrefix`, and read by the
|
|
||||||
same consumer with the same failure mode: a split spelling denies
|
|
||||||
every agent as a timeout.
|
|
||||||
|
|
||||||
A suffix on the hive's id rather than a prefix of its own, because
|
|
||||||
`agent-alpha` reads as *the agent named alpha* — which is precisely
|
|
||||||
what this identity does not say.
|
|
||||||
|
|
||||||
One id per hive rather than per agent, because agents are created at
|
|
||||||
runtime and a per-agent client would make creating one a config
|
|
||||||
change plus a reload. The consequence is that this identity says
|
|
||||||
*which hive* an agent belongs to and never *which agent* — the
|
|
||||||
broker cannot tell two agents on one hive apart.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
machine = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
readOnly = true;
|
|
||||||
default = "swarm-authelia";
|
|
||||||
description = ''
|
|
||||||
Name of the nixos-container authelia runs in. Read-only: it is
|
|
||||||
what this module declares, published so callers of
|
|
||||||
`systemctl -M` and `/var/lib/nixos-containers/<name>` do not
|
|
||||||
have to hardcode it.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
unit = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
readOnly = true;
|
|
||||||
default = "${unitName}.service";
|
|
||||||
description = ''
|
|
||||||
authelia's systemd unit *inside* the container. Read-only, and
|
|
||||||
derived from the instance name exactly like the unit itself.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
bridgePort = lib.mkOption {
|
|
||||||
type = lib.types.port;
|
|
||||||
default = 9092;
|
|
||||||
description = ''
|
|
||||||
TCP port `swarm-authelia-bridge` listens on, loopback-bound
|
|
||||||
(`127.0.0.1:''${bridgePort}`) — one above authelia's own default
|
|
||||||
`port` (9091), outside hyperhive's other claimed ranges.
|
|
||||||
|
|
||||||
Reachable directly from this host's other processes (this
|
|
||||||
container shares the host netns, same as authelia's own `port`)
|
|
||||||
without going through the gateway — this is an internal
|
|
||||||
service-to-service endpoint, not something meant to be exposed
|
|
||||||
publicly.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
bridgeUrl = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
readOnly = true;
|
|
||||||
default = if deployCfg.authelia.enable then "http://127.0.0.1:${toString cfg.bridgePort}" else null;
|
|
||||||
defaultText = lib.literalExpression ''if enable then "http://127.0.0.1:''${bridgePort}" else null'';
|
|
||||||
description = ''
|
|
||||||
Where `swarm-authelia-bridge` answers, **as seen from this
|
|
||||||
host** — correct only when a caller (`swarm-controller`) also
|
|
||||||
runs on this host, the same co-location assumption
|
|
||||||
`swarm.nix`'s `clientSecretFile` documents for its own
|
|
||||||
cross-host case. `null` when this host doesn't run
|
|
||||||
`swarm-authelia` at all.
|
|
||||||
|
|
||||||
A split-host swarm has no automated delivery for this address:
|
|
||||||
the operator points `swarm-controller`'s own option at wherever
|
|
||||||
this host has made the bridge reachable (a firewall rule, a
|
|
||||||
different bind address), the same manual-copy shape used
|
|
||||||
throughout this codebase's other cross-host cases.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# What stays above is what authelia IS to every hive: where it answers
|
|
||||||
# (`url`), the OIDC register every service checks itself against, its port.
|
|
||||||
# What the host running the container decides is here — which two builds it
|
|
||||||
# runs, and three filesystem paths that only exist on the machine running
|
# runs, and three filesystem paths that only exist on the machine running
|
||||||
# `swarm-authelia`. A hive that does not run it has nothing at any of those
|
# `swarm-authelia`. A hive that does not run it has nothing at any of those
|
||||||
# paths. `enable` already lives in ./deploy.nix, which also carries the
|
# paths. `enable` already lives in ./deploy.nix, which also carries the
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue