swarm-nats-auth: accept the agent suffix it ships as its default

`--agent-client-suffix` takes a value that opens with `-`, and clap reads
one space-separated as a short-flag cluster. `swarm-nats.nix` passes it that
way, so the responder aborted at startup on `unexpected argument '-a'`.

A responder that never subscribes is not a degraded queue. `auth_callout` is
fail-closed by design, so the server denies every client, and each denial
arrives as `authorization violation` on the client — naming nothing, on a
process that is itself healthy. Two unrelated daemons reported it at once and
neither was at fault.

The option's own `default_value` is `-agent`, so the binary could not accept
the value it ships. Fixing the module's call site instead would leave that
true for anyone running it by hand.

The three tests parse argument vectors rather than assert on a struct: one is
the literal vector the unit builds, one is the control that an ordinary value
goes through the same flag, and one pins the default against the module.
This commit is contained in:
atlas 2026-09-12 12:02:50 +02:00
commit 8104e5e018

View file

@ -94,7 +94,11 @@ struct Args {
/// A suffix on the hive's id rather than a prefix of its own, because /// A suffix on the hive's id rather than a prefix of its own, because
/// `agent-<name>` reads as *the agent called `<name>`* — the one thing /// `agent-<name>` reads as *the agent called `<name>`* — the one thing
/// this identity does not carry. /// this identity does not carry.
#[arg(long, default_value = "-agent")] ///
/// A suffix starts with `-`, so it has to be accepted as a value rather
/// than read as a short-flag cluster — without this the default below
/// cannot be passed back in on the command line that produced it.
#[arg(long, default_value = "-agent", allow_hyphen_values = true)]
agent_client_suffix: String, agent_client_suffix: String,
/// Client ids allowed to read every hive's status. Repeatable. The /// Client ids allowed to read every hive's status. Repeatable. The
@ -275,3 +279,75 @@ async fn main() -> anyhow::Result<()> {
} }
anyhow::bail!("subscription to {AUTH_SUBJECT} ended") anyhow::bail!("subscription to {AUTH_SUBJECT} ended")
} }
#[cfg(test)]
mod tests {
use super::*;
/// The argument vector `swarm-nats.nix` actually builds, in the form it
/// builds it: every flag space-separated, and the suffix's value opening
/// with `-`. Read as a short-flag cluster it aborts before `main` runs,
/// and a responder that never starts denies every client — which reaches
/// the operator as `authorization violation` on an unrelated process.
#[test]
fn the_units_own_argument_vector_parses() {
let args = Args::try_parse_from([
"swarm-nats-auth",
"--user-seed-file",
"/run/credentials/callout-user.seed",
"--issuer-seed-file",
"/run/credentials/issuer.seed",
"--client-secret-file",
"/run/credentials/oidc-client.secret",
"--introspection-url",
"https://auth.example/api/oidc/introspection",
"--hive-client-prefix",
"hive-",
"--agent-client-suffix",
"-agent",
])
.expect("the unit's own argument vector must parse");
assert_eq!(args.agent_client_suffix, "-agent");
}
/// The control for the case above: an ordinary value parses through the
/// same flag, so a failure there is about the leading `-` and not about
/// the argument being unknown.
#[test]
fn an_agent_suffix_without_a_leading_dash_parses_too() {
let args = Args::try_parse_from([
"swarm-nats-auth",
"--user-seed-file",
"/run/credentials/callout-user.seed",
"--issuer-seed-file",
"/run/credentials/issuer.seed",
"--client-secret-file",
"/run/credentials/oidc-client.secret",
"--introspection-url",
"https://auth.example/api/oidc/introspection",
"--agent-client-suffix",
"agent",
])
.expect("an ordinary suffix must parse");
assert_eq!(args.agent_client_suffix, "agent");
}
/// The default is the value the deployment passes, so the two cannot
/// drift into disagreeing about what an agent client is called.
#[test]
fn the_default_suffix_is_what_the_module_passes() {
let args = Args::try_parse_from([
"swarm-nats-auth",
"--user-seed-file",
"/run/credentials/callout-user.seed",
"--issuer-seed-file",
"/run/credentials/issuer.seed",
"--client-secret-file",
"/run/credentials/oidc-client.secret",
"--introspection-url",
"https://auth.example/api/oidc/introspection",
])
.expect("the required flags alone must parse");
assert_eq!(args.agent_client_suffix, "-agent");
}
}