swarm-nats: fetch the callout xkey before the server starts and seal callout traffic
swarm-bao-nats-callout-xkey runs on the queue's host before the swarm-nats container, under the responder's store leaf: it runs `swarm-nats-auth ensure-xkey`, reads the public half back from the store, and writes it where the container's runtime config dir is. On a change it restarts the server. nats-server now always starts through the runtime wrapper, in manual mode too. A privileged ExecStartPre renders auth_callout with the xkey (and, in auto mode, the two generated nkeys) at every start, and fails the server when no xkey has been fetched. The auto-mode key unit now only mints seeds and writes their public halves. The responder is wanted by nats.service, so a server started late, once its key arrived, brings the responder up with it.
This commit is contained in:
parent
bb571285fd
commit
9a241a1df1
4 changed files with 362 additions and 48 deletions
|
|
@ -102,6 +102,109 @@ let
|
|||
name = "the queue's payload ceiling is set, not inherited from the server's default";
|
||||
ok = natsOldPath.containers.swarm-nats.config.services.nats.settings.max_payload == 8388608;
|
||||
}
|
||||
{
|
||||
# What keeps a callout request inside one principal: the responder is
|
||||
# the callout account's only member, and no system account exists to
|
||||
# observe it from. The runtime fragment is rendered by the same
|
||||
# `calloutBlocks`, so the count is the same there. The account name is
|
||||
# the control that the assertions read the callout block at all.
|
||||
name = "the callout account has exactly one member and no system account is configured";
|
||||
ok =
|
||||
lib.all
|
||||
(
|
||||
h:
|
||||
let
|
||||
settings = h.containers.swarm-nats.config.services.nats.settings;
|
||||
in
|
||||
settings.authorization.auth_callout.account == "AUTH"
|
||||
&& lib.length settings.accounts.AUTH.users == 1
|
||||
&& !(settings ? system_account)
|
||||
)
|
||||
[
|
||||
natsOldPath
|
||||
natsOnStoreHost
|
||||
];
|
||||
}
|
||||
{
|
||||
# The xkey exists only at runtime, so the server's config is rendered as
|
||||
# it starts: always the wrapper, and never the store-rendered settings
|
||||
# alone, which carry no xkey. `+` so the render may write `runtimeDir`.
|
||||
name = "the queue's server starts only through the config rendered with its xkey";
|
||||
ok =
|
||||
lib.all
|
||||
(
|
||||
h:
|
||||
let
|
||||
c = h.containers.swarm-nats.config;
|
||||
nats = c.systemd.services.nats.serviceConfig;
|
||||
pre = nats.ExecStartPre or "";
|
||||
in
|
||||
lib.hasSuffix "/bin/nats-server -c /var/lib/nats-callout/nats.conf" nats.ExecStart
|
||||
&& lib.hasPrefix "+/nix/store/" pre
|
||||
&& lib.hasSuffix "-swarm-nats-render-config" pre
|
||||
&& !(c.services.nats.settings.authorization.auth_callout ? xkey)
|
||||
)
|
||||
[
|
||||
natsOldPath
|
||||
natsOnStoreHost
|
||||
];
|
||||
}
|
||||
{
|
||||
# A server that starts late, once its xkey arrives, must bring the
|
||||
# responder with it: the responder requires the server, so a server that
|
||||
# failed to start left the responder inactive rather than failed.
|
||||
name = "starting the queue's server starts its responder";
|
||||
ok = lib.elem "nats.service" (responderOf natsOldPath).wantedBy;
|
||||
}
|
||||
{
|
||||
# The key is created before anything needs it, by the responder's own
|
||||
# verb under its own leaf, then read back from the public path and left
|
||||
# where the server's render reads it. Before the container and wanted by
|
||||
# it, and a fetch that finds nothing fails so `storeRetry` retries.
|
||||
name = "the callout xkey is created, then fetched from the store before the queue's container starts";
|
||||
ok =
|
||||
let
|
||||
u = natsWithStoreIdentity.systemd.services.swarm-bao-nats-callout-xkey;
|
||||
s = builtins.unsafeDiscardStringContext u.script;
|
||||
at = needle: lib.stringLength (lib.head (lib.splitString needle s));
|
||||
ensure = "swarm-nats-auth ensure-xkey --store-cert-role swarm-nats-auth";
|
||||
fetch = "bao kv get -field=value secret/swarm/public/swarm-nats/callout-xkey";
|
||||
file = "/var/lib/nixos-containers/swarm-nats/var/lib/nats-callout/callout-xkey.pub";
|
||||
in
|
||||
lib.elem "container@swarm-nats.service" u.before
|
||||
&& lib.elem "container@swarm-nats.service" u.wantedBy
|
||||
&& u.environment.BAO_CLIENT_CERT == "/etc/pki/nats-auth.pem"
|
||||
&& lib.hasInfix ensure s
|
||||
&& lib.hasInfix fetch s
|
||||
&& at ensure < at fetch
|
||||
&& lib.hasInfix "atomic_write_secret 0444 \"\" ${file}" s
|
||||
&& lib.hasInfix "refresh_consumer swarm-nats nats.service ${file}" s
|
||||
&& !(lib.hasInfix "exit 0" s)
|
||||
&& u.serviceConfig.Restart == "on-failure";
|
||||
}
|
||||
{
|
||||
name = "a queue host with no responder leaf renders an xkey fetch that fails naming the options";
|
||||
ok =
|
||||
let
|
||||
u = natsOldPath.systemd.services.swarm-bao-nats-callout-xkey;
|
||||
in
|
||||
!(u.environment ? BAO_CLIENT_CERT)
|
||||
&& lib.hasInfix "deploy.nats.authBaoClientCertFile" u.script
|
||||
&& lib.hasInfix "exit 1" u.script
|
||||
&& !(lib.hasInfix "ensure-xkey" u.script);
|
||||
}
|
||||
{
|
||||
# Its creates are what the responder's role was granted; on the store's
|
||||
# host the role's unit has to have run first.
|
||||
name = "on the store's host the callout xkey fetch waits for the responder's role";
|
||||
ok =
|
||||
let
|
||||
u = natsOnStoreHost.systemd.services.swarm-bao-nats-callout-xkey;
|
||||
in
|
||||
u.environment.BAO_CLIENT_CERT == "/var/lib/swarm-bao-pki/nats-auth.pem"
|
||||
&& lib.elem "swarm-bao-nats-auth-policy.service" u.after
|
||||
&& lib.elem "swarm-bao-nats-auth-policy.service" u.wants;
|
||||
}
|
||||
{
|
||||
# The responder refuses the hive's shared agent client and takes no flag
|
||||
# granting it subjects: an `--agent-publish-subject` fails the unit at
|
||||
|
|
|
|||
Loading…
Reference in a new issue