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.
This commit is contained in:
Artemis Tosini
2026-01-22 14:10:16 -05:00
parent 2f1ce8900b
commit 94907eb37a
2 changed files with 76 additions and 0 deletions

View File

@@ -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;

View File

@@ -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
""")
'';
}