# Cases for the closure pull (hive-c0re/src/cache_pull.rs): `nix-store --realise # --max-jobs 0` against a store whose configuration is what the daemon # has on a hive host, the host's own substituter plus the swarm cache from the # nix.conf fragment. # # The swarm cache holds the top path and NOT its dependency, as attic does for # a path cache.nixos.org serves; a second cache, standing for cache.nixos.org, # holds the dependency. The stores are chroot stores, so the realise runs in # this process where a host runs it in the daemon; the substitution logic is # the same. # # The last cases are hive-priv's step before its `nix store verify` # (hive-priv/src/closure.rs): a path the host built itself is valid in its store # without the swarm signature, the pull leaves it so, and `nix store copy-sigs` # imports the signature from the cache. set -uo pipefail failures=0 count=0 case="setup" fail() { echo "FAILED: $case: $*" >&2 failures=$((failures + 1)) } expect() { # count=$((count + 1)) if [ "$2" != "$3" ]; then fail "$1: got '$2', wanted '$3'"; fi } T=$(mktemp -d) export HOME=$T/home XDG_CONFIG_HOME=$T/home NIX_USER_CONF_FILES= mkdir -p "$HOME" "$T/empty" export NIX_CONF_DIR=$T/empty printf 'build-users-group =\nsandbox = false\nexperimental-features = nix-command\n' >"$T/empty/nix.conf" src="local?root=$T/src" dep=$(nix eval --raw --store "$src" --expr 'builtins.toFile "dep" "dependency"') top=$(nix eval --raw --store "$src" --expr 'let dep = builtins.toFile "dep" "dependency"; in builtins.toFile "top" "needs ${dep}"') nix-store --generate-binary-cache-key swarm-test-1 "$T/swarm.sec" "$T/swarm.pub" >/dev/null nix-store --generate-binary-cache-key upstream-test-1 "$T/up.sec" "$T/up.pub" >/dev/null attic=file://$T/attic upstream=file://$T/upstream nix copy --from "$src" --to "$attic?secret-key=$T/swarm.sec" "$top" nix copy --from "$src" --to "$upstream?secret-key=$T/up.sec" "$dep" # What attic leaves out because the upstream serves it. rm -f "$T/attic/$(basename "$dep" | cut -c1-32).narinfo" # The daemon's nix.conf: its own substituter and key, then the include. # conf : a NIX_CONF_DIR with that fragment. conf() { mkdir -p "$T/$1" printf 'build-users-group =\nsandbox = false\nsubstituters = %s\ntrusted-public-keys = %s\n!include %s/cache.conf\n' \ "$upstream" "$(cat "$T/up.pub")" "$T/$1" >"$T/$1/nix.conf" printf '%s\n' "$2" >"$T/$1/cache.conf" } fragment="extra-substituters = $attic extra-trusted-public-keys = $(cat "$T/swarm.pub")" # The paths are content-addressed (`builtins.toFile`), which `require-sigs` does # not apply to, so this test cannot show the signature gate; hive-priv's # `nix store verify` step and the fragment test carry the key. # pull : the exact pull of cache_pull.rs. pull() { NIX_CONF_DIR=$T/$1 nix-store --store "local?root=$T/$2" --realise "$top" --max-jobs 0 >/dev/null 2>"$T/err" } valid() { # nix-store --store "local?root=$T/$1" --check-validity "$2" 2>/dev/null && echo yes || echo no } narinfo() { # : whether the cache has its narinfo if [ -e "$T/$1/$(basename "$2" | cut -c1-32).narinfo" ]; then echo yes; else echo no; fi } case="control: the swarm cache has the top path and lacks the dependency" expect "attic has the top path" "$(narinfo attic "$top")" yes expect "attic lacks the dependency" "$(narinfo attic "$dep")" no expect "upstream has the dependency" "$(narinfo upstream "$dep")" yes case="the client-side copy that failed on a host fails here too" if nix copy --from "$attic" --to "local?root=$T/d0" "$top" 2>"$T/err"; then fail "nix copy --from the swarm cache alone succeeded" fi expect "no top path" "$(valid d0 "$top")" no case="the daemon with both substituters realises the top path and its dependency" conf both "$fragment" pull both d1 expect "exit" $? 0 expect "top path valid" "$(valid d1 "$top")" yes expect "dependency valid" "$(valid d1 "$dep")" yes case="without the fragment the daemon has only the upstream and cannot realise the top path" conf plain "" if pull plain d2; then fail "realised without the swarm cache"; fi expect "no top path" "$(valid d2 "$top")" no # A path built on the host is input-addressed and so, unlike the toFile paths # above, has no content address that lets `nix store verify` pass it unsigned. # The stores here share one store directory (`store=`), as cache and host do. case="setup: a path built in the host store, and the swarm cache holding it signed" : "${HIVE_TEST_BASH:=$(command -v bash)}" host="local?store=$T/hs&state=$T/hs-state&log=$T/hs-log" built=$(nix build --store "$host" --no-link --print-out-paths --impure --expr "derivation { name = \"agent-closure\"; system = builtins.currentSystem; builder = \"$HIVE_TEST_BASH\"; args = [ \"-c\" \"echo agent > \$out\" ]; }") nix copy --from "$host" --to "file://$T/signed?store=$T/hs&secret-key=$T/swarm.sec" "$built" swarm_key=$(cat "$T/swarm.pub") other_key=$(cat "$T/up.pub") # verify : hive-priv's check, against the host store. verify() { nix store verify --store "$host" --no-contents --sigs-needed 1 --option trusted-public-keys "$1" "$built" >/dev/null 2>&1 echo $? } # copy_sigs : the exact arguments of copy_sigs_args, bar the store. copy_sigs() { nix store copy-sigs --store "$host" --substituter "$1" --option netrc-file "$T/netrc" "$built" >/dev/null 2>"$T/err" } case="control: the path is valid in the host store and the cache serves it signed" expect "valid" "$(nix-store --store "$host" --check-validity "$built" && echo yes || echo no)" yes expect "the cache has its narinfo" "$(if [ -e "$T/signed/$(basename "$built" | cut -c1-32).narinfo" ]; then echo yes; else echo no; fi)" yes case="a path valid in the host store without the signature fails the verify" expect "verify exit" "$(verify "$swarm_key")" 2 case="a cache without the path imports nothing and the verify still refuses" copy_sigs "file://$T/empty-cache?store=$T/hs" expect "copy-sigs exit" $? 0 expect "verify exit" "$(verify "$swarm_key")" 2 case="the cache serving the path imports the signature and the verify passes" copy_sigs "file://$T/signed?store=$T/hs" expect "copy-sigs exit" $? 0 expect "verify exit" "$(verify "$swarm_key")" 0 expect "another key still fails" "$(verify "$other_key")" 2 echo "$count checks, $failures failed" [ "$failures" = 0 ]