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 <raito@lix.systems>
This commit is contained in:
Raito Bezarius
2026-02-26 15:33:22 +00:00
parent dde7bddbd4
commit 0741e24266
5 changed files with 137 additions and 128 deletions
-2
View File
@@ -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',
@@ -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
@@ -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
+137
View File
@@ -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"})