Compare commits

..

View file

@ -79,14 +79,6 @@ let
# per service from it. Were the queue to declare this list, the next # per service from it. Were the queue to declare this list, the next
# consumer would collide on the same client id — and only at the # consumer would collide on the same client id — and only at the
# moment it landed. # moment it landed.
#
# Fed to the option as a DEFINITION in the config block below, rather
# than appended to the declared list downstream. That is what puts it
# through the submodule: one list, one type, every option's default
# present. Appending a raw attrset instead left the list half-typed —
# and a field later added to the submodule then existed on the
# declared entries and not on these, which is an eval error reachable
# only once hive identities are on.
hiveClients = lib.mapAttrsToList (name: _: { hiveClients = lib.mapAttrsToList (name: _: {
id = "hive-${name}"; id = "hive-${name}";
description = "HyperHive hive ${name}"; description = "HyperHive hive ${name}";
@ -94,18 +86,21 @@ let
redirectUris = [ ]; redirectUris = [ ];
}) hyperhiveCfg.swarm.hives; }) hyperhiveCfg.swarm.hives;
# Everything downstream renders and mints from this, not from the
# declared list alone.
allClients = cfg.oidc.clients ++ lib.optionals cfg.oidc.hiveIdentities hiveClients;
# authelia refuses to start with an OIDC provider that has no clients, # authelia refuses to start with an OIDC provider that has no clients,
# so the provider is derived from the client list rather than carrying # so the provider is derived from the client list rather than carrying
# its own `enable`: one fact, and it cannot contradict itself. An empty # its own `enable`: one fact, and it cannot contradict itself. An empty
# list is the default, which makes every hive that has not opted in # list is the default, which makes every hive that has not opted in
# byte-identical to before. # byte-identical to before.
# #
# ⚠️ The derived hive identities are definitions of this same option, # ⚠️ Derived from `allClients`, so hive identities can turn the provider
# so they can turn the provider on by themselves. That is only # on by themselves. That is only reachable where the queue is already
# reachable where the queue is already enabled (`hiveIdentities` # enabled (`hiveIdentities` defaults to it) — and a queue-enabled hive
# defaults to it) — and a queue-enabled hive already contributes a # already contributes a client, so no existing deployment flips.
# client, so no existing deployment flips. oidcEnabled = allClients != [ ];
oidcEnabled = cfg.oidc.clients != [ ];
# Secrets that are 64 random bytes of hex and nothing more. The OIDC # Secrets that are 64 random bytes of hex and nothing more. The OIDC
# hmac key joins them; the issuer key does not (see below — it is RSA). # hmac key joins them; the issuer key does not (see below — it is RSA).
@ -149,11 +144,6 @@ let
printf -- " client_secret: '%s'\n" "$(cat ${lib.escapeShellArg "${clientsDir}/${c.id}.digest"})" printf -- " client_secret: '%s'\n" "$(cat ${lib.escapeShellArg "${clientsDir}/${c.id}.digest"})"
printf -- ' authorization_policy: one_factor\n' printf -- ' authorization_policy: one_factor\n'
'' ''
# Read plainly, and that is a property of the list rather than of
# this line: every entry reaching here is a definition of
# `oidc.clients`, so the module system has applied the submodule and
# each option's default is present. A derived entry that names only
# the fields it cares about still arrives with the rest filled in.
+ lib.optionalString (c.tokenEndpointAuthMethod != null) '' + lib.optionalString (c.tokenEndpointAuthMethod != null) ''
printf -- ' token_endpoint_auth_method: %s\n' ${lib.escapeShellArg c.tokenEndpointAuthMethod} printf -- ' token_endpoint_auth_method: %s\n' ${lib.escapeShellArg c.tokenEndpointAuthMethod}
'' ''
@ -229,7 +219,7 @@ let
${lib.concatMapStrings (c: '' ${lib.concatMapStrings (c: ''
mint ${lib.escapeShellArg c.id} mint ${lib.escapeShellArg c.id}
'') cfg.oidc.clients} '') allClients}
# Re-rendered every boot, deliberately: the secret is minted once, # Re-rendered every boot, deliberately: the secret is minted once,
# but the metadata around it (a new redirect URI, a renamed client) # but the metadata around it (a new redirect URI, a renamed client)
@ -240,7 +230,7 @@ let
printf -- 'identity_providers:\n' printf -- 'identity_providers:\n'
printf -- ' oidc:\n' printf -- ' oidc:\n'
printf -- ' clients:\n' printf -- ' clients:\n'
${lib.concatMapStrings renderClient cfg.oidc.clients} ${lib.concatMapStrings renderClient allClients}
} > ${lib.escapeShellArg "${clientsFile}.tmp"} } > ${lib.escapeShellArg "${clientsFile}.tmp"}
chmod 0600 ${lib.escapeShellArg "${clientsFile}.tmp"} chmod 0600 ${lib.escapeShellArg "${clientsFile}.tmp"}
mv ${lib.escapeShellArg "${clientsFile}.tmp"} ${lib.escapeShellArg clientsFile} mv ${lib.escapeShellArg "${clientsFile}.tmp"} ${lib.escapeShellArg clientsFile}
@ -548,14 +538,6 @@ in
}; };
config = lib.mkIf (hyperhiveCfg.enable && cfg.enable) { config = lib.mkIf (hyperhiveCfg.enable && cfg.enable) {
# The derived half of the client list, declared the same way an
# operator declares one. Everything downstream then reads a single
# uniformly-typed `cfg.oidc.clients` and cannot tell the two apart —
# including the assertions below, which is why a hive named `x`
# colliding with a declared `hive-x` is caught rather than rendered
# twice.
services.hyperhive.swarm.authelia.oidc.clients = lib.mkIf cfg.oidc.hiveIdentities hiveClients;
# A redirect URI on a machine client is not harmless-but-unused: it # A redirect URI on a machine client is not harmless-but-unused: it
# means whoever wrote it believes a browser is involved. Failing here # means whoever wrote it believes a browser is involved. Failing here
# is how that belief gets corrected at the point it was expressed, # is how that belief gets corrected at the point it was expressed,
@ -568,7 +550,7 @@ in
+ "kind = \"machine\" but declares redirectUris. A client_credentials " + "kind = \"machine\" but declares redirectUris. A client_credentials "
+ "client has nobody to redirect; drop the URIs or make it " + "client has nobody to redirect; drop the URIs or make it "
+ "kind = \"interactive\"."; + "kind = \"interactive\".";
}) cfg.oidc.clients }) allClients
# Two clients sharing an id renders two YAML entries under one name. # Two clients sharing an id renders two YAML entries under one name.
# Newly reachable now that part of the list is DERIVED: a hive called # Newly reachable now that part of the list is DERIVED: a hive called
# `x` and a service client called `hive-x` never met before. Authelia # `x` and a service client called `hive-x` never met before. Authelia
@ -576,14 +558,12 @@ in
# sources here is the cheaper failure. # sources here is the cheaper failure.
++ [ ++ [
{ {
assertion = lib.length (lib.unique (map (c: c.id) cfg.oidc.clients)) == lib.length cfg.oidc.clients; assertion = lib.length (lib.unique (map (c: c.id) allClients)) == lib.length allClients;
message = message =
"services.hyperhive.swarm.authelia: duplicate OIDC client id(s): " "services.hyperhive.swarm.authelia: duplicate OIDC client id(s): "
+ lib.concatStringsSep ", " ( + lib.concatStringsSep ", " (
lib.unique ( lib.unique (
lib.filter (id: lib.count (x: x == id) (map (c: c.id) cfg.oidc.clients) > 1) ( lib.filter (id: lib.count (x: x == id) (map (c: c.id) allClients) > 1) (map (c: c.id) allClients)
map (c: c.id) cfg.oidc.clients
)
) )
) )
+ ". Hive identities are named `hive-<name>` from " + ". Hive identities are named `hive-<name>` from "