diff options
Diffstat (limited to 'nixos/tests')
30 files changed, 1138 insertions, 381 deletions
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 7b1159d667151..ecf95c2cba0a4 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -92,6 +92,7 @@ in { bcachefs = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bcachefs.nix {}; beanstalkd = handleTest ./beanstalkd.nix {}; bees = handleTest ./bees.nix {}; + binary-cache = handleTest ./binary-cache.nix {}; bind = handleTest ./bind.nix {}; bird = handleTest ./bird.nix {}; bitcoind = handleTest ./bitcoind.nix {}; @@ -134,6 +135,7 @@ in { cloud-init-hostname = handleTest ./cloud-init-hostname.nix {}; cloudlog = handleTest ./cloudlog.nix {}; cntr = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cntr.nix {}; + cockpit = handleTest ./cockpit.nix {}; cockroachdb = handleTestOn ["x86_64-linux"] ./cockroachdb.nix {}; collectd = handleTest ./collectd.nix {}; connman = handleTest ./connman.nix {}; @@ -247,6 +249,7 @@ in { gnome = handleTest ./gnome.nix {}; gnome-flashback = handleTest ./gnome-flashback.nix {}; gnome-xorg = handleTest ./gnome-xorg.nix {}; + gnupg = handleTest ./gnupg.nix {}; go-neb = handleTest ./go-neb.nix {}; gobgpd = handleTest ./gobgpd.nix {}; gocd-agent = handleTest ./gocd-agent.nix {}; @@ -365,6 +368,7 @@ in { login = handleTest ./login.nix {}; logrotate = handleTest ./logrotate.nix {}; loki = handleTest ./loki.nix {}; + luks = handleTest ./luks.nix {}; lvm2 = handleTest ./lvm2 {}; lxd = handleTest ./lxd.nix {}; lxd-nftables = handleTest ./lxd-nftables.nix {}; @@ -487,6 +491,7 @@ in { ombi = handleTest ./ombi.nix {}; openarena = handleTest ./openarena.nix {}; openldap = handleTest ./openldap.nix {}; + opensearch = discoverTests (import ./opensearch.nix); openresty-lua = handleTest ./openresty-lua.nix {}; opensmtpd = handleTest ./opensmtpd.nix {}; opensmtpd-rspamd = handleTest ./opensmtpd-rspamd.nix {}; @@ -518,7 +523,6 @@ in { peering-manager = handleTest ./web-apps/peering-manager.nix {}; peertube = handleTestOn ["x86_64-linux"] ./web-apps/peertube.nix {}; pgadmin4 = handleTest ./pgadmin4.nix {}; - pgadmin4-standalone = handleTest ./pgadmin4-standalone.nix {}; pgjwt = handleTest ./pgjwt.nix {}; pgmanage = handleTest ./pgmanage.nix {}; phosh = handleTest ./phosh.nix {}; @@ -643,6 +647,7 @@ in { systemd-confinement = handleTest ./systemd-confinement.nix {}; systemd-coredump = handleTest ./systemd-coredump.nix {}; systemd-cryptenroll = handleTest ./systemd-cryptenroll.nix {}; + systemd-credentials-tpm2 = handleTest ./systemd-credentials-tpm2.nix {}; systemd-escaping = handleTest ./systemd-escaping.nix {}; systemd-initrd-btrfs-raid = handleTest ./systemd-initrd-btrfs-raid.nix {}; systemd-initrd-luks-fido2 = handleTest ./systemd-initrd-luks-fido2.nix {}; @@ -653,6 +658,7 @@ in { systemd-initrd-shutdown = handleTest ./systemd-shutdown.nix { systemdStage1 = true; }; systemd-initrd-simple = handleTest ./systemd-initrd-simple.nix {}; systemd-initrd-swraid = handleTest ./systemd-initrd-swraid.nix {}; + systemd-initrd-vconsole = handleTest ./systemd-initrd-vconsole.nix {}; systemd-journal = handleTest ./systemd-journal.nix {}; systemd-machinectl = handleTest ./systemd-machinectl.nix {}; systemd-networkd = handleTest ./systemd-networkd.nix {}; @@ -664,6 +670,7 @@ in { systemd-nspawn = handleTest ./systemd-nspawn.nix {}; systemd-oomd = handleTest ./systemd-oomd.nix {}; systemd-portabled = handleTest ./systemd-portabled.nix {}; + systemd-repart = handleTest ./systemd-repart.nix {}; systemd-shutdown = handleTest ./systemd-shutdown.nix {}; systemd-timesyncd = handleTest ./systemd-timesyncd.nix {}; systemd-user-tmpfiles-rules = handleTest ./systemd-user-tmpfiles-rules.nix {}; diff --git a/nixos/tests/binary-cache.nix b/nixos/tests/binary-cache.nix new file mode 100644 index 0000000000000..0809e59e5a115 --- /dev/null +++ b/nixos/tests/binary-cache.nix @@ -0,0 +1,62 @@ +import ./make-test-python.nix ({ lib, ... }: + +with lib; + +{ + name = "binary-cache"; + meta.maintainers = with maintainers; [ thomasjm ]; + + nodes.machine = + { pkgs, ... }: { + imports = [ ../modules/installer/cd-dvd/channel.nix ]; + environment.systemPackages = with pkgs; [python3]; + system.extraDependencies = with pkgs; [hello.inputDerivation]; + nix.extraOptions = '' + experimental-features = nix-command + ''; + }; + + testScript = '' + # Build the cache, then remove it from the store + cachePath = machine.succeed("nix-build --no-out-link -E 'with import <nixpkgs> {}; mkBinaryCache { rootPaths = [hello]; }'").strip() + machine.succeed("cp -r %s/. /tmp/cache" % cachePath) + machine.succeed("nix-store --delete " + cachePath) + + # Sanity test of cache structure + status, stdout = machine.execute("ls /tmp/cache") + cache_files = stdout.split() + assert ("nix-cache-info" in cache_files) + assert ("nar" in cache_files) + + # Nix store ping should work + machine.succeed("nix store ping --store file:///tmp/cache") + + # Cache should contain a .narinfo referring to "hello" + grepLogs = machine.succeed("grep -l 'StorePath: /nix/store/[[:alnum:]]*-hello-.*' /tmp/cache/*.narinfo") + + # Get the store path referenced by the .narinfo + narInfoFile = grepLogs.strip() + narInfoContents = machine.succeed("cat " + narInfoFile) + import re + match = re.match(r"^StorePath: (/nix/store/[a-z0-9]*-hello-.*)$", narInfoContents, re.MULTILINE) + if not match: raise Exception("Couldn't find hello store path in cache") + storePath = match[1] + + # Delete the store path + machine.succeed("nix-store --delete " + storePath) + machine.succeed("[ ! -d %s ] || exit 1" % storePath) + + # Should be able to build hello using the cache + logs = machine.succeed("nix-build -A hello '<nixpkgs>' --option require-sigs false --option trusted-substituters file:///tmp/cache --option substituters file:///tmp/cache 2>&1") + logLines = logs.split("\n") + if not "this path will be fetched" in logLines[0]: raise Exception("Unexpected first log line") + def shouldBe(got, desired): + if got != desired: raise Exception("Expected '%s' but got '%s'" % (desired, got)) + shouldBe(logLines[1], " " + storePath) + shouldBe(logLines[2], "copying path '%s' from 'file:///tmp/cache'..." % storePath) + shouldBe(logLines[3], storePath) + + # Store path should exist in the store now + machine.succeed("[ -d %s ] || exit 1" % storePath) + ''; +}) diff --git a/nixos/tests/cockpit.nix b/nixos/tests/cockpit.nix new file mode 100644 index 0000000000000..4a4983f9bc4e6 --- /dev/null +++ b/nixos/tests/cockpit.nix @@ -0,0 +1,135 @@ +import ./make-test-python.nix ( + { pkgs, lib, ... }: + + let + user = "alice"; # from ./common/user-account.nix + password = "foobar"; # from ./common/user-account.nix + in { + name = "cockpit"; + meta = { + maintainers = with lib.maintainers; [ lucasew ]; + }; + nodes = { + server = { config, ... }: { + imports = [ ./common/user-account.nix ]; + security.polkit.enable = true; + users.users.${user} = { + extraGroups = [ "wheel" ]; + }; + services.cockpit = { + enable = true; + openFirewall = true; + settings = { + WebService = { + Origins = "https://server:9090"; + }; + }; + }; + }; + client = { config, ... }: { + imports = [ ./common/user-account.nix ]; + environment.systemPackages = let + seleniumScript = pkgs.writers.writePython3Bin "selenium-script" { + libraries = with pkgs.python3Packages; [ selenium ]; + } '' + from selenium import webdriver + from selenium.webdriver.common.by import By + from selenium.webdriver.firefox.options import Options + from selenium.webdriver.support.ui import WebDriverWait + from selenium.webdriver.support import expected_conditions as EC + from time import sleep + + + def log(msg): + from sys import stderr + print(f"[*] {msg}", file=stderr) + + + log("Initializing") + + options = Options() + options.add_argument("--headless") + + driver = webdriver.Firefox(options=options) + + driver.implicitly_wait(10) + + log("Opening homepage") + driver.get("https://server:9090") + + wait = WebDriverWait(driver, 60) + + + def wait_elem(by, query): + wait.until(EC.presence_of_element_located((by, query))) + + + def wait_title_contains(title): + wait.until(EC.title_contains(title)) + + + def find_element(by, query): + return driver.find_element(by, query) + + + def set_value(elem, value): + script = 'arguments[0].value = arguments[1]' + return driver.execute_script(script, elem, value) + + + log("Waiting for the homepage to load") + + # cockpit sets initial title as hostname + wait_title_contains("server") + wait_elem(By.CSS_SELECTOR, 'input#login-user-input') + + log("Homepage loaded!") + + log("Filling out username") + login_input = find_element(By.CSS_SELECTOR, 'input#login-user-input') + set_value(login_input, "${user}") + + log("Filling out password") + password_input = find_element(By.CSS_SELECTOR, 'input#login-password-input') + set_value(password_input, "${password}") + + log("Submiting credentials for login") + driver.find_element(By.CSS_SELECTOR, 'button#login-button').click() + + # driver.implicitly_wait(1) + # driver.get("https://server:9090/system") + + log("Waiting dashboard to load") + wait_title_contains("${user}@server") + + log("Waiting for the frontend to initalize") + sleep(1) + + log("Looking for that banner that tells about limited access") + container_iframe = find_element(By.CSS_SELECTOR, 'iframe.container-frame') + driver.switch_to.frame(container_iframe) + + assert "Web console is running in limited access mode" in driver.page_source + + driver.close() + ''; + in with pkgs; [ firefox-unwrapped geckodriver seleniumScript ]; + }; + }; + + testScript = '' + start_all() + + server.wait_for_open_port(9090) + server.wait_for_unit("network.target") + server.wait_for_unit("multi-user.target") + server.systemctl("start", "polkit") + + client.wait_for_unit("multi-user.target") + + client.succeed("curl -k https://server:9090 -o /dev/stderr") + print(client.succeed("whoami")) + client.succeed('PYTHONUNBUFFERED=1 selenium-script') + ''; + } +) diff --git a/nixos/tests/cups-pdf.nix b/nixos/tests/cups-pdf.nix index 70d14f29e2e5d..957b0296a755b 100644 --- a/nixos/tests/cups-pdf.nix +++ b/nixos/tests/cups-pdf.nix @@ -23,7 +23,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: { testScript = '' from subprocess import run - machine.wait_for_unit("cups.service") + machine.wait_for_unit("multi-user.target") for name in ("opt", "noopt"): text = f"test text {name}".upper() machine.wait_until_succeeds(f"lpstat -v {name}") diff --git a/nixos/tests/discourse.nix b/nixos/tests/discourse.nix index 35ca083c6c4e0..c79ba41c2eb9c 100644 --- a/nixos/tests/discourse.nix +++ b/nixos/tests/discourse.nix @@ -40,7 +40,7 @@ import ./make-test-python.nix ( networking.extraHosts = '' 127.0.0.1 ${discourseDomain} - ${nodes.client.config.networking.primaryIPAddress} ${clientDomain} + ${nodes.client.networking.primaryIPAddress} ${clientDomain} ''; services.postfix = { @@ -90,7 +90,7 @@ import ./make-test-python.nix ( networking.extraHosts = '' 127.0.0.1 ${clientDomain} - ${nodes.discourse.config.networking.primaryIPAddress} ${discourseDomain} + ${nodes.discourse.networking.primaryIPAddress} ${discourseDomain} ''; services.dovecot2 = { @@ -178,8 +178,8 @@ import ./make-test-python.nix ( discourse.wait_until_succeeds("curl -sS -f https://${discourseDomain}") discourse.succeed( "curl -sS -f https://${discourseDomain}/session/csrf -c cookie -b cookie -H 'Accept: application/json' | jq -r '\"X-CSRF-Token: \" + .csrf' > csrf_token", - "curl -sS -f https://${discourseDomain}/session -c cookie -b cookie -H @csrf_token -H 'Accept: application/json' -d 'login=${nodes.discourse.config.services.discourse.admin.username}' -d \"password=${adminPassword}\" | jq -e '.user.username == \"${nodes.discourse.config.services.discourse.admin.username}\"'", - "curl -sS -f https://${discourseDomain}/login -v -H 'Accept: application/json' -c cookie -b cookie 2>&1 | grep ${nodes.discourse.config.services.discourse.admin.username}", + "curl -sS -f https://${discourseDomain}/session -c cookie -b cookie -H @csrf_token -H 'Accept: application/json' -d 'login=${nodes.discourse.services.discourse.admin.username}' -d \"password=${adminPassword}\" | jq -e '.user.username == \"${nodes.discourse.services.discourse.admin.username}\"'", + "curl -sS -f https://${discourseDomain}/login -v -H 'Accept: application/json' -c cookie -b cookie 2>&1 | grep ${nodes.discourse.services.discourse.admin.username}", ) client.wait_for_unit("postfix.service") diff --git a/nixos/tests/envoy.nix b/nixos/tests/envoy.nix index 9d2c32ce102f2..1e4bfe626398e 100644 --- a/nixos/tests/envoy.nix +++ b/nixos/tests/envoy.nix @@ -13,7 +13,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : { socket_address = { protocol = "TCP"; address = "127.0.0.1"; - port_value = 9901; + port_value = 80; }; }; }; @@ -22,12 +22,33 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : { clusters = []; }; }; + specialisation = { + withoutConfigValidation.configuration = { ... }: { + services.envoy = { + requireValidConfig = false; + settings.admin.access_log_path = lib.mkForce "/var/log/envoy/access.log"; + }; + }; + }; }; - testScript = '' - machine.start() - machine.wait_for_unit("envoy.service") - machine.wait_for_open_port(9901) - machine.wait_until_succeeds("curl -fsS localhost:9901/ready") - ''; + testScript = { nodes, ... }: + let + specialisations = "${nodes.machine.system.build.toplevel}/specialisation"; + in + '' + machine.start() + + with subtest("envoy.service starts and responds with ready"): + machine.wait_for_unit("envoy.service") + machine.wait_for_open_port(80) + machine.wait_until_succeeds("curl -fsS localhost:80/ready") + + with subtest("envoy.service works with config path not available at eval time"): + machine.succeed('${specialisations}/withoutConfigValidation/bin/switch-to-configuration test') + machine.wait_for_unit("envoy.service") + machine.wait_for_open_port(80) + machine.wait_until_succeeds("curl -fsS localhost:80/ready") + machine.succeed('test -f /var/log/envoy/access.log') + ''; }) diff --git a/nixos/tests/gnupg.nix b/nixos/tests/gnupg.nix new file mode 100644 index 0000000000000..65a9a93007fd9 --- /dev/null +++ b/nixos/tests/gnupg.nix @@ -0,0 +1,118 @@ +import ./make-test-python.nix ({ pkgs, lib, ...}: + +{ + name = "gnupg"; + meta = with lib.maintainers; { + maintainers = [ rnhmjoj ]; + }; + + # server for testing SSH + nodes.server = { ... }: { + imports = [ ../modules/profiles/minimal.nix ]; + + users.users.alice.isNormalUser = true; + services.openssh.enable = true; + }; + + # machine for testing GnuPG + nodes.machine = { pkgs, ... }: { + imports = [ ../modules/profiles/minimal.nix ]; + + users.users.alice.isNormalUser = true; + services.getty.autologinUser = "alice"; + + environment.shellInit = '' + # preset a key passphrase in gpg-agent + preset_key() { + # find all keys + case "$1" in + ssh) grips=$(awk '/^[0-9A-F]/{print $1}' "''${GNUPGHOME:-$HOME/.gnupg}/sshcontrol") ;; + pgp) grips=$(gpg --with-keygrip --list-secret-keys | awk '/Keygrip/{print $3}') ;; + esac + + # try to preset the passphrase for each key found + for grip in $grips; do + "$(gpgconf --list-dirs libexecdir)/gpg-preset-passphrase" -c -P "$2" "$grip" + done + } + ''; + + programs.gnupg.agent.enable = true; + programs.gnupg.agent.enableSSHSupport = true; + }; + + testScript = + '' + import shlex + + + def as_alice(command: str) -> str: + """ + Wraps a command to run it as Alice in a login shell + """ + quoted = shlex.quote(command) + return "su --login alice --command " + quoted + + + start_all() + + with subtest("Wait for the autologin"): + machine.wait_until_tty_matches("1", "alice@machine") + + with subtest("Can generate a PGP key"): + # Note: this needs a tty because of pinentry + machine.send_chars("gpg --gen-key\n") + machine.wait_until_tty_matches("1", "Real name:") + machine.send_chars("Alice\n") + machine.wait_until_tty_matches("1", "Email address:") + machine.send_chars("alice@machine\n") + machine.wait_until_tty_matches("1", "Change") + machine.send_chars("O\n") + machine.wait_until_tty_matches("1", "Please enter") + machine.send_chars("pgp_p4ssphrase\n") + machine.wait_until_tty_matches("1", "Please re-enter") + machine.send_chars("pgp_p4ssphrase\n") + machine.wait_until_tty_matches("1", "public and secret key created") + + with subtest("Confirm the key is in the keyring"): + machine.wait_until_succeeds(as_alice("gpg --list-secret-keys | grep -q alice@machine")) + + with subtest("Can generate and add an SSH key"): + machine.succeed(as_alice("ssh-keygen -t ed25519 -f alice -N ssh_p4ssphrase")) + + # Note: apparently this must be run before using the OpenSSH agent + # socket for the first time in a tty. It's not needed for `ssh` + # because there's a hook that calls it automatically (only in NixOS). + machine.send_chars("gpg-connect-agent updatestartuptty /bye\n") + + # Note: again, this needs a tty because of pinentry + machine.send_chars("ssh-add alice\n") + machine.wait_until_tty_matches("1", "Enter passphrase") + machine.send_chars("ssh_p4ssphrase\n") + machine.wait_until_tty_matches("1", "Please enter") + machine.send_chars("ssh_agent_p4ssphrase\n") + machine.wait_until_tty_matches("1", "Please re-enter") + machine.send_chars("ssh_agent_p4ssphrase\n") + + with subtest("Confirm the SSH key has been registered"): + machine.wait_until_succeeds(as_alice("ssh-add -l | grep -q alice@machine")) + + with subtest("Can preset the key passphrases in the agent"): + machine.succeed(as_alice("echo allow-preset-passphrase > .gnupg/gpg-agent.conf")) + machine.succeed(as_alice("pkill gpg-agent")) + machine.succeed(as_alice("preset_key pgp pgp_p4ssphrase")) + machine.succeed(as_alice("preset_key ssh ssh_agent_p4ssphrase")) + + with subtest("Can encrypt and decrypt a message"): + machine.succeed(as_alice("echo Hello | gpg -e -r alice | gpg -d | grep -q Hello")) + + with subtest("Can log into the server"): + # Install Alice's public key + public_key = machine.succeed(as_alice("cat alice.pub")) + server.succeed("mkdir /etc/ssh/authorized_keys.d") + server.succeed(f"printf '{public_key}' > /etc/ssh/authorized_keys.d/alice") + + server.wait_for_open_port(22) + machine.succeed(as_alice("ssh -i alice -o StrictHostKeyChecking=no server exit")) + ''; +}) diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix index 8d58de75eabc3..8585cb3585fef 100644 --- a/nixos/tests/home-assistant.nix +++ b/nixos/tests/home-assistant.nix @@ -22,22 +22,23 @@ in { enable = true; inherit configDir; - # tests loading components by overriding the package + # provide dependencies through package overrides package = (pkgs.home-assistant.override { extraPackages = ps: with ps; [ colorama ]; - extraComponents = [ "zha" ]; - }).overrideAttrs (oldAttrs: { - doInstallCheck = false; + extraComponents = [ + # test char-tty device allow propagation into the service + "zha" + ]; }); - # tests loading components from the module + # provide component dependencies explicitly from the module extraComponents = [ - "wake_on_lan" + "mqtt" ]; - # test extra package passing from the module + # provide package for postgresql support extraPackages = python3Packages: with python3Packages; [ psycopg2 ]; @@ -111,36 +112,38 @@ in { }; testScript = { nodes, ... }: let - system = nodes.hass.config.system.build.toplevel; + system = nodes.hass.system.build.toplevel; in '' - import re import json start_all() - # Parse the package path out of the systemd unit, as we cannot - # access the final package, that is overridden inside the module, - # by any other means. - pattern = re.compile(r"path=(?P<path>[\/a-z0-9-.]+)\/bin\/hass") - response = hass.execute("systemctl show -p ExecStart home-assistant.service")[1] - match = pattern.search(response) - assert match - package = match.group('path') - - def get_journal_cursor(host) -> str: - exit, out = host.execute("journalctl -u home-assistant.service -n1 -o json-pretty --output-fields=__CURSOR") + def get_journal_cursor() -> str: + exit, out = hass.execute("journalctl -u home-assistant.service -n1 -o json-pretty --output-fields=__CURSOR") assert exit == 0 return json.loads(out)["__CURSOR"] - def wait_for_homeassistant(host, cursor): - host.wait_until_succeeds(f"journalctl --after-cursor='{cursor}' -u home-assistant.service | grep -q 'Home Assistant initialized in'") + def get_journal_since(cursor) -> str: + exit, out = hass.execute(f"journalctl --after-cursor='{cursor}' -u home-assistant.service") + assert exit == 0 + return out + + + def get_unit_property(property) -> str: + exit, out = hass.execute(f"systemctl show --property={property} home-assistant.service") + assert exit == 0 + return out + + + def wait_for_homeassistant(cursor): + hass.wait_until_succeeds(f"journalctl --after-cursor='{cursor}' -u home-assistant.service | grep -q 'Home Assistant initialized in'") hass.wait_for_unit("home-assistant.service") - cursor = get_journal_cursor(hass) + cursor = get_journal_cursor() with subtest("Check that YAML configuration file is in place"): hass.succeed("test -L ${configDir}/configuration.yaml") @@ -148,19 +151,22 @@ in { with subtest("Check the lovelace config is copied because lovelaceConfigWritable = true"): hass.succeed("test -f ${configDir}/ui-lovelace.yaml") - with subtest("Check extraComponents and extraPackages are considered from the package"): - hass.succeed(f"grep -q 'colorama' {package}/extra_packages") - hass.succeed(f"grep -q 'zha' {package}/extra_components") - - with subtest("Check extraComponents and extraPackages are considered from the module"): - hass.succeed(f"grep -q 'psycopg2' {package}/extra_packages") - hass.succeed(f"grep -q 'wake_on_lan' {package}/extra_components") - with subtest("Check that Home Assistant's web interface and API can be reached"): - wait_for_homeassistant(hass, cursor) + wait_for_homeassistant(cursor) hass.wait_for_open_port(8123) hass.succeed("curl --fail http://localhost:8123/lovelace") + with subtest("Check that optional dependencies are in the PYTHONPATH"): + env = get_unit_property("Environment") + python_path = env.split("PYTHONPATH=")[1].split()[0] + for package in ["colorama", "paho-mqtt", "psycopg2"]: + assert package in python_path, f"{package} not in PYTHONPATH" + + with subtest("Check that declaratively configured components get setup"): + journal = get_journal_since(cursor) + for domain in ["emulated_hue", "wake_on_lan"]: + assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing" + with subtest("Check that capabilities are passed for emulated_hue to bind to port 80"): hass.wait_for_open_port(80) hass.succeed("curl --fail http://localhost:80/description.xml") @@ -169,25 +175,28 @@ in { hass.succeed("systemctl show -p DeviceAllow home-assistant.service | grep -q char-ttyUSB") with subtest("Check service reloads when configuration changes"): - # store the old pid of the process - pid = hass.succeed("systemctl show --property=MainPID home-assistant.service") - cursor = get_journal_cursor(hass) - hass.succeed("${system}/specialisation/differentName/bin/switch-to-configuration test") - new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service") - assert pid == new_pid, "The PID of the process should not change between process reloads" - wait_for_homeassistant(hass, cursor) - - with subtest("check service restarts when package changes"): - pid = new_pid - cursor = get_journal_cursor(hass) - hass.succeed("${system}/specialisation/newFeature/bin/switch-to-configuration test") - new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service") - assert pid != new_pid, "The PID of the process shoudl change when the HA binary changes" - wait_for_homeassistant(hass, cursor) + pid = hass.succeed("systemctl show --property=MainPID home-assistant.service") + cursor = get_journal_cursor() + hass.succeed("${system}/specialisation/differentName/bin/switch-to-configuration test") + new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service") + assert pid == new_pid, "The PID of the process should not change between process reloads" + wait_for_homeassistant(cursor) + + with subtest("Check service restarts when dependencies change"): + pid = new_pid + cursor = get_journal_cursor() + hass.succeed("${system}/specialisation/newFeature/bin/switch-to-configuration test") + new_pid = hass.succeed("systemctl show --property=MainPID home-assistant.service") + assert pid != new_pid, "The PID of the process should change when its PYTHONPATH changess" + wait_for_homeassistant(cursor) + + with subtest("Check that new components get setup after restart"): + journal = get_journal_since(cursor) + for domain in ["esphome"]: + assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing" with subtest("Check that no errors were logged"): - output_log = hass.succeed("cat ${configDir}/home-assistant.log") - assert "ERROR" not in output_log + hass.fail("journalctl -u home-assistant -o cat | grep -q ERROR") with subtest("Check systemd unit hardening"): hass.log(hass.succeed("systemctl cat home-assistant.service")) diff --git a/nixos/tests/k3s/default.nix b/nixos/tests/k3s/default.nix index 07d93c41c7a68..e168f8233c763 100644 --- a/nixos/tests/k3s/default.nix +++ b/nixos/tests/k3s/default.nix @@ -1,9 +1,13 @@ { system ? builtins.currentSystem , pkgs ? import ../../.. { inherit system; } +, lib ? pkgs.lib }: +let + allK3s = lib.filterAttrs (n: _: lib.strings.hasPrefix "k3s_" n) pkgs; +in { # Run a single node k3s cluster and verify a pod can run - single-node = import ./single-node.nix { inherit system pkgs; }; + single-node = lib.mapAttrs (_: k3s: import ./single-node.nix { inherit system pkgs k3s; }) allK3s; # Run a multi-node k3s cluster and verify pod networking works across nodes - multi-node = import ./multi-node.nix { inherit system pkgs; }; + multi-node = lib.mapAttrs (_: k3s: import ./multi-node.nix { inherit system pkgs k3s; }) allK3s; } diff --git a/nixos/tests/k3s/multi-node.nix b/nixos/tests/k3s/multi-node.nix index 9a6c7fd465739..932b4639b39c8 100644 --- a/nixos/tests/k3s/multi-node.nix +++ b/nixos/tests/k3s/multi-node.nix @@ -1,4 +1,4 @@ -import ../make-test-python.nix ({ pkgs, lib, ... }: +import ../make-test-python.nix ({ pkgs, lib, k3s, ... }: let imageEnv = pkgs.buildEnv { name = "k3s-pause-image-env"; @@ -39,7 +39,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... }: tokenFile = pkgs.writeText "token" "p@s$w0rd"; in { - name = "k3s-multi-node"; + name = "${k3s.name}-multi-node"; nodes = { server = { pkgs, ... }: { @@ -52,7 +52,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... }: inherit tokenFile; enable = true; role = "server"; - package = pkgs.k3s; + package = k3s; clusterInit = true; extraFlags = builtins.toString [ "--disable" "coredns" diff --git a/nixos/tests/k3s/single-node.nix b/nixos/tests/k3s/single-node.nix index a95fa4a031e3f..d61595d889e2a 100644 --- a/nixos/tests/k3s/single-node.nix +++ b/nixos/tests/k3s/single-node.nix @@ -1,4 +1,4 @@ -import ../make-test-python.nix ({ pkgs, lib, ... }: +import ../make-test-python.nix ({ pkgs, lib, k3s, ... }: let imageEnv = pkgs.buildEnv { name = "k3s-pause-image-env"; @@ -24,7 +24,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... }: ''; in { - name = "k3s"; + name = "${k3s.name}-single-node"; meta = with pkgs.lib.maintainers; { maintainers = [ euank ]; }; @@ -38,7 +38,7 @@ import ../make-test-python.nix ({ pkgs, lib, ... }: services.k3s.enable = true; services.k3s.role = "server"; - services.k3s.package = pkgs.k3s; + services.k3s.package = k3s; # Slightly reduce resource usage services.k3s.extraFlags = builtins.toString [ "--disable" "coredns" @@ -77,6 +77,9 @@ import ../make-test-python.nix ({ pkgs, lib, ... }: machine.succeed("k3s kubectl wait --for 'condition=Ready' pod/test") machine.succeed("k3s kubectl delete -f ${testPodYaml}") + # regression test for #176445 + machine.fail("journalctl -o cat -u k3s.service | grep 'ipset utility not found'") + machine.shutdown() ''; }) diff --git a/nixos/tests/keepassxc.nix b/nixos/tests/keepassxc.nix index debb469032a62..a4f452412cdf8 100644 --- a/nixos/tests/keepassxc.nix +++ b/nixos/tests/keepassxc.nix @@ -4,6 +4,7 @@ import ./make-test-python.nix ({ pkgs, ...} : name = "keepassxc"; meta = with pkgs.lib.maintainers; { maintainers = [ turion ]; + timeout = 1800; }; nodes.machine = { ... }: @@ -55,9 +56,12 @@ import ./make-test-python.nix ({ pkgs, ...} : machine.sleep(5) # Regression #163482: keepassxc did not crash machine.succeed("ps -e | grep keepassxc") - machine.wait_for_text("foo.kdbx") + machine.wait_for_text("Open database") machine.send_key("ret") - machine.sleep(1) + + # Wait for the enter password screen to appear. + machine.wait_for_text("/home/alice/foo.kdbx") + # Click on "Browse" button to select keyfile machine.send_key("tab") machine.send_chars("/home/alice/foo.keyfile") diff --git a/nixos/tests/luks.nix b/nixos/tests/luks.nix new file mode 100644 index 0000000000000..82f5095cb2602 --- /dev/null +++ b/nixos/tests/luks.nix @@ -0,0 +1,69 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: { + name = "luks"; + + nodes.machine = { pkgs, ... }: { + # Use systemd-boot + virtualisation = { + emptyDiskImages = [ 512 512 ]; + useBootLoader = true; + useEFIBoot = true; + }; + boot.loader.systemd-boot.enable = true; + + boot.kernelParams = lib.mkOverride 5 [ "console=tty1" ]; + + environment.systemPackages = with pkgs; [ cryptsetup ]; + + specialisation = rec { + boot-luks.configuration = { + boot.initrd.luks.devices = lib.mkVMOverride { + # We have two disks and only type one password - key reuse is in place + cryptroot.device = "/dev/vdc"; + cryptroot2.device = "/dev/vdd"; + }; + virtualisation.bootDevice = "/dev/mapper/cryptroot"; + }; + boot-luks-custom-keymap.configuration = lib.mkMerge [ + boot-luks.configuration + { + console.keyMap = "neo"; + } + ]; + }; + }; + + enableOCR = true; + + testScript = '' + # Create encrypted volume + machine.wait_for_unit("multi-user.target") + machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -") + machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdd -") + + # Boot from the encrypted disk + machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf") + machine.succeed("sync") + machine.crash() + + # Boot and decrypt the disk + machine.start() + machine.wait_for_text("Passphrase for") + machine.send_chars("supersecret\n") + machine.wait_for_unit("multi-user.target") + + assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount") + + # Boot from the encrypted disk with custom keymap + machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks-custom-keymap.conf") + machine.succeed("sync") + machine.crash() + + # Boot and decrypt the disk + machine.start() + machine.wait_for_text("Passphrase for") + machine.send_chars("havfkhfrkfl\n") + machine.wait_for_unit("multi-user.target") + + assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount") + ''; +}) diff --git a/nixos/tests/maddy.nix b/nixos/tests/maddy.nix index b9d0416482da1..800d254f17704 100644 --- a/nixos/tests/maddy.nix +++ b/nixos/tests/maddy.nix @@ -9,6 +9,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { hostname = "server"; primaryDomain = "server"; openFirewall = true; + ensureAccounts = [ "postmaster@server" ]; }; }; @@ -50,7 +51,6 @@ import ./make-test-python.nix ({ pkgs, ... }: { server.wait_for_open_port(587) server.succeed("maddyctl creds create --password test postmaster@server") - server.succeed("maddyctl imap-acct create postmaster@server") client.succeed("send-testmail") client.succeed("test-imap") diff --git a/nixos/tests/miriway.nix b/nixos/tests/miriway.nix index c4c50646f0153..d0d9f16d40f95 100644 --- a/nixos/tests/miriway.nix +++ b/nixos/tests/miriway.nix @@ -3,7 +3,10 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { meta = { maintainers = with lib.maintainers; [ OPNA2608 ]; - # FIXME On ARM Miriway inside the VM doesn't receive keyboard inputs, why? + # Natively running Mir has problems with capturing the first registered libinput device. + # In our VM runners on ARM and on some hardware configs (my RPi4, distro-independent), this misses the keyboard. + # It can be worked around by dis- and reconnecting the affected hardware, but we can't do this in these tests. + # https://github.com/MirServer/mir/issues/2837 broken = pkgs.stdenv.hostPlatform.isAarch; }; @@ -30,6 +33,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { enable = true; config = '' add-wayland-extensions=all + enable-x11= ctrl-alt=t:foot --maximized ctrl-alt=a:env WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY=invalid alacritty --option window.startup_mode=maximized diff --git a/nixos/tests/nebula.nix b/nixos/tests/nebula.nix index 372cfebdf801b..89b91d89fcb3f 100644 --- a/nixos/tests/nebula.nix +++ b/nixos/tests/nebula.nix @@ -10,6 +10,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: let environment.systemPackages = [ pkgs.nebula ]; users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; services.openssh.enable = true; + networking.interfaces.eth1.useDHCP = false; services.nebula.networks.smoke = { # Note that these paths won't exist when the machine is first booted. @@ -30,13 +31,14 @@ in lighthouse = { ... } @ args: makeNebulaNode args "lighthouse" { - networking.interfaces.eth1.ipv4.addresses = [{ + networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{ address = "192.168.1.1"; prefixLength = 24; }]; services.nebula.networks.smoke = { isLighthouse = true; + isRelay = true; firewall = { outbound = [ { port = "any"; proto = "any"; host = "any"; } ]; inbound = [ { port = "any"; proto = "any"; host = "any"; } ]; @@ -44,9 +46,9 @@ in }; }; - node2 = { ... } @ args: - makeNebulaNode args "node2" { - networking.interfaces.eth1.ipv4.addresses = [{ + allowAny = { ... } @ args: + makeNebulaNode args "allowAny" { + networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{ address = "192.168.1.2"; prefixLength = 24; }]; @@ -55,6 +57,7 @@ in staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; }; isLighthouse = false; lighthouses = [ "10.0.100.1" ]; + relays = [ "10.0.100.1" ]; firewall = { outbound = [ { port = "any"; proto = "any"; host = "any"; } ]; inbound = [ { port = "any"; proto = "any"; host = "any"; } ]; @@ -62,9 +65,9 @@ in }; }; - node3 = { ... } @ args: - makeNebulaNode args "node3" { - networking.interfaces.eth1.ipv4.addresses = [{ + allowFromLighthouse = { ... } @ args: + makeNebulaNode args "allowFromLighthouse" { + networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{ address = "192.168.1.3"; prefixLength = 24; }]; @@ -73,6 +76,7 @@ in staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; }; isLighthouse = false; lighthouses = [ "10.0.100.1" ]; + relays = [ "10.0.100.1" ]; firewall = { outbound = [ { port = "any"; proto = "any"; host = "any"; } ]; inbound = [ { port = "any"; proto = "any"; host = "lighthouse"; } ]; @@ -80,9 +84,9 @@ in }; }; - node4 = { ... } @ args: - makeNebulaNode args "node4" { - networking.interfaces.eth1.ipv4.addresses = [{ + allowToLighthouse = { ... } @ args: + makeNebulaNode args "allowToLighthouse" { + networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{ address = "192.168.1.4"; prefixLength = 24; }]; @@ -92,6 +96,7 @@ in staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; }; isLighthouse = false; lighthouses = [ "10.0.100.1" ]; + relays = [ "10.0.100.1" ]; firewall = { outbound = [ { port = "any"; proto = "any"; host = "lighthouse"; } ]; inbound = [ { port = "any"; proto = "any"; host = "any"; } ]; @@ -99,9 +104,9 @@ in }; }; - node5 = { ... } @ args: - makeNebulaNode args "node5" { - networking.interfaces.eth1.ipv4.addresses = [{ + disabled = { ... } @ args: + makeNebulaNode args "disabled" { + networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{ address = "192.168.1.5"; prefixLength = 24; }]; @@ -111,6 +116,7 @@ in staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; }; isLighthouse = false; lighthouses = [ "10.0.100.1" ]; + relays = [ "10.0.100.1" ]; firewall = { outbound = [ { port = "any"; proto = "any"; host = "lighthouse"; } ]; inbound = [ { port = "any"; proto = "any"; host = "any"; } ]; @@ -123,12 +129,14 @@ in testScript = let setUpPrivateKey = name: '' - ${name}.succeed( - "mkdir -p /root/.ssh", - "chown 700 /root/.ssh", - "cat '${snakeOilPrivateKey}' > /root/.ssh/id_snakeoil", - "chown 600 /root/.ssh/id_snakeoil", - ) + ${name}.start() + ${name}.succeed( + "mkdir -p /root/.ssh", + "chown 700 /root/.ssh", + "cat '${snakeOilPrivateKey}' > /root/.ssh/id_snakeoil", + "chown 600 /root/.ssh/id_snakeoil", + "mkdir -p /root" + ) ''; # From what I can tell, StrictHostKeyChecking=no is necessary for ssh to work between machines. @@ -146,26 +154,48 @@ in ${name}.succeed( "mkdir -p /etc/nebula", "nebula-cert keygen -out-key /etc/nebula/${name}.key -out-pub /etc/nebula/${name}.pub", - "scp ${sshOpts} /etc/nebula/${name}.pub 192.168.1.1:/tmp/${name}.pub", + "scp ${sshOpts} /etc/nebula/${name}.pub root@192.168.1.1:/root/${name}.pub", ) lighthouse.succeed( - 'nebula-cert sign -ca-crt /etc/nebula/ca.crt -ca-key /etc/nebula/ca.key -name "${name}" -groups "${name}" -ip "${ip}" -in-pub /tmp/${name}.pub -out-crt /tmp/${name}.crt', + 'nebula-cert sign -ca-crt /etc/nebula/ca.crt -ca-key /etc/nebula/ca.key -name "${name}" -groups "${name}" -ip "${ip}" -in-pub /root/${name}.pub -out-crt /root/${name}.crt' ) ${name}.succeed( - "scp ${sshOpts} 192.168.1.1:/tmp/${name}.crt /etc/nebula/${name}.crt", - "scp ${sshOpts} 192.168.1.1:/etc/nebula/ca.crt /etc/nebula/ca.crt", + "scp ${sshOpts} root@192.168.1.1:/root/${name}.crt /etc/nebula/${name}.crt", + "scp ${sshOpts} root@192.168.1.1:/etc/nebula/ca.crt /etc/nebula/ca.crt", + '(id nebula-smoke >/dev/null && chown -R nebula-smoke:nebula-smoke /etc/nebula) || true' ) ''; - in '' - start_all() + getPublicIp = node: '' + ${node}.succeed("ip --brief addr show eth1 | awk '{print $3}' | tail -n1 | cut -d/ -f1").strip() + ''; + # Never do this for anything security critical! (Thankfully it's just a test.) + # Restart Nebula right after the mutual block and/or restore so the state is fresh. + blockTrafficBetween = nodeA: nodeB: '' + node_a = ${getPublicIp nodeA} + node_b = ${getPublicIp nodeB} + ${nodeA}.succeed("iptables -I INPUT -s " + node_b + " -j DROP") + ${nodeB}.succeed("iptables -I INPUT -s " + node_a + " -j DROP") + ${nodeA}.systemctl("restart nebula@smoke.service") + ${nodeB}.systemctl("restart nebula@smoke.service") + ''; + allowTrafficBetween = nodeA: nodeB: '' + node_a = ${getPublicIp nodeA} + node_b = ${getPublicIp nodeB} + ${nodeA}.succeed("iptables -D INPUT -s " + node_b + " -j DROP") + ${nodeB}.succeed("iptables -D INPUT -s " + node_a + " -j DROP") + ${nodeA}.systemctl("restart nebula@smoke.service") + ${nodeB}.systemctl("restart nebula@smoke.service") + ''; + in '' # Create the certificate and sign the lighthouse's keys. ${setUpPrivateKey "lighthouse"} lighthouse.succeed( "mkdir -p /etc/nebula", 'nebula-cert ca -name "Smoke Test" -out-crt /etc/nebula/ca.crt -out-key /etc/nebula/ca.key', 'nebula-cert sign -ca-crt /etc/nebula/ca.crt -ca-key /etc/nebula/ca.key -name "lighthouse" -groups "lighthouse" -ip "10.0.100.1/24" -out-crt /etc/nebula/lighthouse.crt -out-key /etc/nebula/lighthouse.key', + 'chown -R nebula-smoke:nebula-smoke /etc/nebula' ) # Reboot the lighthouse and verify that the nebula service comes up on boot. @@ -175,49 +205,104 @@ in lighthouse.wait_for_unit("nebula@smoke.service") lighthouse.succeed("ping -c5 10.0.100.1") - # Create keys for node2's nebula service and test that it comes up. - ${setUpPrivateKey "node2"} - ${signKeysFor "node2" "10.0.100.2/24"} - ${restartAndCheckNebula "node2" "10.0.100.2"} + # Create keys for allowAny's nebula service and test that it comes up. + ${setUpPrivateKey "allowAny"} + ${signKeysFor "allowAny" "10.0.100.2/24"} + ${restartAndCheckNebula "allowAny" "10.0.100.2"} - # Create keys for node3's nebula service and test that it comes up. - ${setUpPrivateKey "node3"} - ${signKeysFor "node3" "10.0.100.3/24"} - ${restartAndCheckNebula "node3" "10.0.100.3"} + # Create keys for allowFromLighthouse's nebula service and test that it comes up. + ${setUpPrivateKey "allowFromLighthouse"} + ${signKeysFor "allowFromLighthouse" "10.0.100.3/24"} + ${restartAndCheckNebula "allowFromLighthouse" "10.0.100.3"} - # Create keys for node4's nebula service and test that it comes up. - ${setUpPrivateKey "node4"} - ${signKeysFor "node4" "10.0.100.4/24"} - ${restartAndCheckNebula "node4" "10.0.100.4"} + # Create keys for allowToLighthouse's nebula service and test that it comes up. + ${setUpPrivateKey "allowToLighthouse"} + ${signKeysFor "allowToLighthouse" "10.0.100.4/24"} + ${restartAndCheckNebula "allowToLighthouse" "10.0.100.4"} - # Create keys for node4's nebula service and test that it does not come up. - ${setUpPrivateKey "node5"} - ${signKeysFor "node5" "10.0.100.5/24"} - node5.fail("systemctl status nebula@smoke.service") - node5.fail("ping -c5 10.0.100.5") + # Create keys for disabled's nebula service and test that it does not come up. + ${setUpPrivateKey "disabled"} + ${signKeysFor "disabled" "10.0.100.5/24"} + disabled.fail("systemctl status nebula@smoke.service") + disabled.fail("ping -c5 10.0.100.5") - # The lighthouse can ping node2 and node3 but not node5 + # The lighthouse can ping allowAny and allowFromLighthouse but not disabled lighthouse.succeed("ping -c3 10.0.100.2") lighthouse.succeed("ping -c3 10.0.100.3") lighthouse.fail("ping -c3 10.0.100.5") - # node2 can ping the lighthouse, but not node3 because of its inbound firewall - node2.succeed("ping -c3 10.0.100.1") - node2.fail("ping -c3 10.0.100.3") - - # node3 can ping the lighthouse and node2 - node3.succeed("ping -c3 10.0.100.1") - node3.succeed("ping -c3 10.0.100.2") - - # node4 can ping the lighthouse but not node2 or node3 - node4.succeed("ping -c3 10.0.100.1") - node4.fail("ping -c3 10.0.100.2") - node4.fail("ping -c3 10.0.100.3") - - # node2 can ping node3 now that node3 pinged it first - node2.succeed("ping -c3 10.0.100.3") - # node4 can ping node2 if node2 pings it first - node2.succeed("ping -c3 10.0.100.4") - node4.succeed("ping -c3 10.0.100.2") + # allowAny can ping the lighthouse, but not allowFromLighthouse because of its inbound firewall + allowAny.succeed("ping -c3 10.0.100.1") + allowAny.fail("ping -c3 10.0.100.3") + + # allowFromLighthouse can ping the lighthouse and allowAny + allowFromLighthouse.succeed("ping -c3 10.0.100.1") + allowFromLighthouse.succeed("ping -c3 10.0.100.2") + + # block allowFromLighthouse <-> allowAny, and allowFromLighthouse -> allowAny should still work. + ${blockTrafficBetween "allowFromLighthouse" "allowAny"} + allowFromLighthouse.succeed("ping -c10 10.0.100.2") + ${allowTrafficBetween "allowFromLighthouse" "allowAny"} + allowFromLighthouse.succeed("ping -c10 10.0.100.2") + + # allowToLighthouse can ping the lighthouse but not allowAny or allowFromLighthouse + allowToLighthouse.succeed("ping -c3 10.0.100.1") + allowToLighthouse.fail("ping -c3 10.0.100.2") + allowToLighthouse.fail("ping -c3 10.0.100.3") + + # allowAny can ping allowFromLighthouse now that allowFromLighthouse pinged it first + allowAny.succeed("ping -c3 10.0.100.3") + + # block allowAny <-> allowFromLighthouse, and allowAny -> allowFromLighthouse should still work. + ${blockTrafficBetween "allowAny" "allowFromLighthouse"} + allowFromLighthouse.succeed("ping -c10 10.0.100.2") + allowAny.succeed("ping -c10 10.0.100.3") + ${allowTrafficBetween "allowAny" "allowFromLighthouse"} + allowFromLighthouse.succeed("ping -c10 10.0.100.2") + allowAny.succeed("ping -c10 10.0.100.3") + + # allowToLighthouse can ping allowAny if allowAny pings it first + allowAny.succeed("ping -c3 10.0.100.4") + allowToLighthouse.succeed("ping -c3 10.0.100.2") + + # block allowToLighthouse <-> allowAny, and allowAny <-> allowToLighthouse should still work. + ${blockTrafficBetween "allowAny" "allowToLighthouse"} + allowAny.succeed("ping -c10 10.0.100.4") + allowToLighthouse.succeed("ping -c10 10.0.100.2") + ${allowTrafficBetween "allowAny" "allowToLighthouse"} + allowAny.succeed("ping -c10 10.0.100.4") + allowToLighthouse.succeed("ping -c10 10.0.100.2") + + # block lighthouse <-> allowFromLighthouse and allowAny <-> allowFromLighthouse; allowFromLighthouse won't get to allowAny + ${blockTrafficBetween "allowFromLighthouse" "lighthouse"} + ${blockTrafficBetween "allowFromLighthouse" "allowAny"} + allowFromLighthouse.fail("ping -c3 10.0.100.2") + ${allowTrafficBetween "allowFromLighthouse" "lighthouse"} + ${allowTrafficBetween "allowFromLighthouse" "allowAny"} + allowFromLighthouse.succeed("ping -c3 10.0.100.2") + + # block lighthouse <-> allowAny, allowAny <-> allowFromLighthouse, and allowAny <-> allowToLighthouse; it won't get to allowFromLighthouse or allowToLighthouse + ${blockTrafficBetween "allowAny" "lighthouse"} + ${blockTrafficBetween "allowAny" "allowFromLighthouse"} + ${blockTrafficBetween "allowAny" "allowToLighthouse"} + allowFromLighthouse.fail("ping -c3 10.0.100.2") + allowAny.fail("ping -c3 10.0.100.3") + allowAny.fail("ping -c3 10.0.100.4") + ${allowTrafficBetween "allowAny" "lighthouse"} + ${allowTrafficBetween "allowAny" "allowFromLighthouse"} + ${allowTrafficBetween "allowAny" "allowToLighthouse"} + allowFromLighthouse.succeed("ping -c3 10.0.100.2") + allowAny.succeed("ping -c3 10.0.100.3") + allowAny.succeed("ping -c3 10.0.100.4") + + # block lighthouse <-> allowToLighthouse and allowToLighthouse <-> allowAny; it won't get to allowAny + ${blockTrafficBetween "allowToLighthouse" "lighthouse"} + ${blockTrafficBetween "allowToLighthouse" "allowAny"} + allowAny.fail("ping -c3 10.0.100.4") + allowToLighthouse.fail("ping -c3 10.0.100.2") + ${allowTrafficBetween "allowToLighthouse" "lighthouse"} + ${allowTrafficBetween "allowToLighthouse" "allowAny"} + allowAny.succeed("ping -c3 10.0.100.4") + allowToLighthouse.succeed("ping -c3 10.0.100.2") ''; }) diff --git a/nixos/tests/opensearch.nix b/nixos/tests/opensearch.nix new file mode 100644 index 0000000000000..c0caf950cb9c9 --- /dev/null +++ b/nixos/tests/opensearch.nix @@ -0,0 +1,52 @@ +let + opensearchTest = + import ./make-test-python.nix ( + { pkgs, lib, extraSettings ? {} }: { + name = "opensearch"; + meta.maintainers = with pkgs.lib.maintainers; [ shyim ]; + + nodes.machine = lib.mkMerge [ + { + virtualisation.memorySize = 2048; + services.opensearch.enable = true; + } + extraSettings + ]; + + testScript = '' + machine.start() + machine.wait_for_unit("opensearch.service") + machine.wait_for_open_port(9200) + + machine.succeed( + "curl --fail localhost:9200" + ) + ''; + }); +in +{ + opensearch = opensearchTest {}; + opensearchCustomPathAndUser = opensearchTest { + extraSettings = { + services.opensearch.dataDir = "/var/opensearch_test"; + services.opensearch.user = "open_search"; + services.opensearch.group = "open_search"; + system.activationScripts.createDirectory = { + text = '' + mkdir -p "/var/opensearch_test" + chown open_search:open_search /var/opensearch_test + chmod 0700 /var/opensearch_test + ''; + deps = [ "users" "groups" ]; + }; + users = { + groups.open_search = {}; + users.open_search = { + description = "OpenSearch daemon user"; + group = "open_search"; + isSystemUser = true; + }; + }; + }; + }; +} diff --git a/nixos/tests/pass-secret-service.nix b/nixos/tests/pass-secret-service.nix index a85a508bfe16b..e0dddf0ad29e2 100644 --- a/nixos/tests/pass-secret-service.nix +++ b/nixos/tests/pass-secret-service.nix @@ -1,6 +1,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { name = "pass-secret-service"; - meta.maintainers = with lib; [ aidalgol ]; + meta.maintainers = [ lib.maintainers.aidalgol ]; nodes.machine = { nodes, pkgs, ... }: { diff --git a/nixos/tests/pgadmin4-standalone.nix b/nixos/tests/pgadmin4-standalone.nix deleted file mode 100644 index 5aa17fcb5bb9d..0000000000000 --- a/nixos/tests/pgadmin4-standalone.nix +++ /dev/null @@ -1,43 +0,0 @@ -import ./make-test-python.nix ({ pkgs, lib, ... }: - # This is separate from pgadmin4 since we don't want both running at once - - { - name = "pgadmin4-standalone"; - meta.maintainers = with lib.maintainers; [ mkg20001 ]; - - nodes.machine = { pkgs, ... }: { - environment.systemPackages = with pkgs; [ - curl - ]; - - services.postgresql = { - enable = true; - - authentication = '' - host all all localhost trust - ''; - - ensureUsers = [ - { - name = "postgres"; - ensurePermissions = { - "DATABASE \"postgres\"" = "ALL PRIVILEGES"; - }; - } - ]; - }; - - services.pgadmin = { - enable = true; - initialEmail = "bruh@localhost.de"; - initialPasswordFile = pkgs.writeText "pw" "bruh2012!"; - }; - }; - - testScript = '' - machine.wait_for_unit("postgresql") - machine.wait_for_unit("pgadmin") - - machine.wait_until_succeeds("curl -s localhost:5050") - ''; - }) diff --git a/nixos/tests/pgadmin4.nix b/nixos/tests/pgadmin4.nix index 2a2b5aaa2841d..6a9ce6ceae298 100644 --- a/nixos/tests/pgadmin4.nix +++ b/nixos/tests/pgadmin4.nix @@ -1,133 +1,57 @@ -import ./make-test-python.nix ({ pkgs, lib, buildDeps ? [ ], pythonEnv ? [ ], ... }: - - /* - This test suite replaces the typical pytestCheckHook function in python - packages. Pgadmin4 test suite needs a running and configured postgresql - server. This is why this test exists. - - To not repeat all the python dependencies needed, this test is called directly - from the pgadmin4 derivation, which also passes the currently - used propagatedBuildInputs and any python overrides. - - Unfortunately, there doesn't seem to be an easy way to otherwise include - the needed packages here. - - Due the the needed parameters a direct call to "nixosTests.pgadmin4" fails - and needs to be called as "pgadmin4.tests" - - */ - - let - pgadmin4SrcDir = "/pgadmin"; - pgadmin4Dir = "/var/lib/pgadmin"; - pgadmin4LogDir = "/var/log/pgadmin"; - - in - { - name = "pgadmin4"; - meta.maintainers = with lib.maintainers; [ gador ]; - - nodes.machine = { pkgs, ... }: { - imports = [ ./common/x11.nix ]; - # needed because pgadmin 6.8 will fail, if those dependencies get updated - nixpkgs.overlays = [ - (self: super: { - pythonPackages = pythonEnv; - }) +import ./make-test-python.nix ({ pkgs, lib, ... }: + +{ + name = "pgadmin4"; + meta.maintainers = with lib.maintainers; [ mkg20001 gador ]; + + nodes.machine = { pkgs, ... }: { + + imports = [ ./common/user-account.nix ]; + + environment.systemPackages = with pkgs; [ + curl + pgadmin4-desktopmode + ]; + + services.postgresql = { + enable = true; + authentication = '' + host all all localhost trust + ''; + ensureUsers = [ + { + name = "postgres"; + ensurePermissions = { + "DATABASE \"postgres\"" = "ALL PRIVILEGES"; + }; + } ]; + }; - environment.systemPackages = with pkgs; [ - pgadmin4 - postgresql - chromedriver - chromium - # include the same packages as in pgadmin minus speaklater3 - (python3.withPackages - (ps: buildDeps ++ - [ - # test suite package requirements - pythonPackages.testscenarios - pythonPackages.selenium - ]) - ) - ]; - services.postgresql = { - enable = true; - authentication = '' - host all all localhost trust - ''; - ensureUsers = [ - { - name = "postgres"; - ensurePermissions = { - "DATABASE \"postgres\"" = "ALL PRIVILEGES"; - }; - } - ]; - }; + services.pgadmin = { + port = 5051; + enable = true; + initialEmail = "bruh@localhost.de"; + initialPasswordFile = pkgs.writeText "pw" "bruh2012!"; }; + }; - testScript = '' + testScript = '' + with subtest("Check pgadmin module"): machine.wait_for_unit("postgresql") - - # pgadmin4 needs its data and log directories - machine.succeed( - "mkdir -p ${pgadmin4Dir} \ - && mkdir -p ${pgadmin4LogDir} \ - && mkdir -p ${pgadmin4SrcDir}" - ) - - machine.succeed( - "tar xvzf ${pkgs.pgadmin4.src} -C ${pgadmin4SrcDir}" - ) - - machine.wait_for_file("${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/README.md") - - # set paths and config for tests - machine.succeed( - "cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version} \ - && cp -v web/regression/test_config.json.in web/regression/test_config.json \ - && sed -i 's|PostgreSQL 9.4|PostgreSQL|' web/regression/test_config.json \ - && sed -i 's|/opt/PostgreSQL/9.4/bin/|${pkgs.postgresql}/bin|' web/regression/test_config.json \ - && sed -i 's|\"headless_chrome\": false|\"headless_chrome\": true|' web/regression/test_config.json" - ) - - # adapt chrome config to run within a sandbox without GUI - # see https://stackoverflow.com/questions/50642308/webdriverexception-unknown-error-devtoolsactiveport-file-doesnt-exist-while-t#50642913 - # add chrome binary path. use spaces to satisfy python indention (tabs throw an error) - # this works for selenium 3 (currently used), but will need to be updated - # to work with "from selenium.webdriver.chrome.service import Service" in selenium 4 - machine.succeed( - "cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version} \ - && sed -i '\|options.add_argument(\"--disable-infobars\")|a \ \ \ \ \ \ \ \ options.binary_location = \"${pkgs.chromium}/bin/chromium\"' web/regression/runtests.py \ - && sed -i '\|options.add_argument(\"--no-sandbox\")|a \ \ \ \ \ \ \ \ options.add_argument(\"--headless\")' web/regression/runtests.py \ - && sed -i '\|options.add_argument(\"--disable-infobars\")|a \ \ \ \ \ \ \ \ options.add_argument(\"--disable-dev-shm-usage\")' web/regression/runtests.py \ - && sed -i 's|(chrome_options=options)|(executable_path=\"${pkgs.chromedriver}/bin/chromedriver\", chrome_options=options)|' web/regression/runtests.py \ - && sed -i 's|driver_local.maximize_window()||' web/regression/runtests.py" - ) - - # Don't bother to test LDAP or kerberos authentication - with subtest("run browser test"): - machine.succeed( - 'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \ - && python regression/runtests.py \ - --pkg browser \ - --exclude browser.tests.test_ldap_login.LDAPLoginTestCase,browser.tests.test_ldap_login,browser.tests.test_kerberos_with_mocking' - ) - - # fontconfig is necessary for chromium to run - # https://github.com/NixOS/nixpkgs/issues/136207 - with subtest("run feature test"): - machine.succeed( - 'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \ - && export FONTCONFIG_FILE=${pkgs.makeFontsConf { fontDirectories = [];}} \ - && python regression/runtests.py --pkg feature_tests' - ) - - with subtest("run resql test"): - machine.succeed( - 'cd ${pgadmin4SrcDir}/pgadmin4-${pkgs.pgadmin4.version}/web \ - && python regression/runtests.py --pkg resql' - ) - ''; - }) + machine.wait_for_unit("pgadmin") + machine.wait_until_succeeds("curl -s localhost:5051") + machine.wait_until_succeeds("curl -s localhost:5051/login | grep \"<title>pgAdmin 4</title>\" > /dev/null") + + # pgadmin4 module saves the configuration to /etc/pgadmin/config_system.py + # pgadmin4-desktopmode tries to read that as well. This normally fails with a PermissionError, as the config file + # is owned by the user of the pgadmin module. With the check-system-config-dir.patch this will just throw a warning + # but will continue and not read the file. + # If we run pgadmin4-desktopmode as root (something one really shouldn't do), it can read the config file and fail, + # because of the wrong config for desktopmode. + with subtest("Check pgadmin standalone desktop mode"): + machine.execute("sudo -u alice pgadmin4 >&2 &", timeout=60) + machine.wait_until_succeeds("curl -s localhost:5050") + machine.wait_until_succeeds("curl -s localhost:5050/browser/ | grep \"<title>pgAdmin 4</title>\" > /dev/null") + ''; +}) diff --git a/nixos/tests/podman/default.nix b/nixos/tests/podman/default.nix index c2ea399d65af3..69397197775f8 100644 --- a/nixos/tests/podman/default.nix +++ b/nixos/tests/podman/default.nix @@ -6,7 +6,10 @@ import ../make-test-python.nix ( }; nodes = { - podman = { pkgs, ... }: { + rootful = { pkgs, ... }: { + virtualisation.podman.enable = true; + }; + rootless = { pkgs, ... }: { virtualisation.podman.enable = true; users.users.alice = { @@ -49,107 +52,114 @@ import ../make-test-python.nix ( return f"su {user} -l -c {cmd}" - podman.wait_for_unit("sockets.target") + rootful.wait_for_unit("sockets.target") + rootless.wait_for_unit("sockets.target") dns.wait_for_unit("sockets.target") docker.wait_for_unit("sockets.target") start_all() with subtest("Run container as root with runc"): - podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg") - podman.succeed( + rootful.succeed("tar cv --files-from /dev/null | podman import - scratchimg") + rootful.succeed( "podman run --runtime=runc -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10" ) - podman.succeed("podman ps | grep sleeping") - podman.succeed("podman stop sleeping") - podman.succeed("podman rm sleeping") + rootful.succeed("podman ps | grep sleeping") + rootful.succeed("podman stop sleeping") + rootful.succeed("podman rm sleeping") with subtest("Run container as root with crun"): - podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg") - podman.succeed( + rootful.succeed("tar cv --files-from /dev/null | podman import - scratchimg") + rootful.succeed( "podman run --runtime=crun -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10" ) - podman.succeed("podman ps | grep sleeping") - podman.succeed("podman stop sleeping") - podman.succeed("podman rm sleeping") + rootful.succeed("podman ps | grep sleeping") + rootful.succeed("podman stop sleeping") + rootful.succeed("podman rm sleeping") with subtest("Run container as root with the default backend"): - podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg") - podman.succeed( + rootful.succeed("tar cv --files-from /dev/null | podman import - scratchimg") + rootful.succeed( "podman run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10" ) - podman.succeed("podman ps | grep sleeping") - podman.succeed("podman stop sleeping") - podman.succeed("podman rm sleeping") + rootful.succeed("podman ps | grep sleeping") + rootful.succeed("podman stop sleeping") + rootful.succeed("podman rm sleeping") # start systemd session for rootless - podman.succeed("loginctl enable-linger alice") - podman.succeed(su_cmd("whoami")) - podman.sleep(1) + rootless.succeed("loginctl enable-linger alice") + rootless.succeed(su_cmd("whoami")) + rootless.sleep(1) with subtest("Run container rootless with runc"): - podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg")) - podman.succeed( + rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg")) + rootless.succeed( su_cmd( "podman run --runtime=runc -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10" ) ) - podman.succeed(su_cmd("podman ps | grep sleeping")) - podman.succeed(su_cmd("podman stop sleeping")) - podman.succeed(su_cmd("podman rm sleeping")) + rootless.succeed(su_cmd("podman ps | grep sleeping")) + rootless.succeed(su_cmd("podman stop sleeping")) + rootless.succeed(su_cmd("podman rm sleeping")) with subtest("Run container rootless with crun"): - podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg")) - podman.succeed( + rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg")) + rootless.succeed( su_cmd( "podman run --runtime=crun -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10" ) ) - podman.succeed(su_cmd("podman ps | grep sleeping")) - podman.succeed(su_cmd("podman stop sleeping")) - podman.succeed(su_cmd("podman rm sleeping")) + rootless.succeed(su_cmd("podman ps | grep sleeping")) + rootless.succeed(su_cmd("podman stop sleeping")) + rootless.succeed(su_cmd("podman rm sleeping")) with subtest("Run container rootless with the default backend"): - podman.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg")) - podman.succeed( + rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg")) + rootless.succeed( su_cmd( "podman run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10" ) ) - podman.succeed(su_cmd("podman ps | grep sleeping")) - podman.succeed(su_cmd("podman stop sleeping")) - podman.succeed(su_cmd("podman rm sleeping")) + rootless.succeed(su_cmd("podman ps | grep sleeping")) + rootless.succeed(su_cmd("podman stop sleeping")) + rootless.succeed(su_cmd("podman rm sleeping")) + + with subtest("rootlessport"): + rootless.succeed(su_cmd("tar cv --files-from /dev/null | podman import - scratchimg")) + rootless.succeed( + su_cmd( + "podman run -d -p 9000:8888 --name=rootlessport -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin -w ${pkgs.writeTextDir "index.html" "<h1>Testing</h1>"} scratchimg ${pkgs.python3}/bin/python -m http.server 8888" + ) + ) + rootless.succeed(su_cmd("podman ps | grep rootlessport")) + rootless.wait_until_succeeds(su_cmd("${pkgs.curl}/bin/curl localhost:9000 | grep Testing")) + rootless.succeed(su_cmd("podman stop rootlessport")) + rootless.succeed(su_cmd("podman rm rootlessport")) with subtest("Run container with init"): - podman.succeed( + rootful.succeed( "tar cv -C ${pkgs.pkgsStatic.busybox} . | podman import - busybox" ) - pid = podman.succeed("podman run --rm busybox readlink /proc/self").strip() + pid = rootful.succeed("podman run --rm busybox readlink /proc/self").strip() assert pid == "1" - pid = podman.succeed("podman run --rm --init busybox readlink /proc/self").strip() + pid = rootful.succeed("podman run --rm --init busybox readlink /proc/self").strip() assert pid == "2" with subtest("aardvark-dns"): - dns.succeed("tar cv --files-from /dev/null | podman import - scratchimg") - dns.succeed( - "podman run -d --name=webserver -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin -w ${pkgs.writeTextDir "index.html" "<h1>Hi</h1>"} scratchimg ${pkgs.python3}/bin/python -m http.server 8000" - ) - dns.succeed("podman ps | grep webserver") - dns.succeed(""" - for i in `seq 0 120`; do - podman run --rm --name=client -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg ${pkgs.curl}/bin/curl http://webserver:8000 >/dev/console \ - && exit 0 - sleep 0.5 - done - exit 1 - """) - dns.succeed("podman stop webserver") - dns.succeed("podman rm webserver") + dns.succeed("tar cv --files-from /dev/null | podman import - scratchimg") + dns.succeed( + "podman run -d --name=webserver -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin -w ${pkgs.writeTextDir "index.html" "<h1>Testing</h1>"} scratchimg ${pkgs.python3}/bin/python -m http.server 8000" + ) + dns.succeed("podman ps | grep webserver") + dns.wait_until_succeeds( + "podman run --rm --name=client -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg ${pkgs.curl}/bin/curl http://webserver:8000 | grep Testing" + ) + dns.succeed("podman stop webserver") + dns.succeed("podman rm webserver") with subtest("A podman member can use the docker cli"): docker.succeed(su_cmd("docker version")) with subtest("Run container via docker cli"): - docker.succeed("docker network create default") docker.succeed("tar cv --files-from /dev/null | podman import - scratchimg") docker.succeed( "docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin localhost/scratchimg /bin/sleep 10" @@ -158,7 +168,6 @@ import ../make-test-python.nix ( docker.succeed("podman ps | grep sleeping") docker.succeed("docker stop sleeping") docker.succeed("docker rm sleeping") - docker.succeed("docker network rm default") with subtest("A podman non-member can not use the docker cli"): docker.fail(su_cmd("docker version", user="mallory")) diff --git a/nixos/tests/podman/tls-ghostunnel.nix b/nixos/tests/podman/tls-ghostunnel.nix index 268a55701ccf2..52c31dc21f101 100644 --- a/nixos/tests/podman/tls-ghostunnel.nix +++ b/nixos/tests/podman/tls-ghostunnel.nix @@ -113,9 +113,6 @@ import ../make-test-python.nix ( podman.wait_for_unit("sockets.target") podman.wait_for_unit("ghostunnel-server-podman-socket.service") - with subtest("Create default network"): - podman.succeed("docker network create default") - with subtest("Root docker cli also works"): podman.succeed("docker version") diff --git a/nixos/tests/predictable-interface-names.nix b/nixos/tests/predictable-interface-names.nix index 08773120bc127..684df9c39246c 100644 --- a/nixos/tests/predictable-interface-names.nix +++ b/nixos/tests/predictable-interface-names.nix @@ -13,7 +13,7 @@ in pkgs.lib.listToAttrs (builtins.map ({ predictable, withNetworkd }: { name = pkgs.lib.optionalString (!predictable) "un" + "predictable" + pkgs.lib.optionalString withNetworkd "Networkd"; value = makeTest { - name = "${if predictable then "" else "un"}predictableInterfaceNames${if withNetworkd then "-with-networkd" else ""}"; + name = "${pkgs.lib.optionalString (!predictable) "un"}predictableInterfaceNames${pkgs.lib.optionalString withNetworkd "-with-networkd"}"; meta = {}; nodes.machine = { lib, ... }: { diff --git a/nixos/tests/quake3.nix b/nixos/tests/quake3.nix index 82af1af463d03..ef5fcc41476bc 100644 --- a/nixos/tests/quake3.nix +++ b/nixos/tests/quake3.nix @@ -1,4 +1,4 @@ -import ./make-test-python.nix ({ pkgs, ...} : +import ./make-test-python.nix ({ pkgs, lib, ...} : let @@ -11,9 +11,9 @@ let }; # Only allow the demo data to be used (only if it's unfreeRedistributable). - unfreePredicate = pkg: with pkgs.lib; let + unfreePredicate = pkg: with lib; let allowPackageNames = [ "quake3-demodata" "quake3-pointrelease" ]; - allowLicenses = [ pkgs.lib.licenses.unfreeRedistributable ]; + allowLicenses = [ lib.licenses.unfreeRedistributable ]; in elem pkg.pname allowPackageNames && elem (pkg.meta.license or null) allowLicenses; @@ -31,7 +31,7 @@ in rec { name = "quake3"; - meta = with pkgs.stdenv.lib.maintainers; { + meta = with lib.maintainers; { maintainers = [ domenkozar eelco ]; }; diff --git a/nixos/tests/systemd-credentials-tpm2.nix b/nixos/tests/systemd-credentials-tpm2.nix new file mode 100644 index 0000000000000..d2dc1fd7b615b --- /dev/null +++ b/nixos/tests/systemd-credentials-tpm2.nix @@ -0,0 +1,124 @@ +import ./make-test-python.nix ({ lib, pkgs, system, ... }: + +let + tpmSocketPath = "/tmp/swtpm-sock"; + tpmDeviceModels = { + x86_64-linux = "tpm-tis"; + aarch64-linux = "tpm-tis-device"; + }; +in + +{ + name = "systemd-credentials-tpm2"; + + meta = { + maintainers = with pkgs.lib.maintainers; [ tmarkus ]; + }; + + nodes.machine = { pkgs, ... }: { + virtualisation = { + qemu.options = [ + "-chardev socket,id=chrtpm,path=${tpmSocketPath}" + "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm" + "-device ${tpmDeviceModels.${system}},tpmdev=tpm_dev_0" + ]; + }; + + boot.initrd.availableKernelModules = [ "tpm_tis" ]; + + environment.systemPackages = with pkgs; [ diffutils ]; + }; + + testScript = '' + import subprocess + from tempfile import TemporaryDirectory + + # From systemd-initrd-luks-tpm2.nix + class Tpm: + def __init__(self): + self.state_dir = TemporaryDirectory() + self.start() + + def start(self): + self.proc = subprocess.Popen(["${pkgs.swtpm}/bin/swtpm", + "socket", + "--tpmstate", f"dir={self.state_dir.name}", + "--ctrl", "type=unixio,path=${tpmSocketPath}", + "--tpm2", + ]) + + # Check whether starting swtpm failed + try: + exit_code = self.proc.wait(timeout=0.2) + if exit_code is not None and exit_code != 0: + raise Exception("failed to start swtpm") + except subprocess.TimeoutExpired: + pass + + """Check whether the swtpm process exited due to an error""" + def check(self): + exit_code = self.proc.poll() + if exit_code is not None and exit_code != 0: + raise Exception("swtpm process died") + + CRED_NAME = "testkey" + CRED_RAW_FILE = f"/root/{CRED_NAME}" + CRED_FILE = f"/root/{CRED_NAME}.cred" + + def systemd_run(machine, cmd): + machine.log(f"Executing command (via systemd-run): \"{cmd}\"") + + (status, out) = machine.execute( " ".join([ + "systemd-run", + "--service-type=exec", + "--quiet", + "--wait", + "-E PATH=\"$PATH\"", + "-p StandardOutput=journal", + "-p StandardError=journal", + f"-p LoadCredentialEncrypted={CRED_NAME}:{CRED_FILE}", + f"$SHELL -c '{cmd}'" + ]) ) + + if status != 0: + raise Exception(f"systemd_run failed (status {status})") + + machine.log("systemd-run finished successfully") + + tpm = Tpm() + + @polling_condition + def swtpm_running(): + tpm.check() + + machine.wait_for_unit("multi-user.target") + + with subtest("Check whether TPM device exists"): + machine.succeed("test -e /dev/tpm0") + machine.succeed("test -e /dev/tpmrm0") + + with subtest("Check whether systemd-creds detects TPM2 correctly"): + cmd = "systemd-creds has-tpm2" + machine.log(f"Running \"{cmd}\"") + (status, _) = machine.execute(cmd) + + # Check exit code equals 0 or 1 (1 means firmware support is missing, which is OK here) + if status != 0 and status != 1: + raise Exception("systemd-creds failed to detect TPM2") + + with subtest("Encrypt credential using systemd-creds"): + machine.succeed(f"dd if=/dev/urandom of={CRED_RAW_FILE} bs=1k count=16") + machine.succeed(f"systemd-creds --with-key=host+tpm2 encrypt --name=testkey {CRED_RAW_FILE} {CRED_FILE}") + + with subtest("Write provided credential and check for equality"): + CRED_OUT_FILE = f"/root/{CRED_NAME}.out" + systemd_run(machine, f"systemd-creds cat testkey > {CRED_OUT_FILE}") + machine.succeed(f"cmp --silent -- {CRED_RAW_FILE} {CRED_OUT_FILE}") + + with subtest("Check whether systemd service can see credential in systemd-creds list"): + systemd_run(machine, f"systemd-creds list | grep {CRED_NAME}") + + with subtest("Check whether systemd service can access credential in $CREDENTIALS_DIRECTORY"): + systemd_run(machine, f"cmp --silent -- $CREDENTIALS_DIRECTORY/{CRED_NAME} {CRED_RAW_FILE}") + ''; +}) diff --git a/nixos/tests/systemd-cryptenroll.nix b/nixos/tests/systemd-cryptenroll.nix index 9ee2d280fbbea..055ae7d1681f2 100644 --- a/nixos/tests/systemd-cryptenroll.nix +++ b/nixos/tests/systemd-cryptenroll.nix @@ -2,7 +2,6 @@ import ./make-test-python.nix ({ pkgs, ... }: { name = "systemd-cryptenroll"; meta = with pkgs.lib.maintainers; { maintainers = [ ymatsiuk ]; - broken = true; # times out after two hours, details -> https://github.com/NixOS/nixpkgs/issues/167994 }; nodes.machine = { pkgs, lib, ... }: { diff --git a/nixos/tests/systemd-initrd-vconsole.nix b/nixos/tests/systemd-initrd-vconsole.nix new file mode 100644 index 0000000000000..b74df410c4224 --- /dev/null +++ b/nixos/tests/systemd-initrd-vconsole.nix @@ -0,0 +1,33 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: { + name = "systemd-initrd-vconsole"; + + nodes.machine = { pkgs, ... }: { + boot.kernelParams = [ "rd.systemd.unit=rescue.target" ]; + + boot.initrd.systemd = { + enable = true; + emergencyAccess = true; + }; + + console = { + earlySetup = true; + keyMap = "colemak"; + }; + }; + + testScript = '' + # Boot into rescue shell in initrd + machine.start() + machine.wait_for_console_text("Press Enter for maintenance") + machine.send_console("\n") + machine.wait_for_console_text("Logging in with home") + + # Check keymap + machine.send_console("(printf '%s to receive text: \\n' Ready && read text && echo \"$text\") </dev/tty1\n") + machine.wait_for_console_text("Ready to receive text:") + for key in "asdfjkl;\n": + machine.send_key(key) + machine.wait_for_console_text("arstneio") + machine.send_console("systemctl poweroff\n") + ''; +}) diff --git a/nixos/tests/systemd-repart.nix b/nixos/tests/systemd-repart.nix new file mode 100644 index 0000000000000..36de5d988fdb1 --- /dev/null +++ b/nixos/tests/systemd-repart.nix @@ -0,0 +1,134 @@ +{ system ? builtins.currentSystem +, config ? { } +, pkgs ? import ../.. { inherit system config; } +}: + +with import ../lib/testing-python.nix { inherit system pkgs; }; +with pkgs.lib; + +let + # A testScript fragment that prepares a disk with some empty, unpartitioned + # space. and uses it to boot the test with. Takes a single argument `machine` + # from which the diskImage is extraced. + useDiskImage = machine: '' + import os + import shutil + import subprocess + import tempfile + + tmp_disk_image = tempfile.NamedTemporaryFile() + + shutil.copyfile("${machine.system.build.diskImage}/nixos.img", tmp_disk_image.name) + + subprocess.run([ + "${pkgs.qemu}/bin/qemu-img", + "resize", + "-f", + "raw", + tmp_disk_image.name, + "+32M", + ]) + + # Fix the GPT table by moving the backup table to the end of the enlarged + # disk image. This is necessary because we increased the size of the disk + # before. The disk needs to be a raw disk because sgdisk can only run on + # raw images. + subprocess.run([ + "${pkgs.gptfdisk}/bin/sgdisk", + "--move-second-header", + tmp_disk_image.name, + ]) + + # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image. + os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name + ''; + + common = { config, pkgs, lib, ... }: { + virtualisation.useDefaultFilesystems = false; + virtualisation.fileSystems = { + "/" = { + device = "/dev/vda2"; + fsType = "ext4"; + }; + }; + + # systemd-repart operates on disks with a partition table. The qemu module, + # however, creates separate filesystem images without a partition table, so + # we have to create a disk image manually. + # + # This creates two partitions, an ESP mounted on /dev/vda1 and the root + # partition mounted on /dev/vda2 + system.build.diskImage = import ../lib/make-disk-image.nix { + inherit config pkgs lib; + # Use a raw format disk so that it can be resized before starting the + # test VM. + format = "raw"; + # Keep the image as small as possible but leave some room for changes. + bootSize = "32M"; + additionalSpace = "0M"; + # GPT with an EFI System Partition is the typical use case for + # systemd-repart because it does not support MBR. + partitionTableType = "efi"; + # We do not actually care much about the content of the partitions, so we + # do not need a bootloader installed. + installBootLoader = false; + # Improve determinism by not copying a channel. + copyChannel = false; + }; + }; +in +{ + basic = makeTest { + name = "systemd-repart"; + meta.maintainers = with maintainers; [ nikstur ]; + + nodes.machine = { config, pkgs, ... }: { + imports = [ common ]; + + boot.initrd.systemd.enable = true; + + boot.initrd.systemd.repart.enable = true; + systemd.repart.partitions = { + "10-root" = { + Type = "linux-generic"; + }; + }; + }; + + testScript = { nodes, ... }: '' + ${useDiskImage nodes.machine} + + machine.start() + machine.wait_for_unit("multi-user.target") + + systemd_repart_logs = machine.succeed("journalctl --boot --unit systemd-repart.service") + assert "Growing existing partition 1." in systemd_repart_logs + ''; + }; + + after-initrd = makeTest { + name = "systemd-repart-after-initrd"; + meta.maintainers = with maintainers; [ nikstur ]; + + nodes.machine = { config, pkgs, ... }: { + imports = [ common ]; + + systemd.repart.enable = true; + systemd.repart.partitions = { + "10-root" = { + Type = "linux-generic"; + }; + }; + }; + + testScript = { nodes, ... }: '' + ${useDiskImage nodes.machine} + + machine.start() + machine.wait_for_unit("multi-user.target") + + systemd_repart_logs = machine.succeed("journalctl --unit systemd-repart.service") + assert "Growing existing partition 1." in systemd_repart_logs + ''; + }; +} diff --git a/nixos/tests/wireguard/snakeoil-keys.nix b/nixos/tests/wireguard/snakeoil-keys.nix index 55ad582d40595..c979f0e0c8a96 100644 --- a/nixos/tests/wireguard/snakeoil-keys.nix +++ b/nixos/tests/wireguard/snakeoil-keys.nix @@ -6,6 +6,7 @@ peer1 = { privateKey = "uO8JVo/sanx2DOM0L9GUEtzKZ82RGkRnYgpaYc7iXmg="; - publicKey = "Ks9yRJIi/0vYgRmn14mIOQRwkcUGBujYINbMpik2SBI="; + # readFile'd keys may have trailing newlines, emulate this + publicKey = "Ks9yRJIi/0vYgRmn14mIOQRwkcUGBujYINbMpik2SBI=\n"; }; } diff --git a/nixos/tests/zram-generator.nix b/nixos/tests/zram-generator.nix index affa081bcc353..3407361d2824f 100644 --- a/nixos/tests/zram-generator.nix +++ b/nixos/tests/zram-generator.nix @@ -1,18 +1,24 @@ import ./make-test-python.nix { name = "zram-generator"; - nodes.machine = { pkgs, ... }: { - environment.etc."systemd/zram-generator.conf".text = '' - [zram0] - zram-size = ram / 2 - ''; - systemd.packages = [ pkgs.zram-generator ]; - systemd.services."systemd-zram-setup@".path = [ pkgs.util-linux ]; # for mkswap + nodes.machine = { ... }: { + zramSwap = { + enable = true; + priority = 10; + algorithm = "lz4"; + swapDevices = 2; + memoryPercent = 30; + memoryMax = 10 * 1024 * 1024; + }; }; testScript = '' machine.wait_for_unit("systemd-zram-setup@zram0.service") - assert "zram0" in machine.succeed("zramctl -n") - assert "zram0" in machine.succeed("swapon --show --noheadings") + machine.wait_for_unit("systemd-zram-setup@zram1.service") + zram = machine.succeed("zramctl --noheadings --raw") + swap = machine.succeed("swapon --show --noheadings") + for i in range(2): + assert f"/dev/zram{i} lz4 10M" in zram + assert f"/dev/zram{i} partition 10M" in swap ''; } |