swarm: log in to bao before reading or writing a secret
`BAO_CLIENT_CERT` decides which certificate the TLS handshake presents. It is
not an identity: cert auth is a login, and a `bao kv` call without a token asks
its token helper for one instead — a `sh` neither unit carries on `path`.
Measured on this host, from `swarm-bao-matrix-token.service`:
swarm-bao did not return secret/swarm/matrix/registration-token
failed to get token helper: error expanding config path "":
exec: "sh": executable file not found in $PATH
So the first credential meant to travel through the store never has, and the
publisher added last week would not have either.
`-token-only` rather than a plain login: storing is the default, and it stores
through that same helper, so the obvious form reproduces the failure one line
further down. It is `-field=token -no-store`, which keeps the token on stdout
and out of the filesystem.
The two units degrade differently on purpose, and that is preserved. The matrix
fetch is `Wants=`-only and must not hold up the homeserver, so a refused login
reports why and keeps the token already in place. The publisher is
`Restart=on-failure`, where a store that cannot authenticate this host is worth
retrying and "published 0" would read as an ordinary quiet day.
`swarm-bao.nix` is untouched: it authenticates with the bootstrap token from a
file, which is a real identity and not a cert exchange. Its shape is where the
export idiom here comes from.
This commit is contained in:
parent
8104e5e018
commit
5e9b79a719
3 changed files with 66 additions and 0 deletions
|
|
@ -112,6 +112,22 @@ in
|
||||||
err="$(mktemp)"
|
err="$(mktemp)"
|
||||||
trap 'rm -f "$err"' EXIT
|
trap 'rm -f "$err"' EXIT
|
||||||
|
|
||||||
|
# Cert auth is a login, not a transport setting. The `BAO_CLIENT_*`
|
||||||
|
# variables above only decide which certificate the TLS handshake
|
||||||
|
# presents; without a token `bao` asks its token helper instead, and
|
||||||
|
# that is a `sh` this unit's `path` does not carry. `-token-only`
|
||||||
|
# answers on stdout and skips the helper on both sides.
|
||||||
|
if ! BAO_TOKEN="$(bao login -method=cert -token-only 2>"$err")"; then
|
||||||
|
echo "could not log in to swarm-bao with this host's certificate; keeping the token hive-matrix already has." >&2
|
||||||
|
if [ -s "$err" ]; then
|
||||||
|
cat "$err" >&2
|
||||||
|
else
|
||||||
|
echo "bao failed without writing a diagnostic." >&2
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
export BAO_TOKEN
|
||||||
|
|
||||||
if ! token="$(bao kv get -field=value ${lib.escapeShellArg tokenPath} 2>"$err")"; then
|
if ! token="$(bao kv get -field=value ${lib.escapeShellArg tokenPath} 2>"$err")"; then
|
||||||
echo "swarm-bao did not return ${tokenPath}; keeping the token hive-matrix already has." >&2
|
echo "swarm-bao did not return ${tokenPath}; keeping the token hive-matrix already has." >&2
|
||||||
if [ -s "$err" ]; then
|
if [ -s "$err" ]; then
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,18 @@ in
|
||||||
script = ''
|
script = ''
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Cert auth is a login, not a transport setting. The `BAO_CLIENT_*`
|
||||||
|
# variables above only decide which certificate the TLS handshake
|
||||||
|
# presents; without a token `bao` asks its token helper instead, and
|
||||||
|
# that is a `sh` this unit's `path` does not carry. `-token-only`
|
||||||
|
# answers on stdout and skips the helper on both sides.
|
||||||
|
#
|
||||||
|
# Unhandled on purpose: `Restart=on-failure` above is what a store that
|
||||||
|
# cannot authenticate this host should get. Degrading here would report
|
||||||
|
# "published 0" as an ordinary quiet day.
|
||||||
|
BAO_TOKEN="$(bao login -method=cert -token-only)"
|
||||||
|
export BAO_TOKEN
|
||||||
|
|
||||||
published=0
|
published=0
|
||||||
skipped=0
|
skipped=0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1089,6 +1089,44 @@ let
|
||||||
lib.hasInfix "secret/swarm/hives/h1/queue/agent" s
|
lib.hasInfix "secret/swarm/hives/h1/queue/agent" s
|
||||||
&& lib.hasInfix "secret/swarm/hives/h2/queue/agent" s;
|
&& lib.hasInfix "secret/swarm/hives/h2/queue/agent" s;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
# A property of the SET, not of one unit: both of these authenticate by
|
||||||
|
# certificate, and `BAO_CLIENT_CERT` is transport rather than identity, so
|
||||||
|
# a script that reaches `bao kv` without a token asks a token helper this
|
||||||
|
# host does not carry and fails before the store ever answers. `-token-only`
|
||||||
|
# is what keeps the token off the helper on the way back out.
|
||||||
|
#
|
||||||
|
# Ordering, not presence: the login has to come first, so the check is
|
||||||
|
# that nothing before it is a data command. Comments are stripped because
|
||||||
|
# both units explain this in prose directly above the code.
|
||||||
|
name = "the cert-identity bao units log in before their first read or write, and keep the token out of the helper";
|
||||||
|
ok =
|
||||||
|
let
|
||||||
|
code =
|
||||||
|
s:
|
||||||
|
lib.concatStringsSep "\n" (
|
||||||
|
lib.filter (l: builtins.match "[[:space:]]*#.*" l == null) (lib.splitString "\n" s)
|
||||||
|
);
|
||||||
|
holdsTokenFirst =
|
||||||
|
s:
|
||||||
|
let
|
||||||
|
c = code s;
|
||||||
|
in
|
||||||
|
lib.hasInfix "bao login" c
|
||||||
|
&& lib.hasInfix "-token-only" c
|
||||||
|
&& !(lib.hasInfix "bao kv" (lib.head (lib.splitString "bao login" c)));
|
||||||
|
in
|
||||||
|
holdsTokenFirst secretPublisherHere.systemd.services.swarm-secret-publish.script
|
||||||
|
&& holdsTokenFirst baoWithMatrix.systemd.services.swarm-bao-matrix-token.script
|
||||||
|
# Controls, so a clean verdict above means something. In order: a bare
|
||||||
|
# read is refused, a read placed before the login is refused, and a
|
||||||
|
# login that exists only in a comment is refused — that last one is the
|
||||||
|
# arm the comment-stripping exists for.
|
||||||
|
&& !(holdsTokenFirst "bao kv get -field=value secret/x")
|
||||||
|
&& !(holdsTokenFirst "bao kv get secret/x\nBAO_TOKEN=\"$(bao login -method=cert -token-only)\"")
|
||||||
|
&& !(holdsTokenFirst "# bao login -method=cert -token-only goes here\nbao kv get secret/x")
|
||||||
|
&& holdsTokenFirst "BAO_TOKEN=\"$(bao login -method=cert -token-only)\"\nbao kv get secret/x";
|
||||||
|
}
|
||||||
{
|
{
|
||||||
# The doctrine three glue files state, as a property a rewrite has to
|
# The doctrine three glue files state, as a property a rewrite has to
|
||||||
# keep: a client is defined by holding a certificate the store accepts,
|
# keep: a client is defined by holding a certificate the store accepts,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue