From 94907eb37a9eb14ab15268dd106919f070b2bf74 Mon Sep 17 00:00:00 2001 From: Artemis Tosini Date: Thu, 22 Jan 2026 14:10:16 -0500 Subject: [PATCH] Add new VM test with unprivileged daemon user All current NixOS functional VM tests have a daemon as root with the tests running as different unprivileged users. The new `functional_unprivileged-daemon` test runs the daemon and the nix functional tests as separate unprivileged users. Users may want to run an unprivileged daemon on non-NixOS systems where the administrator does not fully trust nix, but multiple users want to use nix for their own purposes. It could also be useful in concert with an overlay-mount store, where the nix daemon cannot modify the derivations used by the system, and thus a nix vulnerability would not lead to root code execution. --- tests/nixos/default.nix | 2 + .../nixos/functional/unprivileged-daemon.nix | 74 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 tests/nixos/functional/unprivileged-daemon.nix diff --git a/tests/nixos/default.nix b/tests/nixos/default.nix index edfa4124f..fd7ea61e3 100644 --- a/tests/nixos/default.nix +++ b/tests/nixos/default.nix @@ -197,6 +197,8 @@ in functional_symlinked-home = runNixOSTest ./functional/symlinked-home.nix; + functional_unprivileged-daemon = runNixOSTest ./functional/unprivileged-daemon.nix; + user-sandboxing = runNixOSTest ./user-sandboxing; s3-binary-cache-store = runNixOSTest ./s3-binary-cache-store.nix; diff --git a/tests/nixos/functional/unprivileged-daemon.nix b/tests/nixos/functional/unprivileged-daemon.nix new file mode 100644 index 000000000..c0dfdf0c4 --- /dev/null +++ b/tests/nixos/functional/unprivileged-daemon.nix @@ -0,0 +1,74 @@ +{ lib, ... }: +{ + name = "functional-tests-on-nixos_unprivileged-daemon"; + + imports = [ ./common.nix ]; + + nodes.machine = + { config, pkgs, ... }: + { + users.groups.nix-daemon = { }; + users.users.nix-daemon = { + isSystemUser = true; + group = "nix-daemon"; + }; + users.users.alice = { + isNormalUser = true; + }; + + # nix.enable makes a lot of assumptions that only make sense as root, so set up nix ourselves. + # Based on nix-daemon.nix from nixpkgs and other references to `config.nix.enable` in nixpkgs. + nix.enable = false; + + # Unprivileged nix daemon cannot remount store read/write, so never make it read-only in the first place. + boot.readOnlyNixStore = false; + + environment.systemPackages = [ config.nix.package ]; + # nix normally defaults to local if running as root, we want root to use the daemon as well. + environment.variables.NIX_REMOTE = "daemon"; + + systemd.packages = [ config.nix.package ]; + + systemd.services.nix-daemon = { + path = [ + config.nix.package + config.programs.ssh.package + ]; + environment = { + CURL_CA_BUNDLE = config.security.pki.caBundle; + NIX_REMOTE = "local?ignore-gc-delete-failure=true"; + NIX_CONFIG = "extra-experimental-features = local-overlay-store"; + }; + serviceConfig = { + User = "nix-daemon"; + ExecStartPre = "${pkgs.writeShellScript "nix-load-db" '' + if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then + ${config.nix.package.out}/bin/nix-store --load-db < ''${BASH_REMATCH[1]} + fi + ''}"; + }; + }; + + systemd.sockets.nix-daemon.wantedBy = [ "sockets.target" ]; + + systemd.tmpfiles.rules = [ + "d /nix/.rw-store 0755 nix-daemon nix-daemon - -" + "d /nix/store 0755 nix-daemon nix-daemon - -" + "d /nix/store/.links 0755 nix-daemon nix-daemon - -" + "d /nix/var 0755 nix-daemon nix-daemon - -" + "d /nix/var/nix 0755 nix-daemon nix-daemon - -" + "d /nix/var/nix/builds 0755 nix-daemon nix-daemon - -" + "d /nix/var/nix/daemon-socket 0755 nix-daemon nix-daemon - -" + "d /nix/var/nix/gcroots 0755 nix-daemon nix-daemon - -" + "L+ /nix/var/nix/gcroots/booted-system 0755 nix-daemon nix-daemon - /run/booted-system" + "d /var/empty/.cache/nix 0755 nix-daemon nix-daemon - -" + ]; + }; + + testScript = '' + machine.wait_for_unit("multi-user.target") + machine.succeed(""" + su --login --command "run-test-suite" alice >&2 + """) + ''; +}