Unverified Commit bc0021ec authored by John Ericson's avatar John Ericson Committed by GitHub
Browse files

nixos/nix: Add support for running an unprivileged daemon (#491809)

parents 13ed9525 795c26dc
Loading
Loading
Loading
Loading
+118 −2
Original line number Diff line number Diff line
@@ -62,6 +62,101 @@ in
      ];
    })
    (lib.mkRemovedOptionModule [ "nix" "daemonNiceLevel" ] "Consider nix.daemonCPUSchedPolicy instead.")
    {
      # Unprivileged Nix daemon
      config = lib.mkIf (cfg.daemonUser != "root") {
        assertions = [
          {
            message = ''
              The Nix daemon cannot run as the root group when not running as the root user.
            '';
            assertion = cfg.daemonGroup != "root";
          }
          {
            message = ''
              Nix must have the `local-overlay-store` experimental feature when not running as the root user.
            '';
            assertion = lib.elem "local-overlay-store" cfg.settings.experimental-features;
          }
          {
            message = ''
              Nix must have the `auto-allocate-uids` experimental feature when not running as the root user.
            '';
            assertion = lib.elem "auto-allocate-uids" cfg.settings.experimental-features;
          }
        ];

        nix.settings = {
          sandbox = true;

          auto-allocate-uids = true;

          # No such group would exist within the sandbox, so chowning to it would fail
          build-users-group = "";

          # Default settings from Nix, we need to specify them here to use them in nix code though
          start-id = lib.mkDefault (832 * 1024 * 1024);
          id-count = lib.mkDefault (128 * 65536);
        };

        systemd.services.nix-daemon = {
          # Nix assumes it should use `daemon` if it isn't root, so we have to set `NIX_REMOTE` anyway
          environment.NIX_REMOTE = "local?use-roots-daemon=true";
          serviceConfig = {
            User = cfg.daemonUser;
            Group = cfg.daemonGroup;

            # Empty string needed to disable old Exec
            ExecStart = [
              ""
              "${nixPackage}/libexec/nix-nswrapper ${toString cfg.settings.start-id} ${toString cfg.settings.id-count} ${nixPackage}/bin/nix-daemon --daemon"
            ];
          };
        };

        # We can't remount rw while unprivileged
        boot.nixStoreMountOpts = [
          "nodev"
          "nosuid"
        ];

        users.users."${cfg.daemonUser}" = {
          subUidRanges = [
            {
              startUid = cfg.settings.start-id;
              count = cfg.settings.id-count;
            }
          ];
          subGidRanges = [
            {
              startGid = cfg.settings.start-id;
              count = cfg.settings.id-count;
            }
          ];
        };

        systemd.tmpfiles.rules = [
          "d /nix/store                 0755 ${config.nix.daemonUser} ${config.nix.daemonGroup} - -"
          "Z /nix/var                   0755 ${config.nix.daemonUser} ${config.nix.daemonGroup} - -"
          "d /nix/var/nix/builds        0755 ${config.nix.daemonUser} ${config.nix.daemonGroup} - 7d"
          "d /nix/var/nix/daemon-socket 0755 ${config.nix.daemonUser} ${config.nix.daemonGroup} - -"
        ];

        systemd.services.nix-roots-daemon = {
          serviceConfig.ExecStart = "${config.nix.package.out}/bin/nix --extra-experimental-features nix-command store roots-daemon";
        };
        systemd.sockets.nix-roots-daemon = {
          wantedBy = [
            "nix-daemon.service"
          ];
          listenStreams = [ "/nix/var/nix/gc-roots-socket/socket" ];
          unitConfig = {
            ConditionPathIsReadWrite = "/nix/var/nix/gc-roots-socket";
            RequiresMountsFor = "/nix/store";
          };
        };
      };
    }
  ];

  ###### interface
@@ -88,6 +183,24 @@ in
        '';
      };

      daemonUser = lib.mkOption {
        type = lib.types.str;
        default = "root";
        description = ''
          User to use to run the Nix daemon.
          If this is not "root" then the Nix daemon will set several settings to preserve functionality.
          When setting this option, you must also set `nix.daemonGroup`.
        '';
      };

      daemonGroup = lib.mkOption {
        type = lib.types.str;
        default = "root";
        description = ''
          Group to use to run the Nix daemon.
        '';
      };

      daemonCPUSchedPolicy = lib.mkOption {
        type = lib.types.enum [
          "other"
@@ -192,7 +305,8 @@ in

    systemd.packages = [ nixPackage ];

    systemd.tmpfiles.packages = [ nixPackage ];
    # The upstream Nix tmpfiles.d file assumes the daemon runs as root
    systemd.tmpfiles.packages = lib.mkIf (cfg.daemonUser == "root") [ nixPackage ];

    systemd.sockets.nix-daemon.wantedBy = [ "sockets.target" ];

@@ -200,7 +314,9 @@ in
      path = [
        nixPackage
        config.programs.ssh.package
      ];
      ]
      # For running "newuidmap"
      ++ lib.optional (cfg.daemonUser != "root") "/run/wrappers";

      environment =
        cfg.envVars
+1 −0
Original line number Diff line number Diff line
@@ -1099,6 +1099,7 @@ in
  nix-channel = pkgs.callPackage ../modules/config/nix-channel/test.nix { };
  nix-config = runTest ./nix-config.nix;
  nix-daemon-firewall = runTest ./nix-daemon-firewall.nix;
  nix-daemon-unprivileged = runTest ./nix-daemon-unprivileged.nix;
  nix-ld = runTest ./nix-ld.nix;
  nix-misc = handleTest ./nix/misc.nix { };
  nix-required-mounts = runTest ./nix-required-mounts;
+38 −0
Original line number Diff line number Diff line
{ lib, pkgs, ... }:
{
  name = "nix-daemon-unprivileged";
  meta.maintainers = with lib.maintainers; [ artemist ];

  nodes.machine = {
    users.groups.nix-daemon = { };
    users.users.nix-daemon = {
      isSystemUser = true;
      group = "nix-daemon";
    };

    nix = {
      package = pkgs.nixVersions.git;
      daemonUser = "nix-daemon";
      daemonGroup = "nix-daemon";
      settings.experimental-features = [
        "local-overlay-store"
        "auto-allocate-uids"
      ];
    };

    # Easiest way to get a file onto the machine
    environment.etc."test.nix".text = ''
      derivation {
        name = "test";
        builder = "/bin/sh";
        args = [ "-c" "echo succeeded > $out" ];
        system = "${pkgs.stdenv.hostPlatform.system}";
      }
    '';
  };
  testScript = ''
    start_all()
    machine.wait_for_unit("sockets.target")
    machine.succeed("NIX_REMOTE=daemon nix-build /etc/test.nix")
  '';
}