From 7dff4efc4a9963c501ab842915e48dcbfff66e99 Mon Sep 17 00:00:00 2001 From: eldritch horrors Date: Mon, 22 Dec 2025 23:31:06 +0100 Subject: [PATCH] testing: migrate build-remote-trustless-* Change-Id: I3edc3fe5babc93833bfe1e7c4bdeb02dc6ac17cd --- .../build-remote-trustless-after.sh | 2 - .../build-remote-trustless-should-fail-0.sh | 29 ---- .../build-remote-trustless-should-pass-0.sh | 9 -- .../build-remote-trustless-should-pass-1.sh | 9 -- .../build-remote-trustless-should-pass-2.sh | 13 -- .../build-remote-trustless-should-pass-3.sh | 14 -- tests/functional/build-remote-trustless.sh | 13 -- tests/functional/meson.build | 5 - tests/functional/nix-daemon-untrusting.sh | 3 - tests/functional2/build/test_remote.py | 152 ++++++++++++++++++ .../global_assets/build-hook-ca-fixed.nix | 62 +++++++ .../testlib/global_assets/build-hook.nix | 63 ++++++++ 12 files changed, 277 insertions(+), 97 deletions(-) delete mode 100644 tests/functional/build-remote-trustless-after.sh delete mode 100644 tests/functional/build-remote-trustless-should-fail-0.sh delete mode 100644 tests/functional/build-remote-trustless-should-pass-0.sh delete mode 100644 tests/functional/build-remote-trustless-should-pass-1.sh delete mode 100644 tests/functional/build-remote-trustless-should-pass-2.sh delete mode 100644 tests/functional/build-remote-trustless-should-pass-3.sh delete mode 100644 tests/functional/build-remote-trustless.sh delete mode 100755 tests/functional/nix-daemon-untrusting.sh create mode 100644 tests/functional2/build/test_remote.py create mode 100644 tests/functional2/testlib/global_assets/build-hook-ca-fixed.nix create mode 100644 tests/functional2/testlib/global_assets/build-hook.nix diff --git a/tests/functional/build-remote-trustless-after.sh b/tests/functional/build-remote-trustless-after.sh deleted file mode 100644 index 19f59e6ae..000000000 --- a/tests/functional/build-remote-trustless-after.sh +++ /dev/null @@ -1,2 +0,0 @@ -outPath=$(readlink -f $TEST_ROOT/result) -grep 'FOO BAR BAZ' ${remoteDir}/${outPath} diff --git a/tests/functional/build-remote-trustless-should-fail-0.sh b/tests/functional/build-remote-trustless-should-fail-0.sh deleted file mode 100644 index e938e63a2..000000000 --- a/tests/functional/build-remote-trustless-should-fail-0.sh +++ /dev/null @@ -1,29 +0,0 @@ -source common.sh - -enableFeatures "daemon-trust-override" - -restartDaemon - -requireSandboxSupport -[[ $busybox =~ busybox ]] || skipTest "no busybox" - -unset NIX_STORE_DIR - -# We first build a dependency of the derivation we eventually want to -# build. -nix-build build-hook.nix -A passthru.input2 \ - -o "$TEST_ROOT/input2" \ - --arg busybox "$busybox" \ - --store "$TEST_ROOT/local" \ - --option system-features bar - -# Now when we go to build that downstream derivation, Lix will try to -# copy our already-build `input2` to the remote store. That store object -# is input-addressed, so this will fail. - -file=build-hook.nix -prog=$(readlink -e ./nix-daemon-untrusting.sh) -proto=ssh-ng - -expectStderr 1 source build-remote-trustless.sh \ - | grepQuiet "cannot add path '[^ ]*' because it lacks a signature by a trusted key" diff --git a/tests/functional/build-remote-trustless-should-pass-0.sh b/tests/functional/build-remote-trustless-should-pass-0.sh deleted file mode 100644 index 2a7ebd8c6..000000000 --- a/tests/functional/build-remote-trustless-should-pass-0.sh +++ /dev/null @@ -1,9 +0,0 @@ -source common.sh - -# Remote trusts us -file=build-hook.nix -prog=nix-store -proto=ssh - -source build-remote-trustless.sh -source build-remote-trustless-after.sh diff --git a/tests/functional/build-remote-trustless-should-pass-1.sh b/tests/functional/build-remote-trustless-should-pass-1.sh deleted file mode 100644 index 516bdf092..000000000 --- a/tests/functional/build-remote-trustless-should-pass-1.sh +++ /dev/null @@ -1,9 +0,0 @@ -source common.sh - -# Remote trusts us -file=build-hook.nix -prog=nix-daemon -proto=ssh-ng - -source build-remote-trustless.sh -source build-remote-trustless-after.sh diff --git a/tests/functional/build-remote-trustless-should-pass-2.sh b/tests/functional/build-remote-trustless-should-pass-2.sh deleted file mode 100644 index b769a88f0..000000000 --- a/tests/functional/build-remote-trustless-should-pass-2.sh +++ /dev/null @@ -1,13 +0,0 @@ -source common.sh - -enableFeatures "daemon-trust-override" - -restartDaemon - -# Remote doesn't trust us -file=build-hook.nix -prog=$(readlink -e ./nix-daemon-untrusting.sh) -proto=ssh-ng - -source build-remote-trustless.sh -source build-remote-trustless-after.sh diff --git a/tests/functional/build-remote-trustless-should-pass-3.sh b/tests/functional/build-remote-trustless-should-pass-3.sh deleted file mode 100644 index 40f81da5a..000000000 --- a/tests/functional/build-remote-trustless-should-pass-3.sh +++ /dev/null @@ -1,14 +0,0 @@ -source common.sh - -enableFeatures "daemon-trust-override" - -restartDaemon - -# Remote doesn't trusts us, but this is fine because we are only -# building (fixed) CA derivations. -file=build-hook-ca-fixed.nix -prog=$(readlink -e ./nix-daemon-untrusting.sh) -proto=ssh-ng - -source build-remote-trustless.sh -source build-remote-trustless-after.sh diff --git a/tests/functional/build-remote-trustless.sh b/tests/functional/build-remote-trustless.sh deleted file mode 100644 index a0733fd4a..000000000 --- a/tests/functional/build-remote-trustless.sh +++ /dev/null @@ -1,13 +0,0 @@ -requireSandboxSupport -[[ $busybox =~ busybox ]] || skipTest "no busybox" - -unset NIX_STORE_DIR - -remoteDir=$TEST_ROOT/remote - -# Note: ssh{-ng}://localhost bypasses ssh. See tests/functional/build-remote.sh for -# more details. -nix-build $file -o $TEST_ROOT/result --max-jobs 0 \ - --arg busybox $busybox \ - --store $TEST_ROOT/local \ - --builders "$proto://localhost?remote-program=$prog&remote-store=${remoteDir}%3Fsystem-features=foo%20bar%20baz - - 1 1 foo,bar,baz" diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 555c03c2f..53fd22560 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -98,11 +98,6 @@ functional_tests_scripts = [ 'dependencies.sh', 'check-reqs.sh', 'build-remote-content-addressed-fixed.sh', - 'build-remote-trustless-should-pass-0.sh', - 'build-remote-trustless-should-pass-1.sh', - 'build-remote-trustless-should-pass-2.sh', - 'build-remote-trustless-should-pass-3.sh', - 'build-remote-trustless-should-fail-0.sh', 'build-jobless.sh', 'nar-access.sh', 'impure-eval.sh', diff --git a/tests/functional/nix-daemon-untrusting.sh b/tests/functional/nix-daemon-untrusting.sh deleted file mode 100755 index 22f0d45ae..000000000 --- a/tests/functional/nix-daemon-untrusting.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -exec nix-daemon --force-untrusted "$@" diff --git a/tests/functional2/build/test_remote.py b/tests/functional2/build/test_remote.py new file mode 100644 index 000000000..63968f97c --- /dev/null +++ b/tests/functional2/build/test_remote.py @@ -0,0 +1,152 @@ +import pytest +import re +import textwrap +from urllib.parse import urlencode, quote +import sys + +from functional2.testlib.fixtures.nix import Nix +from functional2.testlib.fixtures.file_helper import with_files +from functional2.testlib.utils import get_global_asset +from functional2.testlib.fixtures.env import ManagedEnv + + +@pytest.fixture +def busybox_args(env: ManagedEnv) -> list[str]: + return ["--arg", "busybox", env.path.which("busybox")] + + +@pytest.fixture(autouse=True) +def _setup_for_remote_builds(nix: Nix, env: ManagedEnv): + # always add bash, otherwise lix can't execute the build hook + env.path.add_program("bash") + # we don't always use this feature, but it also doesn't hurt + nix.settings.feature("daemon-trust-override") + + +def _builders(proto: str, flags: list[str], env: ManagedEnv) -> str: + prog = "nix-store" if proto == "ssh" else "nix-daemon" + script = f"""\ + #!{sys.executable} + import os, sys + os.execvp("{env.dirs.nix_bin_dir}/nix", [*{[prog, *flags]!s}, *sys.argv[1:]]) + """ + path = env.dirs.test_root / "remote-builder" / "launch.py" + path.parent.mkdir() + path.write_text(textwrap.dedent(script)) + path.chmod(0o755) + + remote_store = "local?" + urlencode( + {"system-features": "foo bar baz", "root": str(env.dirs.home / "remote")}, quote_via=quote + ) + uri_args = urlencode( + {"remote-program": str(path), "remote-store": remote_store}, quote_via=quote + ) + return textwrap.dedent(f""" + version = 1 + + [machines.remote] + uri = "{proto}://localhost?{uri_args}" + jobs = 8 + speed-factor = 1 + supported-features = [ "foo", "bar", "baz" ] + """) + + +@pytest.mark.full_sandbox +@with_files( + { + "build-hook.nix": get_global_asset("build-hook.nix"), + "config.nix": get_global_asset("config.nix"), + } +) +def test_remote_trustless_unsigned(nix: Nix, env: ManagedEnv, busybox_args: list[str]): + # We first build a dependency of the derivation we eventually want to build. + nix.nix_build( + [ + "build-hook.nix", + "-A", + "passthru.input2", + *busybox_args, + "--option", + "system-features", + "bar", + ] + ).run().ok() + + # Now when we go to build that downstream derivation, Lix will try to + # copy our already-build `input2` to the remote store. That store object + # is input-addressed, so this will fail. + + result = nix.nix_build( + [ + "build-hook.nix", + "--max-jobs", + "0", + *busybox_args, + "--builders", + _builders("ssh-ng", ["--force-untrusted"], env), + ] + ).run() + result.expect(1) + assert re.findall( + r"cannot add path '[^ ]*' because it lacks a signature by a trusted key", + result.stderr_plain, + ) + + +@pytest.mark.full_sandbox +@pytest.mark.parametrize( + ("protocol", "flags"), [("ssh", []), ("ssh-ng", []), ("ssh-ng", ["--force-untrusted"])] +) +@with_files( + { + "build-hook.nix": get_global_asset("build-hook.nix"), + "config.nix": get_global_asset("config.nix"), + } +) +def test_remote_trustless_ia( + nix: Nix, env: ManagedEnv, busybox_args: list[str], protocol: str, flags: list[str] +): + result = nix.nix_build( + [ + "build-hook.nix", + "--max-jobs", + "0", + *busybox_args, + "--builders", + _builders(protocol, flags, env), + ] + ).run() + result.ok() + + out_path = (env.dirs.home / "result").readlink() + assert nix.physical_store_path_for(out_path).read_text() == "FOO BAR BAZ\n" + + +@pytest.mark.full_sandbox +@pytest.mark.parametrize(("protocol", "flags"), [("ssh", []), ("ssh-ng", ["--force-untrusted"])]) +@with_files( + { + "build-hook-ca-fixed.nix": get_global_asset("build-hook-ca-fixed.nix"), + "config.nix": get_global_asset("config.nix"), + } +) +def test_remote_trustless_ca( + nix: Nix, env: ManagedEnv, busybox_args: list[str], protocol: str, flags: list[str] +): + # Remote doesn't trusts us, but this is fine because we are only + # building (fixed) CA derivations. + result = nix.nix_build( + [ + "build-hook-ca-fixed.nix", + "--max-jobs", + "0", + *busybox_args, + "--builders", + _builders(protocol, flags, env), + ] + ).run() + result.ok() + + out_path = (env.dirs.home / "result").readlink() + assert nix.physical_store_path_for(out_path).read_text() == "FOO BAR BAZ\n" diff --git a/tests/functional2/testlib/global_assets/build-hook-ca-fixed.nix b/tests/functional2/testlib/global_assets/build-hook-ca-fixed.nix new file mode 100644 index 000000000..3da2de92c --- /dev/null +++ b/tests/functional2/testlib/global_assets/build-hook-ca-fixed.nix @@ -0,0 +1,62 @@ +{ busybox }: + +with import ./config.nix; + +let + + mkDerivation = args: + derivation ({ + inherit system; + builder = busybox; + args = ["sh" "-e" args.builder or (builtins.toFile "builder-${args.name}.sh" '' + if [ -e "$NIX_ATTRS_SH_FILE" ]; then source $NIX_ATTRS_SH_FILE; fi; + eval "$buildCommand" + '')]; + outputHashMode = "recursive"; + outputHashAlgo = "sha256"; + } // removeAttrs args ["builder" "meta" "passthru"]) + // { meta = args.meta or {}; passthru = args.passthru or {}; }; + + input1 = mkDerivation { + shell = busybox; + name = "build-remote-input-1"; + buildCommand = "echo hi-input1; echo FOO > $out"; + requiredSystemFeatures = ["foo"]; + outputHash = "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="; + }; + + input2 = mkDerivation { + shell = busybox; + name = "build-remote-input-2"; + buildCommand = "echo hi; echo BAR > $out"; + requiredSystemFeatures = ["bar"]; + outputHash = "sha256-XArauVH91AVwP9hBBQNlkX9ccuPpSYx9o0zeIHb6e+Q="; + }; + + input3 = mkDerivation { + shell = busybox; + name = "build-remote-input-3"; + # `echo -n` tests handling of logs without trailing newlines + buildCommand = '' + echo -n hi-input3 + read x < ${input2} + echo $x BAZ > $out + ''; + requiredSystemFeatures = ["baz"]; + outputHash = "sha256-daKAcPp/+BYMQsVi/YYMlCKoNAxCNDsaivwSHgQqD2s="; + }; + +in + + mkDerivation { + shell = busybox; + name = "build-remote"; + passthru = { inherit input1 input2 input3; }; + buildCommand = + '' + read x < ${input1} + read y < ${input3} + echo "$x $y" > $out + ''; + outputHash = "sha256-5SxbkUw6xe2l9TE1uwCvTtTDysD1vhRor38OtDF0LqQ="; + } diff --git a/tests/functional2/testlib/global_assets/build-hook.nix b/tests/functional2/testlib/global_assets/build-hook.nix new file mode 100644 index 000000000..8f17f7033 --- /dev/null +++ b/tests/functional2/testlib/global_assets/build-hook.nix @@ -0,0 +1,63 @@ +{ busybox, contentAddressed ? false }: + +with import ./config.nix; + +let + + caArgs = if contentAddressed then { + outputHashMode = "recursive"; + outputHashAlgo = "sha256"; + __contentAddressed = true; + } else {}; + + mkDerivation = args: + derivation ({ + inherit system; + builder = busybox; + args = ["sh" "-e" args.builder or (builtins.toFile "builder-${args.name}.sh" '' + if [ -e "$NIX_ATTRS_SH_FILE" ]; then source $NIX_ATTRS_SH_FILE; fi; + eval "$buildCommand" + '')]; + } // removeAttrs args ["builder" "meta" "passthru"] + // caArgs) + // { meta = args.meta or {}; passthru = args.passthru or {}; }; + + input1 = mkDerivation { + shell = busybox; + name = "build-remote-input-1"; + buildCommand = "echo hi-input1; echo FOO > $out"; + requiredSystemFeatures = ["foo"]; + }; + + input2 = mkDerivation { + shell = busybox; + name = "build-remote-input-2"; + buildCommand = "echo hi; echo BAR > $out"; + requiredSystemFeatures = ["bar"]; + }; + + input3 = mkDerivation { + shell = busybox; + name = "build-remote-input-3"; + # `echo -n` tests handling of logs without trailing newlines + buildCommand = '' + echo -n hi-input3 + read x < ${input2} + echo $x BAZ > $out + ''; + requiredSystemFeatures = ["baz"]; + }; + +in + + mkDerivation { + shell = busybox; + name = "build-remote"; + passthru = { inherit input1 input2 input3; }; + buildCommand = + '' + read x < ${input1} + read y < ${input3} + echo "$x $y" > $out + ''; + }