diff --git a/nixos/lib/test-driver/default.nix b/nixos/lib/test-driver/default.nix index 77638397d11c..03cfcb6915b3 100644 --- a/nixos/lib/test-driver/default.nix +++ b/nixos/lib/test-driver/default.nix @@ -18,8 +18,6 @@ netpbm, vhost-device-vsock, nixosTests, - qemu_pkg ? qemu_test, - qemu_test, setuptools, socat, systemd, @@ -56,7 +54,6 @@ buildPythonApplication { propagatedBuildInputs = [ coreutils netpbm - qemu_pkg socat util-linux vde2 diff --git a/nixos/lib/testing/driver.nix b/nixos/lib/testing/driver.nix index 7a4c7e31a0e1..c15578f4fbfc 100644 --- a/nixos/lib/testing/driver.nix +++ b/nixos/lib/testing/driver.nix @@ -20,7 +20,6 @@ let # the respective qemu version and with or without ocr support testDriver = config.pythonTestDriverPackage.override { inherit (config) enableOCR extraPythonPackages; - qemu_pkg = config.qemu.package; enableNspawn = config.containers != { }; }; diff --git a/nixos/tests/boot.nix b/nixos/tests/boot.nix index d1ac1a8769fe..a59f94b94edf 100644 --- a/nixos/tests/boot.nix +++ b/nixos/tests/boot.nix @@ -207,7 +207,7 @@ in import os # Create a mutable linked image backed by the read-only SD image - if os.system("qemu-img create -f qcow2 -F raw -b ${sdImage} ${mutableImage}") != 0: + if os.system("${pkgs.qemu}/bin/qemu-img create -f qcow2 -F raw -b ${sdImage} ${mutableImage}") != 0: raise RuntimeError("Could not create mutable linked image") machine = create_machine("${startCommand}") diff --git a/nixos/tests/common/ec2.nix b/nixos/tests/common/ec2.nix index 864afc63cb8b..f28e094d455b 100644 --- a/nixos/tests/common/ec2.nix +++ b/nixos/tests/common/ec2.nix @@ -51,9 +51,11 @@ in ) os.makedirs(image_dir, mode=0o700, exist_ok=True) disk_image = os.path.join(image_dir, "machine.qcow2") + QEMU_BIN = "${pkgs.qemu}" + QEMU_IMG = f"{QEMU_BIN}/bin/qemu-img" subprocess.check_call( [ - "qemu-img", + QEMU_IMG, "create", "-f", "qcow2", @@ -64,7 +66,7 @@ in disk_image, ] ) - subprocess.check_call(["qemu-img", "resize", disk_image, "10G"]) + subprocess.check_call([QEMU_IMG, "resize", disk_image, "10G"]) # Note: we use net=169.0.0.0/8 rather than # net=169.254.0.0/16 to prevent dhcpcd from getting horribly @@ -74,7 +76,7 @@ in # turn off the DHCP server, but qemu does not have an option # to do that. start_command = ( - "qemu-kvm -m 1024" + f"{QEMU_BIN}/bin/qemu-kvm -m 1024" + " -device virtio-net-pci,netdev=vlan0" + " -netdev 'user,id=vlan0,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${getExe imdsServer} ${metaData}'" + f" -drive file={disk_image},if=virtio,werror=report" diff --git a/nixos/tests/ec2-image.nix b/nixos/tests/ec2-image.nix index b54b153bd52e..df07d14b8ba7 100644 --- a/nixos/tests/ec2-image.nix +++ b/nixos/tests/ec2-image.nix @@ -88,294 +88,298 @@ in }; }; - testScript = '' - import os - import re - import subprocess - import tempfile + testScript = + { nodes, ... }: + '' + import os + import re + import subprocess + import tempfile - # Instance Metadata Service (IMDSv2 with 1.0 metadata version) - # TODO: Use 'latest' metadata version instead of '1.0' - # TODO: [Test matrix] also test providing the host key through IMDS - # - i.e. a test module argument to select between writing or reading the host key - def create_ec2_metadata_dir(temp_dir, client_pubkey): - """Create fake EC2 metadata directory structure with mock data""" - metadata_dir = os.path.join(temp_dir.name, "ec2-metadata") + # Instance Metadata Service (IMDSv2 with 1.0 metadata version) + # TODO: Use 'latest' metadata version instead of '1.0' + # TODO: [Test matrix] also test providing the host key through IMDS + # - i.e. a test module argument to select between writing or reading the host key + def create_ec2_metadata_dir(temp_dir, client_pubkey): + """Create fake EC2 metadata directory structure with mock data""" + metadata_dir = os.path.join(temp_dir.name, "ec2-metadata") - # Create directory structure - os.makedirs(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0"), exist_ok=True) - os.makedirs(os.path.join(metadata_dir, "latest", "api"), exist_ok=True) + # Create directory structure + os.makedirs(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0"), exist_ok=True) + os.makedirs(os.path.join(metadata_dir, "latest", "api"), exist_ok=True) - # Metadata version 1.0 endpoints (what fetch-ec2-metadata.sh actually fetches) - with open(os.path.join(metadata_dir, "1.0", "meta-data", "hostname"), "w") as f: - f.write("test-instance") - with open(os.path.join(metadata_dir, "1.0", "meta-data", "ami-manifest-path"), "w") as f: - f.write("(test)") - with open(os.path.join(metadata_dir, "1.0", "meta-data", "instance-id"), "w") as f: - f.write("i-1234567890abcdef0") - with open(os.path.join(metadata_dir, "1.0", "user-data"), "w") as f: - f.write("") - with open(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0", "openssh-key"), "w") as f: - f.write(client_pubkey) + # Metadata version 1.0 endpoints (what fetch-ec2-metadata.sh actually fetches) + with open(os.path.join(metadata_dir, "1.0", "meta-data", "hostname"), "w") as f: + f.write("test-instance") + with open(os.path.join(metadata_dir, "1.0", "meta-data", "ami-manifest-path"), "w") as f: + f.write("(test)") + with open(os.path.join(metadata_dir, "1.0", "meta-data", "instance-id"), "w") as f: + f.write("i-1234567890abcdef0") + with open(os.path.join(metadata_dir, "1.0", "user-data"), "w") as f: + f.write("") + with open(os.path.join(metadata_dir, "1.0", "meta-data", "public-keys", "0", "openssh-key"), "w") as f: + f.write(client_pubkey) - # IMDSv2 token endpoint - return a fake token - with open(os.path.join(metadata_dir, "latest", "api", "token"), "w") as f: - f.write("test-token-12345") + # IMDSv2 token endpoint - return a fake token + with open(os.path.join(metadata_dir, "latest", "api", "token"), "w") as f: + f.write("test-token-12345") - return metadata_dir + return metadata_dir - def generate_client_ssh_key(): - """Generate SSH key pair on VM host for client authentication""" - # Use temporary directory for key generation - import tempfile - with tempfile.TemporaryDirectory() as key_dir: - private_key = os.path.join(key_dir, "id_ed25519") - public_key = os.path.join(key_dir, "id_ed25519.pub") + def generate_client_ssh_key(): + """Generate SSH key pair on VM host for client authentication""" + # Use temporary directory for key generation + import tempfile + with tempfile.TemporaryDirectory() as key_dir: + private_key = os.path.join(key_dir, "id_ed25519") + public_key = os.path.join(key_dir, "id_ed25519.pub") - # Generate key pair using host SSH tools - ret = os.system(f"${hostPkgs.openssh}/bin/ssh-keygen -t ed25519 -f {private_key} -N \"\"") - if ret != 0: - raise Exception("Failed to generate SSH key pair") + # Generate key pair using host SSH tools + ret = os.system(f"${hostPkgs.openssh}/bin/ssh-keygen -t ed25519 -f {private_key} -N \"\"") + if ret != 0: + raise Exception("Failed to generate SSH key pair") - # Read the generated public key - with open(public_key, "r") as f: - client_pubkey = f.read().strip() + # Read the generated public key + with open(public_key, "r") as f: + client_pubkey = f.read().strip() - # Read the private key - with open(private_key, "r") as f: - client_private_key = f.read() + # Read the private key + with open(private_key, "r") as f: + client_private_key = f.read() - return client_pubkey, client_private_key + return client_pubkey, client_private_key - def setup_client_ssh_key(client, client_private_key): - """Install the pre-generated SSH private key on client""" - client.succeed("mkdir -p /root/.ssh") - client.succeed(f"cat > /root/.ssh/id_ed25519 << 'EOF'\n{client_private_key}\nEOF") - client.succeed("chmod 600 /root/.ssh/id_ed25519") + def setup_client_ssh_key(client, client_private_key): + """Install the pre-generated SSH private key on client""" + client.succeed("mkdir -p /root/.ssh") + client.succeed(f"cat > /root/.ssh/id_ed25519 << 'EOF'\n{client_private_key}\nEOF") + client.succeed("chmod 600 /root/.ssh/id_ed25519") - def setup_machine(temp_dir, client_pubkey): - """Initialize EC2 machine with disk image, metadata server, and networking""" - # Set up disk image - image_dir = os.path.join( - os.environ.get("TMPDIR", tempfile.gettempdir()), "tmp", "vm-state-machine" - ) - os.makedirs(image_dir, mode=0o700, exist_ok=True) - disk_image = os.path.join(image_dir, "machine.qcow2") - subprocess.check_call([ - "qemu-img", "create", "-f", "qcow2", "-F", "qcow2", - "-o", "backing_file=${image}", disk_image - ]) - subprocess.check_call(["qemu-img", "resize", disk_image, "10G"]) + def setup_machine(temp_dir, client_pubkey): + """Initialize EC2 machine with disk image, metadata server, and networking""" + # Set up disk image + image_dir = os.path.join( + os.environ.get("TMPDIR", tempfile.gettempdir()), "tmp", "vm-state-machine" + ) + os.makedirs(image_dir, mode=0o700, exist_ok=True) + disk_image = os.path.join(image_dir, "machine.qcow2") + QEMU = "${nodes.machine.virtualisation.qemu.package}" + QEMU_IMG = f"{QEMU}/bin/qemu-img" + subprocess.check_call([ + QEMU_IMG, "create", "-f", "qcow2", "-F", "qcow2", + "-o", "backing_file=${image}", disk_image + ]) + subprocess.check_call([QEMU_IMG, "resize", disk_image, "10G"]) - # Create fake EC2 metadata in temporary directory with client's public key - metadata_dir = create_ec2_metadata_dir(temp_dir, client_pubkey) + # Create fake EC2 metadata in temporary directory with client's public key + metadata_dir = create_ec2_metadata_dir(temp_dir, client_pubkey) - # Add both VLAN networking (matching test framework) and EC2 metadata server - vlan_net = ( - " -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:01:02" - + ' -netdev vde,id=vlan1,sock="$QEMU_VDE_SOCKET_1"' - ) - metadata_net = ( - " -device virtio-net-pci,netdev=ec2meta" - + f" -netdev 'user,id=ec2meta,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${lib.getExe imdsServer} {metadata_dir}'" - ) + # Add both VLAN networking (matching test framework) and EC2 metadata server + vlan_net = ( + " -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:01:02" + + ' -netdev vde,id=vlan1,sock="$QEMU_VDE_SOCKET_1"' + ) + metadata_net = ( + " -device virtio-net-pci,netdev=ec2meta" + + f" -netdev 'user,id=ec2meta,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${lib.getExe imdsServer} {metadata_dir}'" + ) - start_command = ( - "qemu-kvm -m 1024" - + f" -drive file={disk_image},if=virtio,werror=report" - + vlan_net - + metadata_net - + " $QEMU_OPTS" - ) + start_command = ( + f"{QEMU}/bin/qemu-kvm -m 1024" + + f" -drive file={disk_image},if=virtio,werror=report" + + vlan_net + + metadata_net + + " $QEMU_OPTS" + ) - return create_machine(start_command), metadata_dir + return create_machine(start_command), metadata_dir - def test_userdata_decompression(machine, user_data_path, compressed_data, format_name): - """Test that compressed user-data is decompressed by fetch-ec2-metadata""" - test_marker = f"{format_name}-decompression-test" - with open(user_data_path, "wb") as f: - f.write(compressed_data) - machine.succeed("systemctl reset-failed fetch-ec2-metadata; systemctl restart fetch-ec2-metadata") - result = machine.succeed("cat /etc/ec2-metadata/user-data") - assert test_marker in result, f"Expected '{test_marker}' in decompressed {format_name} content, got: {result}" - journal = machine.succeed("journalctl -u fetch-ec2-metadata --no-pager -b") - assert "decompressing:" in journal, f"Expected decompression log in journal for {format_name}" + def test_userdata_decompression(machine, user_data_path, compressed_data, format_name): + """Test that compressed user-data is decompressed by fetch-ec2-metadata""" + test_marker = f"{format_name}-decompression-test" + with open(user_data_path, "wb") as f: + f.write(compressed_data) + machine.succeed("systemctl reset-failed fetch-ec2-metadata; systemctl restart fetch-ec2-metadata") + result = machine.succeed("cat /etc/ec2-metadata/user-data") + assert test_marker in result, f"Expected '{test_marker}' in decompressed {format_name} content, got: {result}" + journal = machine.succeed("journalctl -u fetch-ec2-metadata --no-pager -b") + assert "decompressing:" in journal, f"Expected decompression log in journal for {format_name}" - # Create temporary directory for metadata (scoped for cleanup) - temp_dir = tempfile.TemporaryDirectory() + # Create temporary directory for metadata (scoped for cleanup) + temp_dir = tempfile.TemporaryDirectory() - # Start client first (but don't wait for it to boot) - client.start() + # Start client first (but don't wait for it to boot) + client.start() - # Generate SSH key pair on VM host before starting machine - client_pubkey, client_private_key = generate_client_ssh_key() + # Generate SSH key pair on VM host before starting machine + client_pubkey, client_private_key = generate_client_ssh_key() - # Set up machine with client's public key in metadata service - machine, metadata_dir = setup_machine(temp_dir, client_pubkey) - user_data_path = os.path.join(metadata_dir, "1.0", "user-data") + # Set up machine with client's public key in metadata service + machine, metadata_dir = setup_machine(temp_dir, client_pubkey) + user_data_path = os.path.join(metadata_dir, "1.0", "user-data") - try: - machine.start() + try: + machine.start() - # Wait for services to be ready - machine.wait_for_unit("sshd.service") - machine.wait_for_unit("print-host-key.service") - machine.wait_for_unit("apply-ec2-data.service") + # Wait for services to be ready + machine.wait_for_unit("sshd.service") + machine.wait_for_unit("print-host-key.service") + machine.wait_for_unit("apply-ec2-data.service") - # Extract shared variables outside subtests - machine_ip = "${config.nodes.machine.networking.primaryIPAddress}" + # Extract shared variables outside subtests + machine_ip = "${config.nodes.machine.networking.primaryIPAddress}" - with subtest("EC2 metadata service connectivity"): - # Obtain an IMDSv2 token, then use it to fetch metadata - imds_token = machine.succeed( - "curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'" - " http://169.254.169.254/latest/api/token" - ).strip() - assert imds_token, "Failed to obtain IMDSv2 token" - hostname_response = machine.succeed( - f"curl -sf -H 'X-aws-ec2-metadata-token: {imds_token}'" - " http://169.254.169.254/1.0/meta-data/hostname" - ) - assert "test-instance" in hostname_response, f"Expected 'test-instance', got: {hostname_response}" + with subtest("EC2 metadata service connectivity"): + # Obtain an IMDSv2 token, then use it to fetch metadata + imds_token = machine.succeed( + "curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'" + " http://169.254.169.254/latest/api/token" + ).strip() + assert imds_token, "Failed to obtain IMDSv2 token" + hostname_response = machine.succeed( + f"curl -sf -H 'X-aws-ec2-metadata-token: {imds_token}'" + " http://169.254.169.254/1.0/meta-data/hostname" + ) + assert "test-instance" in hostname_response, f"Expected 'test-instance', got: {hostname_response}" - with subtest("SSH host key extraction from console"): - console_log = machine.get_console_log() - assert "-----BEGIN SSH HOST KEY FINGERPRINTS-----" in console_log - assert "-----END SSH HOST KEY FINGERPRINTS-----" in console_log - assert "-----BEGIN SSH HOST KEY KEYS-----" in console_log - assert "-----END SSH HOST KEY KEYS-----" in console_log + with subtest("SSH host key extraction from console"): + console_log = machine.get_console_log() + assert "-----BEGIN SSH HOST KEY FINGERPRINTS-----" in console_log + assert "-----END SSH HOST KEY FINGERPRINTS-----" in console_log + assert "-----BEGIN SSH HOST KEY KEYS-----" in console_log + assert "-----END SSH HOST KEY KEYS-----" in console_log - keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----" - keys_match = re.search(keys_pattern, console_log, re.DOTALL) - assert keys_match, "Could not find SSH host keys section" - keys_content = keys_match.group(1).strip() - assert "ssh-" in keys_content, "SSH keys should contain ssh- prefix" + keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----" + keys_match = re.search(keys_pattern, console_log, re.DOTALL) + assert keys_match, "Could not find SSH host keys section" + keys_content = keys_match.group(1).strip() + assert "ssh-" in keys_content, "SSH keys should contain ssh- prefix" - with subtest("Network connectivity"): - client.succeed(f"ping -c 1 {machine_ip}") + with subtest("Network connectivity"): + client.succeed(f"ping -c 1 {machine_ip}") - with subtest("SSH connectivity with strict host key checking"): - # Install the pre-generated private key on client - setup_client_ssh_key(client, client_private_key) + with subtest("SSH connectivity with strict host key checking"): + # Install the pre-generated private key on client + setup_client_ssh_key(client, client_private_key) - # Get console log and extract host keys - console_log = machine.get_console_log() - keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----" - keys_match = re.search(keys_pattern, console_log, re.DOTALL) - assert keys_match, "Could not find SSH host keys section" + # Get console log and extract host keys + console_log = machine.get_console_log() + keys_pattern = r"-----BEGIN SSH HOST KEY KEYS-----(.*?)-----END SSH HOST KEY KEYS-----" + keys_match = re.search(keys_pattern, console_log, re.DOTALL) + assert keys_match, "Could not find SSH host keys section" - # Create known_hosts file from console-extracted host keys - keys_content = keys_match.group(1).strip() - known_hosts_entries = [] - for line in keys_content.split('\n'): - if line.strip() and line.startswith('ssh-'): - known_hosts_entries.append(f"{machine_ip} {line.strip()}") + # Create known_hosts file from console-extracted host keys + keys_content = keys_match.group(1).strip() + known_hosts_entries = [] + for line in keys_content.split('\n'): + if line.strip() and line.startswith('ssh-'): + known_hosts_entries.append(f"{machine_ip} {line.strip()}") - assert known_hosts_entries, "No SSH host keys found for known_hosts generation" + assert known_hosts_entries, "No SSH host keys found for known_hosts generation" - known_hosts_content = '\n'.join(known_hosts_entries) - client.succeed(f"cat > /root/.ssh/known_hosts << 'EOF'\n{known_hosts_content}\nEOF") + known_hosts_content = '\n'.join(known_hosts_entries) + client.succeed(f"cat > /root/.ssh/known_hosts << 'EOF'\n{known_hosts_content}\nEOF") - # Test SSH connectivity with strict host key checking - ssh_result = client.succeed(f"ssh -o ConnectTimeout=60 -o BatchMode=yes -i /root/.ssh/id_ed25519 root@{machine_ip} 'echo Hello from $(hostname)'") - assert "Hello from test-instance" in ssh_result, f"Unexpected SSH result: {ssh_result}" + # Test SSH connectivity with strict host key checking + ssh_result = client.succeed(f"ssh -o ConnectTimeout=60 -o BatchMode=yes -i /root/.ssh/id_ed25519 root@{machine_ip} 'echo Hello from $(hostname)'") + assert "Hello from test-instance" in ssh_result, f"Unexpected SSH result: {ssh_result}" - with subtest("Basic EC2 functionality"): - machine.succeed("findmnt / -o SIZE -n | grep -E '[0-9]+G'") + with subtest("Basic EC2 functionality"): + machine.succeed("findmnt / -o SIZE -n | grep -E '[0-9]+G'") - with subtest("Decompression of gzip-compressed user-data"): - import gzip as gzip_mod - test_data = b"#!/bin/bash\necho gzip-decompression-test\n" - test_userdata_decompression(machine, user_data_path, gzip_mod.compress(test_data), "gzip") + with subtest("Decompression of gzip-compressed user-data"): + import gzip as gzip_mod + test_data = b"#!/bin/bash\necho gzip-decompression-test\n" + test_userdata_decompression(machine, user_data_path, gzip_mod.compress(test_data), "gzip") - with subtest("Decompression of bzip2-compressed user-data"): - import bz2 - test_data = b"#!/bin/bash\necho bzip2-decompression-test\n" - test_userdata_decompression(machine, user_data_path, bz2.compress(test_data), "bzip2") + with subtest("Decompression of bzip2-compressed user-data"): + import bz2 + test_data = b"#!/bin/bash\necho bzip2-decompression-test\n" + test_userdata_decompression(machine, user_data_path, bz2.compress(test_data), "bzip2") - with subtest("Decompression of xz-compressed user-data"): - import lzma - test_data = b"#!/bin/bash\necho xz-decompression-test\n" - test_userdata_decompression(machine, user_data_path, lzma.compress(test_data), "xz") + with subtest("Decompression of xz-compressed user-data"): + import lzma + test_data = b"#!/bin/bash\necho xz-decompression-test\n" + test_userdata_decompression(machine, user_data_path, lzma.compress(test_data), "xz") - with subtest("Decompression of zstd-compressed user-data"): - test_data = b"#!/bin/bash\necho zstd-decompression-test\n" - proc = subprocess.run( - ["${hostPkgs.zstd}/bin/zstd", "-c"], - input=test_data, capture_output=True, check=True, - ) - test_userdata_decompression(machine, user_data_path, proc.stdout, "zstd") + with subtest("Decompression of zstd-compressed user-data"): + test_data = b"#!/bin/bash\necho zstd-decompression-test\n" + proc = subprocess.run( + ["${hostPkgs.zstd}/bin/zstd", "-c"], + input=test_data, capture_output=True, check=True, + ) + test_userdata_decompression(machine, user_data_path, proc.stdout, "zstd") - with subtest("Decompression of lzip-compressed user-data"): - test_data = b"#!/bin/bash\necho lzip-decompression-test\n" - proc = subprocess.run( - ["${hostPkgs.lzip}/bin/lzip", "-c"], - input=test_data, capture_output=True, check=True, - ) - test_userdata_decompression(machine, user_data_path, proc.stdout, "lzip") + with subtest("Decompression of lzip-compressed user-data"): + test_data = b"#!/bin/bash\necho lzip-decompression-test\n" + proc = subprocess.run( + ["${hostPkgs.lzip}/bin/lzip", "-c"], + input=test_data, capture_output=True, check=True, + ) + test_userdata_decompression(machine, user_data_path, proc.stdout, "lzip") - with subtest("IPv6 IMDS fallback"): - # Save hostname fetched via IPv4 for later comparison - original_hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip() + with subtest("IPv6 IMDS fallback"): + # Save hostname fetched via IPv4 for later comparison + original_hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip() - # Assign the EC2 IPv6 IMDS address to loopback - machine.succeed("ip -6 addr add fd00:ec2::254/128 dev lo") + # Assign the EC2 IPv6 IMDS address to loopback + machine.succeed("ip -6 addr add fd00:ec2::254/128 dev lo") - # Create metadata directory structure for the IPv6 endpoint - machine.succeed( - "mkdir -p /tmp/ipv6-metadata/1.0/meta-data/public-keys/0" - " && mkdir -p /tmp/ipv6-metadata/latest/api" - " && cp /etc/ec2-metadata/hostname /tmp/ipv6-metadata/1.0/meta-data/hostname" - " && cp /etc/ec2-metadata/ami-manifest-path /tmp/ipv6-metadata/1.0/meta-data/ami-manifest-path" - " && echo i-1234567890abcdef0 > /tmp/ipv6-metadata/1.0/meta-data/instance-id" - " && echo ipv6-test-token > /tmp/ipv6-metadata/latest/api/token" - " && touch /tmp/ipv6-metadata/1.0/user-data" - ) - machine.execute( - "test -f /etc/ec2-metadata/public-keys-0-openssh-key" - " && cp /etc/ec2-metadata/public-keys-0-openssh-key" - " /tmp/ipv6-metadata/1.0/meta-data/public-keys/0/openssh-key" - ) + # Create metadata directory structure for the IPv6 endpoint + machine.succeed( + "mkdir -p /tmp/ipv6-metadata/1.0/meta-data/public-keys/0" + " && mkdir -p /tmp/ipv6-metadata/latest/api" + " && cp /etc/ec2-metadata/hostname /tmp/ipv6-metadata/1.0/meta-data/hostname" + " && cp /etc/ec2-metadata/ami-manifest-path /tmp/ipv6-metadata/1.0/meta-data/ami-manifest-path" + " && echo i-1234567890abcdef0 > /tmp/ipv6-metadata/1.0/meta-data/instance-id" + " && echo ipv6-test-token > /tmp/ipv6-metadata/latest/api/token" + " && touch /tmp/ipv6-metadata/1.0/user-data" + ) + machine.execute( + "test -f /etc/ec2-metadata/public-keys-0-openssh-key" + " && cp /etc/ec2-metadata/public-keys-0-openssh-key" + " /tmp/ipv6-metadata/1.0/meta-data/public-keys/0/openssh-key" + ) - # Serve metadata on the IPv6 IMDS address via socat + imds-server (inetd-style) - machine.succeed( - "systemd-run --unit=ipv6-imds --" - " socat TCP6-LISTEN:80,bind=[fd00:ec2::254],fork,reuseaddr" - " SYSTEM:'${lib.getExe imdsServer} /tmp/ipv6-metadata'" - ) + # Serve metadata on the IPv6 IMDS address via socat + imds-server (inetd-style) + machine.succeed( + "systemd-run --unit=ipv6-imds --" + " socat TCP6-LISTEN:80,bind=[fd00:ec2::254],fork,reuseaddr" + " SYSTEM:'${lib.getExe imdsServer} /tmp/ipv6-metadata'" + ) - # Wait for IPv6 IMDS to become reachable (token endpoint doesn't require auth) - machine.wait_until_succeeds( - "curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'" - " http://[fd00:ec2::254]/latest/api/token" - ) + # Wait for IPv6 IMDS to become reachable (token endpoint doesn't require auth) + machine.wait_until_succeeds( + "curl -sf -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 600'" + " http://[fd00:ec2::254]/latest/api/token" + ) - # Block IPv4 IMDS to force fallback to IPv6 - machine.succeed( - "iptables -I OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT" - ) + # Block IPv4 IMDS to force fallback to IPv6 + machine.succeed( + "iptables -I OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT" + ) - # Verify IPv4 IMDS is now unreachable - machine.fail( - "curl -sf --connect-timeout 2 http://169.254.169.254/1.0/meta-data/hostname" - ) + # Verify IPv4 IMDS is now unreachable + machine.fail( + "curl -sf --connect-timeout 2 http://169.254.169.254/1.0/meta-data/hostname" + ) - # Clear fetched metadata and re-run the fetcher - machine.succeed("rm -f /etc/ec2-metadata/*") - machine.succeed("systemctl restart fetch-ec2-metadata") + # Clear fetched metadata and re-run the fetcher + machine.succeed("rm -f /etc/ec2-metadata/*") + machine.succeed("systemctl restart fetch-ec2-metadata") - # Verify metadata was successfully re-fetched via IPv6 - hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip() - assert hostname == original_hostname, f"Expected '{original_hostname}', got '{hostname}'" + # Verify metadata was successfully re-fetched via IPv6 + hostname = machine.succeed("cat /etc/ec2-metadata/hostname").strip() + assert hostname == original_hostname, f"Expected '{original_hostname}', got '{hostname}'" - # Clean up: restore IPv4 IMDS access - machine.succeed( - "iptables -D OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT" - ) - machine.succeed("systemctl stop ipv6-imds") + # Clean up: restore IPv4 IMDS access + machine.succeed( + "iptables -D OUTPUT -d 169.254.169.254 -p tcp --dport 80 -j REJECT" + ) + machine.succeed("systemctl stop ipv6-imds") - finally: - machine.shutdown() - temp_dir.cleanup() - ''; + finally: + machine.shutdown() + temp_dir.cleanup() + ''; } diff --git a/nixos/tests/systemd-initrd-simple.nix b/nixos/tests/systemd-initrd-simple.nix index 191a23abb2e4..b02616e1b772 100644 --- a/nixos/tests/systemd-initrd-simple.nix +++ b/nixos/tests/systemd-initrd-simple.nix @@ -1,15 +1,15 @@ +{ pkgs, ... }: { name = "systemd-initrd-simple"; - nodes.machine = - { pkgs, ... }: - { - testing.initrdBackdoor = true; - boot.initrd.systemd.enable = true; - virtualisation.fileSystems."/".autoResize = true; - }; + nodes.machine = { + testing.initrdBackdoor = true; + boot.initrd.systemd.enable = true; + virtualisation.fileSystems."/".autoResize = true; + }; testScript = + { nodes, ... }: # python '' import subprocess @@ -43,7 +43,7 @@ oldAvail = machine.succeed("df --output=avail / | sed 1d") machine.shutdown() - subprocess.check_call(["qemu-img", "resize", "vm-state-machine/machine.qcow2", "+1G"]) + subprocess.check_call(["${nodes.machine.virtualisation.qemu.package}/bin/qemu-img", "resize", "vm-state-machine/machine.qcow2", "+1G"]) machine.start() machine.switch_root() diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix index 8cca68465802..b04b288e8939 100644 --- a/nixos/tests/systemd.nix +++ b/nixos/tests/systemd.nix @@ -38,21 +38,23 @@ services.journald.extraConfig = "Storage=volatile"; test-support.displayManager.auto.user = "alice"; - systemd.shutdown.test = pkgs.writeScript "test.shutdown" '' - #!${pkgs.runtimeShell} - PATH=${ - lib.makeBinPath ( - with pkgs; - [ - util-linux - coreutils - ] - ) - } - mount -t 9p shared -o trans=virtio,version=9p2000.L /tmp/shared - touch /tmp/shared/shutdown-test - umount /tmp/shared - ''; + systemd.shutdownRamfs.contents."/etc/systemd/system-shutdown/test".source = + pkgs.writeShellScript "test.shutdown" '' + PATH=${ + lib.makeBinPath ( + with pkgs; + [ + util-linux + coreutils + ] + ) + } + mkdir -p /tmp/shared + mount -t 9p shared -o trans=virtio,version=9p2000.L /tmp/shared + touch /tmp/shared/shutdown-test + umount /tmp/shared + ''; + systemd.shutdownRamfs.storePaths = [ "${pkgs.util-linux}/bin" ]; systemd.services.oncalendar-test = { description = "calendar test"; @@ -147,7 +149,7 @@ subprocess.check_call( [ - "qemu-img", + "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img", "convert", "-O", "raw", diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index faeef8563850..0889cc3093b5 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -196,7 +196,6 @@ with pkgs; inherit (nix-update) nix-update-script; nixos-test-driver = pkgs.python3Packages.callPackage ../../nixos/lib/test-driver { - qemu_pkg = pkgs.qemu; imagemagick_light = pkgs.imagemagick_light.override { inherit (pkgs) libtiff; }; tesseract4 = pkgs.tesseract4.override { enableLanguages = [ "eng" ]; }; # We want `pkgs.systemd`, *not* `python3Packages.system`.