summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorLeorize <leorize+oss@disroot.org>2022-12-08 02:49:12 -0600
committerLeorize <leorize+oss@disroot.org>2022-12-09 12:10:51 -0600
commit05420f34cf7b8eb6acb1e18d918b1a9a78762473 (patch)
treec805229f040ae4b2b9487742e570029882cb7663 /nixos
parent0cc87ab901909ae949d22caf071e446bed83e47a (diff)
downloadnixpkgs-05420f34cf7b8eb6acb1e18d918b1a9a78762473.tar
nixpkgs-05420f34cf7b8eb6acb1e18d918b1a9a78762473.tar.gz
nixpkgs-05420f34cf7b8eb6acb1e18d918b1a9a78762473.tar.bz2
nixpkgs-05420f34cf7b8eb6acb1e18d918b1a9a78762473.tar.lz
nixpkgs-05420f34cf7b8eb6acb1e18d918b1a9a78762473.tar.xz
nixpkgs-05420f34cf7b8eb6acb1e18d918b1a9a78762473.tar.zst
nixpkgs-05420f34cf7b8eb6acb1e18d918b1a9a78762473.zip
nixos: add systemd-homed support
As a start, it's not very configurable, but works pretty well.
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/security/pam.nix27
-rw-r--r--nixos/modules/system/boot/systemd.nix2
-rw-r--r--nixos/modules/system/boot/systemd/homed.nix43
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/systemd-homed.nix99
6 files changed, 169 insertions, 4 deletions
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 587cb2319e9..3196bfad833 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1276,6 +1276,7 @@
   ./system/boot/systemd/tmpfiles.nix
   ./system/boot/systemd/user.nix
   ./system/boot/systemd/userdbd.nix
+  ./system/boot/systemd/homed.nix
   ./system/boot/timesyncd.nix
   ./system/boot/tmp.nix
   ./system/boot/uvesafb.nix
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index 21e1749d850..ccc48419827 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -488,6 +488,9 @@ let
             account [success=ok ignore=ignore default=die] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so
             account [success=ok default=ignore] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_admin.so
           '' +
+          optionalString config.services.homed.enable ''
+            account sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
+          '' +
           # The required pam_unix.so module has to come after all the sufficient modules
           # because otherwise, the account lookup will fail if the user does not exist
           # locally, for example with MySQL- or LDAP-auth.
@@ -541,8 +544,10 @@ let
           # after it succeeds. Certain modules need to run after pam_unix
           # prompts the user for password so we run it once with 'optional' at an
           # earlier point and it will run again with 'sufficient' further down.
-          # We use try_first_pass the second time to avoid prompting password twice
-          (optionalString (cfg.unixAuth &&
+          # We use try_first_pass the second time to avoid prompting password twice.
+          #
+          # The same principle applies to systemd-homed
+          (optionalString ((cfg.unixAuth || config.services.homed.enable) &&
             (config.security.pam.enableEcryptfs
               || config.security.pam.enableFscrypt
               || cfg.pamMount
@@ -553,7 +558,10 @@ let
               || cfg.failDelay.enable
               || cfg.duoSecurity.enable))
             (
-              ''
+              optionalString config.services.homed.enable ''
+                auth optional ${config.systemd.package}/lib/security/pam_systemd_home.so
+              '' +
+              optionalString cfg.unixAuth ''
                 auth optional pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth
               '' +
               optionalString config.security.pam.enableEcryptfs ''
@@ -584,6 +592,9 @@ let
                 auth required ${pkgs.duo-unix}/lib/security/pam_duo.so
               ''
             )) +
+          optionalString config.services.homed.enable ''
+            auth sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
+          '' +
           optionalString cfg.unixAuth ''
             auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth try_first_pass
           '' +
@@ -605,6 +616,10 @@ let
             auth required pam_deny.so
 
             # Password management.
+          '' +
+          optionalString config.services.homed.enable ''
+            password sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
+          '' + ''
             password sufficient pam_unix.so nullok sha512
           '' +
           optionalString config.security.pam.enableEcryptfs ''
@@ -650,6 +665,9 @@ let
           ++ optional (cfg.ttyAudit.enablePattern != null) "enable=${cfg.ttyAudit.enablePattern}"
           ++ optional (cfg.ttyAudit.disablePattern != null) "disable=${cfg.ttyAudit.disablePattern}"
           )) +
+          optionalString config.services.homed.enable ''
+            session required ${config.systemd.package}/lib/security/pam_systemd_home.so
+          '' +
           optionalString cfg.makeHomeDir ''
             session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=${config.security.pam.makeHomeDir.skelDirectory} umask=0077
           '' +
@@ -1345,6 +1363,9 @@ in
       '' +
       optionalString config.virtualisation.lxc.lxcfs.enable ''
         mr ${pkgs.lxc}/lib/security/pam_cgfs.so
+      '' +
+      optionalString config.services.homed.enable ''
+        mr ${config.systemd.package}/lib/security/pam_systemd_home.so
       '';
   };
 
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index e37ed853181..679a663362b 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -450,7 +450,7 @@ in
         (mkAfter [ "systemd" ])
       ]);
       group = (mkMerge [
-        (mkAfter [ "systemd" ])
+        (mkAfter [ "[success=merge] systemd" ]) # need merge so that NSS won't stop at file-based groups
       ]);
     };
 
diff --git a/nixos/modules/system/boot/systemd/homed.nix b/nixos/modules/system/boot/systemd/homed.nix
new file mode 100644
index 00000000000..403d1690124
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/homed.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.homed;
+in
+{
+  options.services.homed.enable = lib.mkEnableOption (lib.mdDoc ''
+    Enable systemd home area/user account manager
+  '');
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = config.services.nscd.enable;
+        message = "systemd-homed requires the use of systemd nss module. services.nscd.enable must be set to true,";
+      }
+    ];
+
+    systemd.additionalUpstreamSystemUnits = [
+      "systemd-homed.service"
+      "systemd-homed-activate.service"
+    ];
+
+    # This is mentioned in homed's [Install] section.
+    #
+    # While homed appears to work without it, it's probably better
+    # to follow upstream recommendations.
+    services.userdbd.enable = lib.mkDefault true;
+
+    systemd.services = {
+      systemd-homed = {
+        # These packages are required to manage encrypted volumes
+        path = config.system.fsPackages;
+        aliases = [ "dbus-org.freedesktop.home1.service" ];
+        wantedBy = [ "multi-user.target" ];
+      };
+
+      systemd-homed-activate = {
+        wantedBy = [ "systemd-homed.service" ];
+      };
+    };
+  };
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 31243f59bb9..1eab790887d 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -637,6 +637,7 @@ in {
   systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
   systemd-misc = handleTest ./systemd-misc.nix {};
   systemd-userdbd = handleTest ./systemd-userdbd.nix {};
+  systemd-homed = handleTest ./systemd-homed.nix {};
   tandoor-recipes = handleTest ./tandoor-recipes.nix {};
   taskserver = handleTest ./taskserver.nix {};
   tayga = handleTest ./tayga.nix {};
diff --git a/nixos/tests/systemd-homed.nix b/nixos/tests/systemd-homed.nix
new file mode 100644
index 00000000000..ecc92e98edd
--- /dev/null
+++ b/nixos/tests/systemd-homed.nix
@@ -0,0 +1,99 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  password = "foobar";
+  newPass = "barfoo";
+in
+{
+  name = "systemd-homed";
+  nodes.machine = { config, pkgs, ... }: {
+    services.homed.enable = true;
+
+    users.users.test-normal-user = {
+      extraGroups = [ "wheel" ];
+      isNormalUser = true;
+      initialPassword = password;
+    };
+  };
+  testScript = ''
+    def switchTTY(number):
+      machine.send_key(f"alt-f{number}")
+      machine.wait_until_succeeds(f"[ $(fgconsole) = {number} ]")
+      machine.wait_for_unit(f"getty@tty{number}.service")
+      machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{number}'")
+
+    machine.wait_for_unit("multi-user.target")
+
+    # Smoke test to make sure the pam changes didn't break regular users.
+    machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+    with subtest("login as regular user"):
+      switchTTY(2)
+      machine.wait_until_tty_matches("2", "login: ")
+      machine.send_chars("test-normal-user\n")
+      machine.wait_until_tty_matches("2", "login: test-normal-user")
+      machine.wait_until_tty_matches("2", "Password: ")
+      machine.send_chars("${password}\n")
+      machine.wait_until_succeeds("pgrep -u test-normal-user bash")
+      machine.send_chars("whoami > /tmp/1\n")
+      machine.wait_for_file("/tmp/1")
+      assert "test-normal-user" in machine.succeed("cat /tmp/1")
+
+    with subtest("create homed encrypted user"):
+      # TODO: Figure out how to pass password manually.
+      #
+      # This environment variable is used for homed internal testing
+      # and is not documented.
+      machine.succeed("NEWPASSWORD=${password} homectl create --shell=/run/current-system/sw/bin/bash --storage=luks -G wheel test-homed-user")
+
+    with subtest("login as homed user"):
+      switchTTY(3)
+      machine.wait_until_tty_matches("3", "login: ")
+      machine.send_chars("test-homed-user\n")
+      machine.wait_until_tty_matches("3", "login: test-homed-user")
+      machine.wait_until_tty_matches("3", "Password: ")
+      machine.send_chars("${password}\n")
+      machine.wait_until_succeeds("pgrep -t tty3 -u test-homed-user bash")
+      machine.send_chars("whoami > /tmp/2\n")
+      machine.wait_for_file("/tmp/2")
+      assert "test-homed-user" in machine.succeed("cat /tmp/2")
+
+    with subtest("change homed user password"):
+      switchTTY(4)
+      machine.wait_until_tty_matches("4", "login: ")
+      machine.send_chars("test-homed-user\n")
+      machine.wait_until_tty_matches("4", "login: test-homed-user")
+      machine.wait_until_tty_matches("4", "Password: ")
+      machine.send_chars("${password}\n")
+      machine.wait_until_succeeds("pgrep -t tty4 -u test-homed-user bash")
+      machine.send_chars("passwd\n")
+      # homed does it in a weird order, it asks for new passes, then it asks
+      # for the old one.
+      machine.sleep(2)
+      machine.send_chars("${newPass}\n")
+      machine.sleep(2)
+      machine.send_chars("${newPass}\n")
+      machine.sleep(4)
+      machine.send_chars("${password}\n")
+      machine.wait_until_fails("pgrep -t tty4 passwd")
+
+      @polling_condition
+      def not_logged_in_tty5():
+        machine.fail("pgrep -t tty5 bash")
+
+      switchTTY(5)
+      with not_logged_in_tty5: # type: ignore[union-attr]
+        machine.wait_until_tty_matches("5", "login: ")
+        machine.send_chars("test-homed-user\n")
+        machine.wait_until_tty_matches("5", "login: test-homed-user")
+        machine.wait_until_tty_matches("5", "Password: ")
+        machine.send_chars("${password}\n")
+        machine.wait_until_tty_matches("5", "Password incorrect or not sufficient for authentication of user test-homed-user.")
+        machine.wait_until_tty_matches("5", "Sorry, try again: ")
+      machine.send_chars("${newPass}\n")
+      machine.send_chars("whoami > /tmp/4\n")
+      machine.wait_for_file("/tmp/4")
+      assert "test-homed-user" in machine.succeed("cat /tmp/4")
+
+    with subtest("homed user should be in wheel according to NSS"):
+      machine.succeed("userdbctl group wheel -s io.systemd.NameServiceSwitch | grep test-homed-user")
+  '';
+})