lol

Merge pull request #205121 from alaviss/homed

nixos: systemd-homed support

authored by

Florian Klink and committed by
GitHub
6b1a8965 17dc4d77

+223 -5
+2
nixos/modules/module-list.nix
··· 1288 1288 ./system/boot/systemd/shutdown.nix 1289 1289 ./system/boot/systemd/tmpfiles.nix 1290 1290 ./system/boot/systemd/user.nix 1291 + ./system/boot/systemd/userdbd.nix 1292 + ./system/boot/systemd/homed.nix 1291 1293 ./system/boot/timesyncd.nix 1292 1294 ./system/boot/tmp.nix 1293 1295 ./system/boot/uvesafb.nix
+24 -3
nixos/modules/security/pam.nix
··· 488 488 account [success=ok ignore=ignore default=die] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so 489 489 account [success=ok default=ignore] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_admin.so 490 490 '' + 491 + optionalString config.services.homed.enable '' 492 + account sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so 493 + '' + 491 494 # The required pam_unix.so module has to come after all the sufficient modules 492 495 # because otherwise, the account lookup will fail if the user does not exist 493 496 # locally, for example with MySQL- or LDAP-auth. ··· 541 544 # after it succeeds. Certain modules need to run after pam_unix 542 545 # prompts the user for password so we run it once with 'optional' at an 543 546 # earlier point and it will run again with 'sufficient' further down. 544 - # We use try_first_pass the second time to avoid prompting password twice 545 - (optionalString (cfg.unixAuth && 547 + # We use try_first_pass the second time to avoid prompting password twice. 548 + # 549 + # The same principle applies to systemd-homed 550 + (optionalString ((cfg.unixAuth || config.services.homed.enable) && 546 551 (config.security.pam.enableEcryptfs 547 552 || config.security.pam.enableFscrypt 548 553 || cfg.pamMount ··· 553 558 || cfg.failDelay.enable 554 559 || cfg.duoSecurity.enable)) 555 560 ( 556 - '' 561 + optionalString config.services.homed.enable '' 562 + auth optional ${config.systemd.package}/lib/security/pam_systemd_home.so 563 + '' + 564 + optionalString cfg.unixAuth '' 557 565 auth optional pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth 558 566 '' + 559 567 optionalString config.security.pam.enableEcryptfs '' ··· 584 592 auth required ${pkgs.duo-unix}/lib/security/pam_duo.so 585 593 '' 586 594 )) + 595 + optionalString config.services.homed.enable '' 596 + auth sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so 597 + '' + 587 598 optionalString cfg.unixAuth '' 588 599 auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth try_first_pass 589 600 '' + ··· 605 616 auth required pam_deny.so 606 617 607 618 # Password management. 619 + '' + 620 + optionalString config.services.homed.enable '' 621 + password sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so 622 + '' + '' 608 623 password sufficient pam_unix.so nullok sha512 609 624 '' + 610 625 optionalString config.security.pam.enableEcryptfs '' ··· 650 665 ++ optional (cfg.ttyAudit.enablePattern != null) "enable=${cfg.ttyAudit.enablePattern}" 651 666 ++ optional (cfg.ttyAudit.disablePattern != null) "disable=${cfg.ttyAudit.disablePattern}" 652 667 )) + 668 + optionalString config.services.homed.enable '' 669 + session required ${config.systemd.package}/lib/security/pam_systemd_home.so 670 + '' + 653 671 optionalString cfg.makeHomeDir '' 654 672 session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=${config.security.pam.makeHomeDir.skelDirectory} umask=0077 655 673 '' + ··· 1361 1379 '' + 1362 1380 optionalString config.virtualisation.lxc.lxcfs.enable '' 1363 1381 mr ${pkgs.lxc}/lib/security/pam_cgfs.so 1382 + '' + 1383 + optionalString config.services.homed.enable '' 1384 + mr ${config.systemd.package}/lib/security/pam_systemd_home.so 1364 1385 ''; 1365 1386 }; 1366 1387
+1 -1
nixos/modules/system/boot/systemd.nix
··· 450 450 (mkAfter [ "systemd" ]) 451 451 ]); 452 452 group = (mkMerge [ 453 - (mkAfter [ "systemd" ]) 453 + (mkAfter [ "[success=merge] systemd" ]) # need merge so that NSS won't stop at file-based groups 454 454 ]); 455 455 }; 456 456
+43
nixos/modules/system/boot/systemd/homed.nix
··· 1 + { config, lib, pkgs, ... }: 2 + 3 + let 4 + cfg = config.services.homed; 5 + in 6 + { 7 + options.services.homed.enable = lib.mkEnableOption (lib.mdDoc '' 8 + Enable systemd home area/user account manager 9 + ''); 10 + 11 + config = lib.mkIf cfg.enable { 12 + assertions = [ 13 + { 14 + assertion = config.services.nscd.enable; 15 + message = "systemd-homed requires the use of systemd nss module. services.nscd.enable must be set to true,"; 16 + } 17 + ]; 18 + 19 + systemd.additionalUpstreamSystemUnits = [ 20 + "systemd-homed.service" 21 + "systemd-homed-activate.service" 22 + ]; 23 + 24 + # This is mentioned in homed's [Install] section. 25 + # 26 + # While homed appears to work without it, it's probably better 27 + # to follow upstream recommendations. 28 + services.userdbd.enable = lib.mkDefault true; 29 + 30 + systemd.services = { 31 + systemd-homed = { 32 + # These packages are required to manage encrypted volumes 33 + path = config.system.fsPackages; 34 + aliases = [ "dbus-org.freedesktop.home1.service" ]; 35 + wantedBy = [ "multi-user.target" ]; 36 + }; 37 + 38 + systemd-homed-activate = { 39 + wantedBy = [ "systemd-homed.service" ]; 40 + }; 41 + }; 42 + }; 43 + }
+18
nixos/modules/system/boot/systemd/userdbd.nix
··· 1 + { config, lib, ... }: 2 + 3 + let 4 + cfg = config.services.userdbd; 5 + in 6 + { 7 + options.services.userdbd.enable = lib.mkEnableOption (lib.mdDoc '' 8 + Enables the systemd JSON user/group record lookup service 9 + ''); 10 + config = lib.mkIf cfg.enable { 11 + systemd.additionalUpstreamSystemUnits = [ 12 + "systemd-userdbd.socket" 13 + "systemd-userdbd.service" 14 + ]; 15 + 16 + systemd.sockets.systemd-userdbd.wantedBy = [ "sockets.target" ]; 17 + }; 18 + }
+2
nixos/tests/all-tests.nix
··· 642 642 systemd-shutdown = handleTest ./systemd-shutdown.nix {}; 643 643 systemd-timesyncd = handleTest ./systemd-timesyncd.nix {}; 644 644 systemd-misc = handleTest ./systemd-misc.nix {}; 645 + systemd-userdbd = handleTest ./systemd-userdbd.nix {}; 646 + systemd-homed = handleTest ./systemd-homed.nix {}; 645 647 tandoor-recipes = handleTest ./tandoor-recipes.nix {}; 646 648 taskserver = handleTest ./taskserver.nix {}; 647 649 tayga = handleTest ./tayga.nix {};
+99
nixos/tests/systemd-homed.nix
··· 1 + import ./make-test-python.nix ({ pkgs, lib, ... }: 2 + let 3 + password = "foobar"; 4 + newPass = "barfoo"; 5 + in 6 + { 7 + name = "systemd-homed"; 8 + nodes.machine = { config, pkgs, ... }: { 9 + services.homed.enable = true; 10 + 11 + users.users.test-normal-user = { 12 + extraGroups = [ "wheel" ]; 13 + isNormalUser = true; 14 + initialPassword = password; 15 + }; 16 + }; 17 + testScript = '' 18 + def switchTTY(number): 19 + machine.send_key(f"alt-f{number}") 20 + machine.wait_until_succeeds(f"[ $(fgconsole) = {number} ]") 21 + machine.wait_for_unit(f"getty@tty{number}.service") 22 + machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{number}'") 23 + 24 + machine.wait_for_unit("multi-user.target") 25 + 26 + # Smoke test to make sure the pam changes didn't break regular users. 27 + machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'") 28 + with subtest("login as regular user"): 29 + switchTTY(2) 30 + machine.wait_until_tty_matches("2", "login: ") 31 + machine.send_chars("test-normal-user\n") 32 + machine.wait_until_tty_matches("2", "login: test-normal-user") 33 + machine.wait_until_tty_matches("2", "Password: ") 34 + machine.send_chars("${password}\n") 35 + machine.wait_until_succeeds("pgrep -u test-normal-user bash") 36 + machine.send_chars("whoami > /tmp/1\n") 37 + machine.wait_for_file("/tmp/1") 38 + assert "test-normal-user" in machine.succeed("cat /tmp/1") 39 + 40 + with subtest("create homed encrypted user"): 41 + # TODO: Figure out how to pass password manually. 42 + # 43 + # This environment variable is used for homed internal testing 44 + # and is not documented. 45 + machine.succeed("NEWPASSWORD=${password} homectl create --shell=/run/current-system/sw/bin/bash --storage=luks -G wheel test-homed-user") 46 + 47 + with subtest("login as homed user"): 48 + switchTTY(3) 49 + machine.wait_until_tty_matches("3", "login: ") 50 + machine.send_chars("test-homed-user\n") 51 + machine.wait_until_tty_matches("3", "login: test-homed-user") 52 + machine.wait_until_tty_matches("3", "Password: ") 53 + machine.send_chars("${password}\n") 54 + machine.wait_until_succeeds("pgrep -t tty3 -u test-homed-user bash") 55 + machine.send_chars("whoami > /tmp/2\n") 56 + machine.wait_for_file("/tmp/2") 57 + assert "test-homed-user" in machine.succeed("cat /tmp/2") 58 + 59 + with subtest("change homed user password"): 60 + switchTTY(4) 61 + machine.wait_until_tty_matches("4", "login: ") 62 + machine.send_chars("test-homed-user\n") 63 + machine.wait_until_tty_matches("4", "login: test-homed-user") 64 + machine.wait_until_tty_matches("4", "Password: ") 65 + machine.send_chars("${password}\n") 66 + machine.wait_until_succeeds("pgrep -t tty4 -u test-homed-user bash") 67 + machine.send_chars("passwd\n") 68 + # homed does it in a weird order, it asks for new passes, then it asks 69 + # for the old one. 70 + machine.sleep(2) 71 + machine.send_chars("${newPass}\n") 72 + machine.sleep(2) 73 + machine.send_chars("${newPass}\n") 74 + machine.sleep(4) 75 + machine.send_chars("${password}\n") 76 + machine.wait_until_fails("pgrep -t tty4 passwd") 77 + 78 + @polling_condition 79 + def not_logged_in_tty5(): 80 + machine.fail("pgrep -t tty5 bash") 81 + 82 + switchTTY(5) 83 + with not_logged_in_tty5: # type: ignore[union-attr] 84 + machine.wait_until_tty_matches("5", "login: ") 85 + machine.send_chars("test-homed-user\n") 86 + machine.wait_until_tty_matches("5", "login: test-homed-user") 87 + machine.wait_until_tty_matches("5", "Password: ") 88 + machine.send_chars("${password}\n") 89 + machine.wait_until_tty_matches("5", "Password incorrect or not sufficient for authentication of user test-homed-user.") 90 + machine.wait_until_tty_matches("5", "Sorry, try again: ") 91 + machine.send_chars("${newPass}\n") 92 + machine.send_chars("whoami > /tmp/4\n") 93 + machine.wait_for_file("/tmp/4") 94 + assert "test-homed-user" in machine.succeed("cat /tmp/4") 95 + 96 + with subtest("homed user should be in wheel according to NSS"): 97 + machine.succeed("userdbctl group wheel -s io.systemd.NameServiceSwitch | grep test-homed-user") 98 + ''; 99 + })
+32
nixos/tests/systemd-userdbd.nix
··· 1 + import ./make-test-python.nix ({ pkgs, lib, ... }: { 2 + name = "systemd-userdbd"; 3 + nodes.machine = { config, pkgs, ... }: { 4 + services.userdbd.enable = true; 5 + 6 + users.users.test-user-nss = { 7 + isNormalUser = true; 8 + }; 9 + 10 + environment.etc."userdb/test-user-dropin.user".text = builtins.toJSON { 11 + userName = "test-user-dropin"; 12 + }; 13 + 14 + environment.systemPackages = with pkgs; [ libvarlink ]; 15 + }; 16 + testScript = '' 17 + import json 18 + from shlex import quote 19 + 20 + def getUserRecord(name): 21 + Interface = "unix:/run/systemd/userdb/io.systemd.Multiplexer/io.systemd.UserDatabase" 22 + payload = json.dumps({ 23 + "service": "io.systemd.Multiplexer", 24 + "userName": name 25 + }) 26 + return json.loads(machine.succeed(f"varlink call {Interface}.GetUserRecord {quote(payload)}")) 27 + 28 + machine.wait_for_unit("systemd-userdbd.socket") 29 + getUserRecord("test-user-nss") 30 + getUserRecord("test-user-dropin") 31 + ''; 32 + })
+1 -1
pkgs/os-specific/linux/systemd/default.nix
··· 82 82 , withDocumentation ? true 83 83 , withEfi ? stdenv.hostPlatform.isEfi && !stdenv.hostPlatform.isMusl 84 84 , withFido2 ? true 85 - , withHomed ? false 85 + , withHomed ? true 86 86 , withHostnamed ? true 87 87 , withHwdb ? true 88 88 , withImportd ? !stdenv.hostPlatform.isMusl
+1
pkgs/top-level/all-packages.nix
··· 26152 26152 withEfi = false; 26153 26153 withFido2 = false; 26154 26154 withHostnamed = false; 26155 + withHomed = false; 26155 26156 withHwdb = false; 26156 26157 withImportd = false; 26157 26158 withLibBPF = false;