Watch
0
0
Fork
You've already forked hyperhive
0

nix trusts the swarm's root through its own CA bundle, so root's nix clients reach the cache

This commit is contained in:
atlas 2026-11-08 18:32:37 +01:00
commit 9d62ef44b5
2 changed files with 299 additions and 79 deletions

View file

@ -4,30 +4,39 @@
# ./attic-root-facts.sh.
#
# What runs is the rendered script, with the directory it writes moved into the
# test's scratch space.
# test's scratch space. `SCRIPT_HOSTCA` is the same script for a host whose nix
# sets its own `ssl-cert-file`, which becomes the base of the CA bundle.
{
pkgs,
...
}:
let
script = pkgs.writeText "attic-root-facts.sh" (
import ../host-modules/lib/attic-root-facts.nix { inherit (pkgs) lib; } {
hive = "alpha";
host = "cache.example";
dir = "@DIR@/attic";
url = "https://cache.example/swarm";
confFile = "@DIR@/nix/cache.conf";
netrcFile = "@DIR@/nix/netrc";
hostNetrc = "@DIR@/host-netrc";
}
);
render =
baseBundle:
pkgs.writeText "attic-root-facts.sh" (
import ../host-modules/lib/attic-root-facts.nix { inherit (pkgs) lib; } {
hive = "alpha";
host = "cache.example";
dir = "@DIR@/attic";
url = "https://cache.example/swarm";
confFile = "@DIR@/nix/cache.conf";
netrcFile = "@DIR@/nix/netrc";
hostNetrc = "@DIR@/host-netrc";
caBundleFile = "@DIR@/nix/ca-bundle.crt";
inherit baseBundle;
swarmCa = "@DIR@/swarm-root.pem";
}
);
in
pkgs.runCommand "hyperhive-script-test-attic-root-facts"
{
SCRIPT = script;
SCRIPT = render "@DIR@/system-bundle.crt";
SCRIPT_HOSTCA = render "@DIR@/host-ca.pem";
nativeBuildInputs = [
pkgs.coreutils
pkgs.curl
pkgs.gnugrep
pkgs.openssl
];
}
''

View file

