fix(#3384): source the queue policy's principals from the modules that mint them
The auth-callout responder decides what an admitted client may publish
from two strings: the prefix marking a hive client, and the client id
allowed to read every hive's key. Both were literals in three places --
swarm-authelia.nix mints "hive-${name}", swarm-controller.nix defines
"swarm-controller", and the responder carried its own copies as clap
defaults because swarm-nats.nix passed neither.
Each producer now publishes its value as a readOnly option and the
responder's ExecStart reads them, so the agreement is one evaluation
rather than three strings that happen to be equal. Same pattern the
module already uses for `--account`, and the same argument
swarm-authelia.nix gives for publishing `machine` and `unit`.
Worth the change because the failure is silent and misattributed:
rename either principal and the responder starts denying the one that
stopped matching, a denial reaches a NATS client as a timeout rather
than an error, and a hive that is refused looks exactly like a hive
that has not reported yet.
This commit is contained in:
parent
2e9ce53a32
commit
e0e5823080
3 changed files with 54 additions and 2 deletions
|
|
@ -91,7 +91,7 @@ let
|
||||||
# declared entries and not on these, which is an eval error reachable
|
# declared entries and not on these, which is an eval error reachable
|
||||||
# only once hive identities are on.
|
# only once hive identities are on.
|
||||||
hiveClients = lib.mapAttrsToList (name: _: {
|
hiveClients = lib.mapAttrsToList (name: _: {
|
||||||
id = "hive-${name}";
|
id = "${cfg.hiveClientPrefix}${name}";
|
||||||
description = "HyperHive hive ${name}";
|
description = "HyperHive hive ${name}";
|
||||||
kind = "machine";
|
kind = "machine";
|
||||||
redirectUris = [ ];
|
redirectUris = [ ];
|
||||||
|
|
@ -508,6 +508,26 @@ in
|
||||||
# the call site: the machine and unit names are derived from
|
# the call site: the machine and unit names are derived from
|
||||||
# `instance` here, so a second copy elsewhere is a second thing to
|
# `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.
|
# 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.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
machine = lib.mkOption {
|
machine = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ let
|
||||||
# belongs to the responder. One identity per principal — the rule is that
|
# belongs to the responder. One identity per principal — the rule is that
|
||||||
# a principal's credentials all derive from the same identity, not that
|
# a principal's credentials all derive from the same identity, not that
|
||||||
# the swarm has one.
|
# the swarm has one.
|
||||||
queueClientId = "swarm-controller";
|
queueClientId = cfg.queueClientId;
|
||||||
|
|
||||||
# `LoadCredential` and not a copy-oneshot, which is where this deliberately
|
# `LoadCredential` and not a copy-oneshot, which is where this deliberately
|
||||||
# differs from the callout responder: that one delivers INTO a container,
|
# differs from the callout responder: that one delivers INTO a container,
|
||||||
|
|
@ -114,6 +114,25 @@ let
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.services.hyperhive.swarm.controller = {
|
options.services.hyperhive.swarm.controller = {
|
||||||
|
queueClientId = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
readOnly = true;
|
||||||
|
default = "swarm-controller";
|
||||||
|
description = ''
|
||||||
|
The OAuth2 client id the controller presents to the swarm queue.
|
||||||
|
Read-only: it is what this module registers, published so the
|
||||||
|
auth-callout responder can be told which client may read every
|
||||||
|
hive's key without repeating the string.
|
||||||
|
|
||||||
|
The responder decides that from a client id, and a client id it
|
||||||
|
does not recognise is **denied**. A denial reaches a NATS client
|
||||||
|
as a timeout rather than an error, and a controller that cannot
|
||||||
|
read looks exactly like a swarm where no hive has reported yet —
|
||||||
|
so a drift between these two spellings is invisible at the point
|
||||||
|
it is introduced and misattributed everywhere it shows up.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
enable = lib.mkOption {
|
enable = lib.mkOption {
|
||||||
type = lib.types.bool;
|
type = lib.types.bool;
|
||||||
default = false;
|
default = false;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@ let
|
||||||
autheliaCfg = config.services.hyperhive.swarm.authelia;
|
autheliaCfg = config.services.hyperhive.swarm.authelia;
|
||||||
autheliaUrl = autheliaCfg.url;
|
autheliaUrl = autheliaCfg.url;
|
||||||
networkCfg = config.services.hyperhive.network;
|
networkCfg = config.services.hyperhive.network;
|
||||||
|
# Read even when the controller runs on a different host: what is needed
|
||||||
|
# is the client id that module *declares*, which is the same string
|
||||||
|
# everywhere, not whether the daemon happens to be enabled here.
|
||||||
|
controllerCfg = config.services.hyperhive.swarm.controller;
|
||||||
|
|
||||||
# The account the callout responder authenticates as, and the account
|
# The account the callout responder authenticates as, and the account
|
||||||
# authorized clients are placed in. Two accounts rather than one: an
|
# authorized clients are placed in. Two accounts rather than one: an
|
||||||
|
|
@ -531,6 +535,15 @@ in
|
||||||
# be the same string — which is why both come from one let.
|
# be the same string — which is why both come from one let.
|
||||||
"--account ${lib.escapeShellArg clientAccount}"
|
"--account ${lib.escapeShellArg clientAccount}"
|
||||||
"--introspection-url ${lib.escapeShellArg introspectionUrl}"
|
"--introspection-url ${lib.escapeShellArg introspectionUrl}"
|
||||||
|
# Both of these name a principal some OTHER module mints,
|
||||||
|
# so both are read out of that module rather than spelled
|
||||||
|
# again here — same argument as `--account` above, one
|
||||||
|
# level wider. The responder denies a client id it does
|
||||||
|
# not recognise, and a NATS denial arrives as a timeout,
|
||||||
|
# so a drift here is silent at the point of change and
|
||||||
|
# misattributed at the point of failure.
|
||||||
|
"--hive-client-prefix ${lib.escapeShellArg autheliaCfg.hiveClientPrefix}"
|
||||||
|
"--reader-client ${lib.escapeShellArg controllerCfg.queueClientId}"
|
||||||
];
|
];
|
||||||
# Every credential arrives by `LoadCredential` and is named
|
# Every credential arrives by `LoadCredential` and is named
|
||||||
# on the command line only as a **path** — `argv` is
|
# on the command line only as a **path** — `argv` is
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue