From 0741e242664d2b9f6d27bb176591701fc4ae8b6e Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Thu, 5 Feb 2026 18:02:36 +0100 Subject: [PATCH] tests/functional2: migrate (un|)sandboxed CA tests CA tests have grown quite complicated and are hard to debug and manoeuver for new needs. They are a prime target for F2 migration and they were extremely pleasant to write. Most functionality is preserved or improved with additional behaviors asserted on warnings and the full set of reports. Change-Id: I70cd52abbe5ff13f56744b966ab00962ad3a49d6 Signed-off-by: Raito Bezarius --- tests/functional/meson.build | 2 - .../sandboxed-certificate-authorities.sh | 72 --------- .../unsandboxed-certificate-authorities.sh | 54 ------- .../build/assets/certificate-test.nix} | 0 tests/functional2/build/test_ca.py | 137 ++++++++++++++++++ 5 files changed, 137 insertions(+), 128 deletions(-) delete mode 100644 tests/functional/sandboxed-certificate-authorities.sh delete mode 100644 tests/functional/unsandboxed-certificate-authorities.sh rename tests/{functional/cert-test.nix => functional2/build/assets/certificate-test.nix} (100%) create mode 100644 tests/functional2/build/test_ca.py diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 0aa9dc3a5..d8041f9d2 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -39,8 +39,6 @@ functional_tests_scripts = [ 'legacy-ssh-store.sh', 'experimental-features.sh', 'fetchMercurial.sh', - 'sandboxed-certificate-authorities.sh', - 'unsandboxed-certificate-authorities.sh', 'gc-auto.sh', 'user-envs.sh', 'binary-cache.sh', diff --git a/tests/functional/sandboxed-certificate-authorities.sh b/tests/functional/sandboxed-certificate-authorities.sh deleted file mode 100644 index 5a2b6b5b7..000000000 --- a/tests/functional/sandboxed-certificate-authorities.sh +++ /dev/null @@ -1,72 +0,0 @@ -# This tests checks that CA works in builder's environments, *with* a sandbox. -source common.sh - -requireSandboxSupport -needLocalStore "the sandbox only runs on the builder side, so it makes no sense to test it with the daemon" - -clearStore - -# Note: we need to bind-mount $SHELL into the chroot. Currently we -# only support the case where $SHELL is in the Nix store, because -# otherwise things get complicated (e.g. if it's in /bin, do we need -# /lib as well?). -if [[ ! $SHELL =~ /nix/store ]]; then skipTest "Shell is not from Nix store"; fi -# An alias to automatically bind-mount the $SHELL on nix-build invocations -nix-sandbox-build () { nix-build --no-out-link --sandbox-paths /nix/store "$@"; } - -chmod -R u+w $TEST_ROOT/store0 || true -rm -rf $TEST_ROOT/store0 - -export NIX_STORE_DIR=/my/store -export NIX_REMOTE=$TEST_ROOT/store0 - -## Test mounting of SSL certificates into the sandbox -testCert () { - expectation=$1 # "missing" | "present" - mode=$2 # "normal" | "fixed-output" | "clobbering-impurities" - certFile=$3 # a string that can be the path to a cert file - # `100` means build failure without extra info, see doc/manual/src/command-ref/status-build-failure.md - ([ "$mode" == fixed-output ] || [ "$mode" == clobbering-impurities ]) && ret=1 || ret=100 - expectStderr $ret nix-sandbox-build cert-test.nix --argstr mode "$mode" --option ssl-cert-file "$certFile" | - grepQuiet "CERT_${expectation}_IN_SANDBOX" -} -testCertWithoutOption () { - expectation=$1 # "missing" | "present" - mode=$2 # "normal" | "fixed-output" | "clobbering-impurities" - # `100` means build failure without extra info, see doc/manual/src/command-ref/status-build-failure.md - ([ "$mode" == fixed-output ] || [ "$mode" == clobbering-impurities ]) && ret=1 || ret=100 - expectStderr $ret nix-sandbox-build cert-test.nix --argstr mode "$mode" | - grepQuiet "CERT_${expectation}_IN_SANDBOX" -} - -nocert=$TEST_ROOT/no-cert-file.pem -cert=$TEST_ROOT/some-cert-file.pem -certsymlink=$TEST_ROOT/cert-symlink.pem -echo -n "CERT_CONTENT" > $cert -ln -s $cert $certsymlink - -# No cert in sandbox when not a fixed-output derivation -testCert missing normal "$cert" - -# No cert in sandbox when ssl-cert-file is empty -testCert missing fixed-output "" - -# No cert in sandbox when ssl-cert-file is a nonexistent file -testCert missing fixed-output "$nocert" - -# Cert in sandbox when ssl-cert-file is set to an existing file -testCert present fixed-output "$cert" -testCert present-env-var fixed-output "$cert" -NIX_SSL_CERT_FILE="$cert" testCertWithoutOption present-env-var fixed-output - -# Cert in sandbox when ssl-cert-file is set to a symlink -testCert present fixed-output "$certsymlink" -testCert present-env-var fixed-output "$certsymlink" -NIX_SSL_CERT_FILE="$certsymlink" testCertWithoutOption present-env-var fixed-output - -# Cert in sandbox when ssl-cert-file is set to a file and impurities clobbers the environment variables. -testCert present clobbering-impurities "$cert" -testCert present-env-var clobbering-impurities "$cert" -# Set the environment variable to clobber it. -NIX_SSL_CERT_FILE=/nowhere testCert present-env-var clobbering-impurities "$cert" -NIX_SSL_CERT_FILE="$cert" testCertWithoutOption present-env-var clobbering-impurities diff --git a/tests/functional/unsandboxed-certificate-authorities.sh b/tests/functional/unsandboxed-certificate-authorities.sh deleted file mode 100644 index 7a7f291fe..000000000 --- a/tests/functional/unsandboxed-certificate-authorities.sh +++ /dev/null @@ -1,54 +0,0 @@ -# This tests checks that CA works in builder's environments, *without* sandbox. -source common.sh - -needLocalStore "the sandbox only runs on the builder side, so it makes no sense to test it with the daemon" - -clearStore - -## Test mounting of SSL certificates into an unsanboxed builder. -testCert () { - expectation=$1 # "missing" | "present" - mode=$2 # "normal" | "fixed-output" | "clobbering-impurities" - certFile=$3 # a string that can be the path to a cert file - # `100` means build failure without extra info, see doc/manual/src/command-ref/status-build-failure.md - ([ "$mode" == fixed-output ] || [ "$mode" == clobbering-impurities ]) && ret=1 || ret=100 - expectStderr $ret nix-build --no-out-link cert-test.nix --arg sandbox false --argstr mode "$mode" --option ssl-cert-file "$certFile" | - grepQuiet "CERT_${expectation}_IN_SANDBOX" -} -testCertWithoutOption () { - expectation=$1 # "missing" | "present" - mode=$2 # "normal" | "fixed-output" | "clobbering-impurities" - # `100` means build failure without extra info, see doc/manual/src/command-ref/status-build-failure.md - ([ "$mode" == fixed-output ] || [ "$mode" == clobbering-impurities ]) && ret=1 || ret=100 - expectStderr $ret nix-build --no-out-link cert-test.nix --arg sandbox false --argstr mode "$mode" | - grepQuiet "CERT_${expectation}_IN_SANDBOX" -} - -nocert=$TEST_ROOT/no-cert-file.pem -cert=$TEST_ROOT/some-cert-file.pem -certsymlink=$TEST_ROOT/cert-symlink.pem -echo -n "CERT_CONTENT" > $cert -ln -s $cert $certsymlink - -# No cert in sandbox when not a fixed-output derivation -testCert missing normal "$cert" - -# No cert in sandbox when ssl-cert-file is empty -testCert missing fixed-output "" - -# No cert in sandbox when ssl-cert-file is a nonexistent file -testCert missing fixed-output "$nocert" - -# Cert in sandbox when ssl-cert-file is set to an existing file -testCert present-env-var fixed-output "$cert" -NIX_SSL_CERT_FILE="$cert" testCertWithoutOption present-env-var fixed-output - -# Cert in sandbox when ssl-cert-file is set to a symlink -testCert present-env-var fixed-output "$certsymlink" -NIX_SSL_CERT_FILE="$certsymlink" testCertWithoutOption present-env-var fixed-output - -# Cert in sandbox when ssl-cert-file is set to a file and impurities clobbers the environment variables. -testCert present-env-var clobbering-impurities "$cert" -# Set the environment variable to clobber it. -NIX_SSL_CERT_FILE=/nowhere testCert present-env-var clobbering-impurities "$cert" -NIX_SSL_CERT_FILE="$cert" testCertWithoutOption present-env-var clobbering-impurities diff --git a/tests/functional/cert-test.nix b/tests/functional2/build/assets/certificate-test.nix similarity index 100% rename from tests/functional/cert-test.nix rename to tests/functional2/build/assets/certificate-test.nix diff --git a/tests/functional2/build/test_ca.py b/tests/functional2/build/test_ca.py new file mode 100644 index 000000000..fba54ae63 --- /dev/null +++ b/tests/functional2/build/test_ca.py @@ -0,0 +1,137 @@ +import pytest +import re + +from pathlib import Path + +from testlib.fixtures.nix import Nix +from testlib.fixtures.file_helper import with_files +from testlib.utils import get_global_asset +from testlib.fixtures.file_helper import Symlink, File, CopyFile + +from typing import Literal + +from dataclasses import dataclass + + +@dataclass +class CertAssessment: + warnings: list[str] + outcomes: set[Literal["missing", "present", "present-env-var", "corrupted"]] + + +certificate_files = { + "cert": File("CERT_CONTENT"), + "symlinked-cert": Symlink("cert"), + "certificate-test.nix": CopyFile("assets/certificate-test.nix"), + "config.nix": get_global_asset("config.nix"), +} + + +def assess_cert_presence_in_builds( + nix: Nix, + mode: Literal["normal", "fixed-output", "clobbering-impurities"], + *, + cert: Path | str | None = None, + sandboxed: bool = True, +) -> CertAssessment: + """ + Run a build with some special Nix code that makes use of the certificate authority code. + We use the stdout to inspect whether our expectations are met. + We return a list of known metadata about how the build ran. + """ + expected_ret_code = 1 if mode in ("fixed-output", "clobbering-impurities") else 100 + + res = ( + nix.nix_build( + [ + "certificate-test.nix", + "--argstr", + "mode", + mode, + "--arg", + "sandbox", + "true" if sandboxed else "false", + "--no-out-link", + ] + + (["--option", "ssl-cert-file", str(cert)] if cert is not None else []) + ) + .run() + .expect(expected_ret_code) + ) + + contains_clobbering_warning = ( + "warning: 'NIX_SSL_CERT_FILE is an impure environment variable" in res.stderr_plain + ) + + # this should be read as: mode == "clobbering-impurities" implies contains_clobbering_warning + assert not contains_clobbering_warning or mode == "clobbering-impurities" + + outcomes = set(re.findall(r"CERT_(.*?)_IN_SANDBOX", res.stderr_plain)) + warnings = re.findall(r"warning: (.*)", res.stderr_plain) + + return CertAssessment(outcomes=outcomes, warnings=warnings) + + +@with_files(certificate_files) +@pytest.mark.parametrize("sandboxed", [False, pytest.param(True, marks=pytest.mark.full_sandbox)]) +def test_missing_cert_in_ia_builds(nix: Nix, sandboxed: bool): + assert assess_cert_presence_in_builds( + nix, "normal", cert="cert", sandboxed=sandboxed + ).outcomes == {"missing"} + + +@with_files(certificate_files) +@pytest.mark.parametrize("cert_path", ["", "/nowhere"]) +@pytest.mark.parametrize("sandboxed", [False, pytest.param(True, marks=pytest.mark.full_sandbox)]) +def test_missing_cert_in_fod_builds(nix: Nix, sandboxed: bool, cert_path: str): + assert assess_cert_presence_in_builds( + nix, "fixed-output", cert=cert_path, sandboxed=sandboxed + ).outcomes == {"missing"} + + +@with_files(certificate_files) +@pytest.mark.parametrize("cert_path", ["cert", "symlinked-cert"]) +@pytest.mark.parametrize("sandboxed", [False, pytest.param(True, marks=pytest.mark.full_sandbox)]) +def test_presence_cert_in_fod_builds(nix: Nix, cert_path: str, sandboxed: bool): + assert assess_cert_presence_in_builds( + nix, "fixed-output", cert=cert_path, sandboxed=sandboxed + ).outcomes == ({"present", "present-env-var"} if sandboxed else {"present-env-var"}) + + nix.env["NIX_SSL_CERT_FILE"] = str(cert_path) + assert assess_cert_presence_in_builds(nix, "fixed-output", sandboxed=sandboxed).outcomes == ( + {"present", "present-env-var"} if sandboxed else {"present-env-var"} + ) + + +@with_files(certificate_files) +@pytest.mark.parametrize("sandboxed", [False, pytest.param(True, marks=pytest.mark.full_sandbox)]) +class TestCertClobberingInFODs: + def test_no_env(self, nix: Nix, sandboxed: bool): + # there's no NIX_SSL_CERT_FILE presently, so everything works as usually. + assert assess_cert_presence_in_builds( + nix, "clobbering-impurities", cert="cert", sandboxed=sandboxed + ).outcomes == ({"present", "present-env-var"} if sandboxed else {"present-env-var"}) + + def test_with_env_with_flag(self, nix: Nix, sandboxed: bool): + # NIX_SSL_CERT_FILE is set but we pass --ssl-cert-file which takes precedence. + # We expect warnings to occur. + nix.env["NIX_SSL_CERT_FILE"] = "/nowhere" + assert assess_cert_presence_in_builds( + nix, "clobbering-impurities", cert="cert", sandboxed=sandboxed + ).outcomes == ({"present", "present-env-var"} if sandboxed else {"present-env-var"}) + + def test_with_env_no_flag(self, nix: Nix, sandboxed: bool): + # NIX_SSL_CERT_FILE is set but we do not pass any flag. + # We expect no warning. + nix.env["NIX_SSL_CERT_FILE"] = "/nowhere" + + assert assess_cert_presence_in_builds( + nix, "clobbering-impurities", sandboxed=sandboxed + ).outcomes == {"missing"} + + def test_with_valid_env_no_flag(self, nix: Nix, sandboxed: bool): + # We change the environment variable to a valid path. + nix.env["NIX_SSL_CERT_FILE"] = "cert" + assert assess_cert_presence_in_builds( + nix, "clobbering-impurities", sandboxed=sandboxed + ).outcomes == ({"present", "present-env-var"} if sandboxed else {"present-env-var"})