@ -62,11 +62,39 @@ STUB
chmod +x "$dir/bin/systemctl"
export PATH="$dir/bin:$PATH"
# Certificates made here, with fixed names and subjects: `system-ca` stands for
# a CA the host's own bundle holds, `swarm-ca` for the swarm's root, `host-ca`
# for a bundle the host's nix.conf names. `swarm-leaf` and `system-leaf` are
# servers for localhost signed by the first two.
pki=$dir/pki
mkdir -p "$pki"
newca() { # <name>
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
-keyout "$pki/$1.key" -out "$pki/$1.pem" -subj "/CN=$1" -days 2 \
-addext basicConstraints=critical,CA:TRUE -addext keyUsage=keyCertSign 2>/dev/null
}
newleaf() { # <ca> <name>
openssl req -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
-keyout "$pki/$2.key" -out "$pki/$2.csr" -subj "/CN=localhost" 2>/dev/null
printf 'subjectAltName=DNS:localhost\nbasicConstraints=CA:FALSE\n' >"$pki/$2.ext"
openssl x509 -req -in "$pki/$2.csr" -CA "$pki/$1.pem" -CAkey "$pki/$1.key" \
-CAcreateserial -out "$pki/$2.pem" -days 2 -extfile "$pki/$2.ext" 2>/dev/null
}
newca system-ca
newca swarm-ca
newca host-ca
newleaf swarm-ca swarm-leaf
newleaf system-ca system-leaf
certs() { grep -c -- '-----BEGIN CERTIFICATE-----' "$1" 2>/dev/null || echo 0; }
KEYLEAF=secret_swarm_hives_alpha_attic_public-key
TOKENLEAF=secret_swarm_hives_alpha_attic_pull-token
reset() {
rm -rf "$dir/store" "$dir/attic" "$dir/nix" "$dir/host-netrc" "$calls" "$dir/systemctl"
cp "$pki/system-ca.pem" "$dir/system-bundle.crt"
cp "$pki/swarm-ca.pem" "$dir/swarm-root.pem"
cp "$pki/host-ca.pem" "$dir/host-ca.pem"
mkdir -p "$dir/store"
: >"$calls"
: >"$dir/systemctl"
@ -97,9 +125,10 @@ expect "netrc" "$(body "$dir/nix/netrc")" "machine cache.example password tok-1"
expect "key mode" "$(mode "$dir/attic/public-key")" 600
expect "netrc mode" "$(mode "$dir/nix/netrc")" 600
expect "dir mode" "$(mode "$dir/attic")" 700
expect "fragment names the cache, the key and the root-only netrc" "$(body "$dir/nix/cache.conf")" "extra-substituters = https://cache.example/swarm
expect "fragment names the cache, the key, the root-only netrc and the CA bundle" "$(body "$dir/nix/cache.conf")" "extra-substituters = https://cache.example/swarm
extra-trusted-public-keys = swarm:S0VZ
netrc-file = $dir/nix/netrc"
netrc-file = $dir/nix/netrc
ssl-cert-file = $dir/nix/ca-bundle.crt"
expect "fragment mode" "$(mode "$dir/nix/cache.conf")" 644
expect "fragment dir mode" "$(mode "$dir/nix")" 755
expect "no secret in the fragment" "$(grep -c tok-1 "$dir/nix/cache.conf")" 0
@ -314,6 +343,123 @@ run
expect "exit" $? 1
expect "no fragment" "$(body "$dir/nix/cache.conf")" missing
# The CA bundle the fragment names: the host's own CAs, then the swarm's root.
bundle=$dir/nix/ca-bundle.crt
case="the bundle holds the host's CAs and the swarm's root, 0644, and the fragment names it"
reset
seed "swarm:S0VZ" "tok-1"
run
expect "exit" $? 0
expect "bundle" "$(body "$bundle")" "$(cat "$pki/system-ca.pem")
$(cat "$pki/swarm-ca.pem")"
expect "two certificates" "$(certs "$bundle")" 2
expect "mode" "$(mode "$bundle")" 644
expect "fragment names it" "$(grep -c "^ssl-cert-file = $bundle\$" "$dir/nix/cache.conf")" 1
expect "control: the host's bundle held one certificate" "$(certs "$dir/system-bundle.crt")" 1
expect "the host's bundle is not written" "$(body "$dir/system-bundle.crt")" "$(cat "$pki/system-ca.pem")"
case="the swarm and host servers verify against the bundle, and the swarm one does not against the host CAs alone"
expect "swarm leaf, bundle" "$(openssl verify -CAfile "$bundle" "$pki/swarm-leaf.pem" >/dev/null 2>&1 && echo ok || echo bad)" ok
expect "system leaf, bundle" "$(openssl verify -CAfile "$bundle" "$pki/system-leaf.pem" >/dev/null 2>&1 && echo ok || echo bad)" ok
expect "control: swarm leaf, host's CAs alone" "$(openssl verify -CAfile "$dir/system-bundle.crt" "$pki/swarm-leaf.pem" >/dev/null 2>&1 && echo ok || echo bad)" bad
# curl takes the file as nix does. A TLS server for localhost with the swarm's
# leaf, on a port picked at random until one binds.
case="curl trusts the swarm's server with the bundle and refuses it with the host's CAs alone"
port=
for _ in 1 2 3 4 5 6 7 8; do
p=$((20000 + RANDOM % 20000))
openssl s_server -www -accept "$p" -cert "$pki/swarm-leaf.pem" -key "$pki/swarm-leaf.key" \
>"$dir/s_server.log" 2>&1 &
server=$!
for _ in 1 2 3 4 5 6 7 8 9 10; do
if curl -s --cacert "$pki/swarm-ca.pem" "https://localhost:$p/" >/dev/null 2>&1; then port=$p; break; fi
kill -0 "$server" 2>/dev/null || break
sleep 0.3
done
[ -n "$port" ] && break
kill "$server" 2>/dev/null
done
if [ -z "$port" ]; then
fail "no TLS server for the curl check came up: $(cat "$dir/s_server.log")"
else
curl -s --cacert "$bundle" "https://localhost:$port/" >/dev/null 2>&1
expect "with the bundle" $? 0
curl -s --cacert "$dir/system-bundle.crt" "https://localhost:$port/" >/dev/null 2>&1
expect "with the host's CAs alone" $? 60
kill "$server" 2>/dev/null
fi
case="a second run with the same CAs rewrites nothing"
b=$(ino "$bundle")
run
expect "exit" $? 0
expect "bundle untouched" "$(ino "$bundle")" "$b"
case="a bundle left 0640 is rewritten 0644 though its content is current"
chmod 640 "$bundle"
run
expect "exit" $? 0
expect "mode" "$(mode "$bundle")" 644
case="a host that sets its own ssl-cert-file has that file merged in, not replaced"
reset
seed "swarm:S0VZ" "tok-1"
sed -e "s|@DIR@|$dir|g" "$SCRIPT_HOSTCA" >"$dir/script-hostca.sh"
cp "$dir/script.sh" "$dir/script-default.sh"
cp "$dir/script-hostca.sh" "$dir/script.sh"
run
expect "exit" $? 0
expect "the host's file and the swarm's root" "$(body "$bundle")" "$(cat "$pki/host-ca.pem")
$(cat "$pki/swarm-ca.pem")"
line=$(sed -n 2p "$pki/system-ca.pem")
expect "the default bundle's CA is not in it" "$(grep -c -x -F -- "$line" "$bundle")" 0
expect "control: the same search finds it in its own file" "$(grep -c -x -F -- "$line" "$pki/system-ca.pem")" 1
expect "the host's file is not written" "$(body "$dir/host-ca.pem")" "$(cat "$pki/host-ca.pem")"
cat "$pki/system-ca.pem" >>"$dir/host-ca.pem"
run
expect "an edit to the host's file reaches the bundle" "$(certs "$bundle")" 3
cp "$dir/script-default.sh" "$dir/script.sh"
case="a swarm root that is not on the host yet leaves the host's CAs alone, and a later run adds it"
reset
seed "swarm:S0VZ" "tok-1"
rm "$dir/swarm-root.pem"
run DAEMON_STARTED="Sat 2000-01-01 00:00:00.000000 UTC"
expect "exit" $? 0
expect "bundle" "$(body "$bundle")" "$(cat "$pki/system-ca.pem")"
expect "the fragment still names it" "$(grep -c "^ssl-cert-file = $bundle\$" "$dir/nix/cache.conf")" 1
expect "said so" "$(grep -c "swarm-root.pem is not on this host yet" "$dir/out")" 1
started=$(now)
c=$(ino "$dir/nix/cache.conf")
: >"$dir/systemctl"
cp "$pki/swarm-ca.pem" "$dir/swarm-root.pem"
run DAEMON_STARTED="$started"
expect "exit" $? 0
expect "root added" "$(certs "$bundle")" 2
expect "fragment untouched" "$(ino "$dir/nix/cache.conf")" "$c"
expect "no restart: nix reads the bundle per download" "$(restarts)" 0
case="a swarm root file holding no certificate is not appended"
reset
seed "swarm:S0VZ" "tok-1"
echo "garbage" >"$dir/swarm-root.pem"
run
expect "exit" $? 0
expect "bundle" "$(body "$bundle")" "$(cat "$pki/system-ca.pem")"
case="a host bundle that cannot be read fails the run and leaves the fragment and bundle"
good
cp "$bundle" "$dir/bundle-before"
rm "$dir/system-bundle.crt"
seed "swarm:NEW" "tok-new"
run
expect "exit" $? 1
expect "says why" "$(grep -c "cannot read the host.s CA bundle" "$dir/err")" 1
expect "bundle kept" "$(body "$bundle")" "$(body "$dir/bundle-before")"
expect "fragment kept" "$(grep -c 'extra-trusted-public-keys = swarm:S0VZ' "$dir/nix/cache.conf")" 1
if [ "$failures" -ne 0 ]; then
echo "$failures of $count checks failed" >&2
exit 1