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"})