container-test-driver: fixup /etc/passwd for unprivileged user
By default /etc/passwd in container build sandboxes have two users (root,nixbld) mapped to root. This confuses nix especially it behaves different if it runs as root. setuid/setgid() is not enough because ssh will break if the current uid does not exist in /etc/passwd. Along with this we now also only run the setup for setting up the network bridge and cgroup filesystems once and not per container.
This commit is contained in:
@@ -29,18 +29,10 @@ nixosLib.runTest (
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
import subprocess
|
||||
from nixos_test_lib.nix_setup import setup_nix_in_nix # type: ignore[import-untyped]
|
||||
setup_nix_in_nix(None) # No closure info for this test
|
||||
|
||||
def run_clan(cmd: list[str], **kwargs) -> str:
|
||||
import subprocess
|
||||
clan = "${clan-core.packages.${hostPkgs.system}.clan-cli}/bin/clan"
|
||||
clan_args = ["--flake", "${config.clan.test.flakeForSandbox}"]
|
||||
return subprocess.run(
|
||||
["${hostPkgs.util-linux}/bin/unshare", "--user", "--map-user", "1000", "--map-group", "1000", clan, *cmd, *clan_args],
|
||||
**kwargs,
|
||||
check=True,
|
||||
).stdout
|
||||
setup_nix_in_nix(None) # No closure info for this test
|
||||
|
||||
start_all()
|
||||
admin1.wait_for_unit("multi-user.target")
|
||||
@@ -60,7 +52,13 @@ nixosLib.runTest (
|
||||
# Check that the file is in the '0644' mode
|
||||
assert "-rw-r--r--" in ls_out, f"File is not in the '0644' mode: {ls_out}"
|
||||
|
||||
run_clan(["machines", "list"])
|
||||
# Run clan command
|
||||
result = subprocess.run(
|
||||
["${
|
||||
clan-core.packages.${hostPkgs.system}.clan-cli
|
||||
}/bin/clan", "machines", "list", "--flake", "${config.clan.test.flakeForSandbox}"],
|
||||
check=True
|
||||
)
|
||||
'';
|
||||
}
|
||||
)
|
||||
|
||||
@@ -13,37 +13,78 @@ from contextlib import _GeneratorContextManager
|
||||
from dataclasses import dataclass
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from tempfile import NamedTemporaryFile, TemporaryDirectory
|
||||
from typing import Any
|
||||
|
||||
from colorama import Fore, Style
|
||||
|
||||
from .logger import AbstractLogger, CompositeLogger, TerminalLogger
|
||||
|
||||
# Global flag to track if bridge has been created
|
||||
_bridge_created = False
|
||||
# Global flag to track if test environment has been initialized
|
||||
_test_env_initialized = False
|
||||
|
||||
|
||||
def ensure_bridge_exists() -> None:
|
||||
"""Ensure the br0 bridge exists, creating it if necessary."""
|
||||
global _bridge_created
|
||||
if _bridge_created:
|
||||
def init_test_environment() -> None:
|
||||
"""Set up the test environment (network bridge, /etc/passwd) once."""
|
||||
global _test_env_initialized
|
||||
if _test_env_initialized:
|
||||
return
|
||||
|
||||
# Check if bridge already exists
|
||||
bridge_check = subprocess.run(
|
||||
["ip", "link", "show", "br0"], capture_output=True, text=True
|
||||
)
|
||||
if bridge_check.returncode == 0:
|
||||
_bridge_created = True
|
||||
return
|
||||
|
||||
# Create bridge
|
||||
# Set up network bridge
|
||||
subprocess.run(
|
||||
["ip", "link", "add", "br0", "type", "bridge"], check=True, text=True
|
||||
)
|
||||
subprocess.run(["ip", "link", "set", "br0", "up"], check=True, text=True)
|
||||
_bridge_created = True
|
||||
subprocess.run(
|
||||
["ip", "addr", "add", "192.168.1.254/24", "dev", "br0"], check=True, text=True
|
||||
)
|
||||
|
||||
# Set up minimal passwd file for unprivileged operations
|
||||
# Using Nix's convention: UID 1000 for nixbld user, GID 100 for nixbld group
|
||||
passwd_content = """root:x:0:0:Root:/root:/bin/sh
|
||||
nixbld:x:1000:100:Nix build user:/tmp:/bin/sh
|
||||
nobody:x:65534:65534:Nobody:/:/bin/sh
|
||||
"""
|
||||
|
||||
with NamedTemporaryFile(mode="w", delete=False, prefix="test-passwd-") as f:
|
||||
f.write(passwd_content)
|
||||
passwd_path = f.name
|
||||
|
||||
# Set up minimal group file
|
||||
group_content = """root:x:0:
|
||||
nixbld:x:100:nixbld
|
||||
nogroup:x:65534:
|
||||
"""
|
||||
|
||||
with NamedTemporaryFile(mode="w", delete=False, prefix="test-group-") as f:
|
||||
f.write(group_content)
|
||||
group_path = f.name
|
||||
|
||||
# Bind mount our passwd over the system's /etc/passwd
|
||||
result = libc.mount(
|
||||
ctypes.c_char_p(passwd_path.encode()),
|
||||
ctypes.c_char_p(b"/etc/passwd"),
|
||||
ctypes.c_char_p(b"none"),
|
||||
ctypes.c_ulong(MS_BIND),
|
||||
None,
|
||||
)
|
||||
if result != 0:
|
||||
errno = ctypes.get_errno()
|
||||
raise OSError(errno, os.strerror(errno), "Failed to mount passwd")
|
||||
|
||||
# Bind mount our group over the system's /etc/group
|
||||
result = libc.mount(
|
||||
ctypes.c_char_p(group_path.encode()),
|
||||
ctypes.c_char_p(b"/etc/group"),
|
||||
ctypes.c_char_p(b"none"),
|
||||
ctypes.c_ulong(MS_BIND),
|
||||
None,
|
||||
)
|
||||
if result != 0:
|
||||
errno = ctypes.get_errno()
|
||||
raise OSError(errno, os.strerror(errno), "Failed to mount group")
|
||||
|
||||
_test_env_initialized = True
|
||||
|
||||
|
||||
# Load the C library
|
||||
@@ -149,7 +190,7 @@ class Machine:
|
||||
|
||||
def start(self) -> None:
|
||||
prepare_machine_root(self.name, self.rootdir)
|
||||
ensure_bridge_exists()
|
||||
init_test_environment()
|
||||
cmd = [
|
||||
"systemd-nspawn",
|
||||
"--keep-unit",
|
||||
@@ -447,6 +488,7 @@ def setup_filesystems(container: ContainerInfo) -> None:
|
||||
Path("/etc/os-release").touch()
|
||||
Path("/etc/machine-id").write_text("a5ea3f98dedc0278b6f3cc8c37eeaeac")
|
||||
container.nix_store_dir.mkdir(parents=True)
|
||||
container.nix_store_dir.chmod(0o755)
|
||||
|
||||
# Recreate symlinks
|
||||
for file in Path("/nix/store").iterdir():
|
||||
@@ -519,8 +561,8 @@ class Driver:
|
||||
)
|
||||
|
||||
def start_all(self) -> None:
|
||||
# Ensure bridge exists
|
||||
ensure_bridge_exists()
|
||||
# Ensure test environment is set up
|
||||
init_test_environment()
|
||||
|
||||
for machine in self.machines:
|
||||
print(f"Starting {machine.name}")
|
||||
|
||||
Reference in New Issue
Block a user