···550550 (mkAfter [ "[success=merge] systemd" ]) # need merge so that NSS won't stop at file-based groups
551551 ]
552552 );
553553+ shadow = (
554554+ mkMerge [
555555+ (mkAfter [ "systemd" ])
556556+ ]
557557+ );
553558 };
554559555560 environment.systemPackages = [ cfg.package ];
+57-11
nixos/modules/system/boot/systemd/homed.nix
···11{
22 config,
33 lib,
44- pkgs,
44+ utils,
55 ...
66}:
7788let
99 cfg = config.services.homed;
1010in
1111+1112{
1212- options.services.homed.enable = lib.mkEnableOption ''
1313- systemd home area/user account manager
1414- '';
1313+ options.services.homed = {
1414+ enable = lib.mkEnableOption "systemd home area/user account manager";
1515+1616+ promptOnFirstBoot =
1717+ lib.mkEnableOption ''
1818+ interactively prompting for user creation on first boot
1919+ ''
2020+ // {
2121+ default = true;
2222+ };
2323+2424+ settings.Home = lib.mkOption {
2525+ default = { };
2626+ type = lib.types.submodule {
2727+ freeformType = lib.types.attrsOf utils.systemdUtils.unitOptions.unitOption;
2828+ };
2929+ example = {
3030+ DefaultStorage = "luks";
3131+ DefaultFileSystemType = "btrfs";
3232+ };
3333+ description = ''
3434+ Options for systemd-homed. See {manpage}`homed.conf(5)` man page for
3535+ available options.
3636+ '';
3737+ };
3838+ };
15391640 config = lib.mkIf cfg.enable {
1741 assertions = [
1842 {
1943 assertion = config.services.nscd.enable;
2020- message = "systemd-homed requires the use of systemd nss module. services.nscd.enable must be set to true,";
4444+ message = ''
4545+ systemd-homed requires the use of the systemd nss module.
4646+ services.nscd.enable must be set to true.
4747+ '';
2148 }
2249 ];
23502451 systemd.additionalUpstreamSystemUnits = [
2552 "systemd-homed.service"
2653 "systemd-homed-activate.service"
5454+ "systemd-homed-firstboot.service"
2755 ];
28562929- # This is mentioned in homed's [Install] section.
3030- #
3131- # While homed appears to work without it, it's probably better
3232- # to follow upstream recommendations.
3333- services.userdbd.enable = lib.mkDefault true;
5757+ # homed exposes SSH public keys and other user metadata using userdb
5858+ services.userdbd = {
5959+ enable = true;
6060+ enableSSHSupport = lib.mkDefault config.services.openssh.enable;
6161+ };
6262+6363+ # Enable creation and mounting of LUKS home areas with all filesystems
6464+ # supported by systemd-homed.
6565+ boot.supportedFilesystems = [
6666+ "btrfs"
6767+ "ext4"
6868+ "xfs"
6969+ ];
7070+7171+ environment.etc."systemd/homed.conf".text = ''
7272+ [Home]
7373+ ${utils.systemdUtils.lib.attrsToSection cfg.settings.Home}
7474+ '';
34753576 systemd.services = {
3677 systemd-homed = {
3737- # These packages are required to manage encrypted volumes
7878+ # These packages are required to manage home areas with LUKS storage
3879 path = config.system.fsPackages;
3980 aliases = [ "dbus-org.freedesktop.home1.service" ];
4081 wantedBy = [ "multi-user.target" ];
4182 };
42834384 systemd-homed-activate = {
8585+ wantedBy = [ "systemd-homed.service" ];
8686+ };
8787+8888+ systemd-homed-firstboot = {
8989+ enable = cfg.promptOnFirstBoot;
4490 wantedBy = [ "systemd-homed.service" ];
4591 };
4692 };
+69-3
nixos/modules/system/boot/systemd/userdbd.nix
···2233let
44 cfg = config.services.userdbd;
55+66+ # List of system users that will be incorrectly treated as regular/normal
77+ # users by userdb.
88+ highSystemUsers = lib.filter (
99+ user: user.enable && user.isSystemUser && (lib.defaultTo 0 user.uid) >= 1000 && user.uid != 65534
1010+ ) (lib.attrValues config.users.users);
511in
612{
77- options.services.userdbd.enable = lib.mkEnableOption ''
88- the systemd JSON user/group record lookup service
99- '';
1313+ options.services.userdbd = {
1414+ enable = lib.mkEnableOption ''
1515+ the systemd JSON user/group record lookup service
1616+ '';
1717+1818+ enableSSHSupport = lib.mkEnableOption ''
1919+ exposing OpenSSH public keys defined in userdb. Be aware that this
2020+ enables modifying public keys at runtime, either by users managed by
2121+ {option}`services.homed`, or globally via drop-in files
2222+ '';
2323+2424+ silenceHighSystemUsers = lib.mkOption {
2525+ type = lib.types.bool;
2626+ default = false;
2727+ example = true;
2828+ description = "Silence warning about system users with high UIDs.";
2929+ visible = false;
3030+ };
3131+ };
3232+1033 config = lib.mkIf cfg.enable {
3434+ assertions = lib.singleton {
3535+ assertion = cfg.enableSSHSupport -> config.security.enableWrappers;
3636+ message = "OpenSSH userdb integration requires security wrappers.";
3737+ };
3838+3939+ warnings = lib.optional (lib.length highSystemUsers > 0 && !cfg.silenceHighSystemUsers) ''
4040+ The following system users have UIDs higher than 1000:
4141+4242+ ${lib.concatLines (lib.map (user: user.name) highSystemUsers)}
4343+4444+ These users will be recognized by systemd-userdb as "regular" users, not
4545+ "system" users. This will affect programs that query regular users, such
4646+ as systemd-homed, which will not run the first boot user creation flow,
4747+ as regular users already exist.
4848+4949+ To fix this issue, please remove or redefine these system users to have
5050+ UIDs below 1000. For Nix build users, it's possible to adjust the base
5151+ build user ID using the `ids.uids.nixbld` option, however care must be
5252+ taken to avoid collisions with UIDs of other services. Alternatively, you
5353+ may enable the `auto-allocate-uids` experimental feature and option in
5454+ the Nix configuration to avoid creating these users, however please note
5555+ that this option is experimental and subject to change.
5656+5757+ Alternatively, to acknowledge and silence this warning, set
5858+ `services.userdbd.silenceHighSystemUsers` to true.
5959+ '';
6060+1161 systemd.additionalUpstreamSystemUnits = [
1262 "systemd-userdbd.socket"
1363 "systemd-userdbd.service"
1464 ];
15651666 systemd.sockets.systemd-userdbd.wantedBy = [ "sockets.target" ];
6767+6868+ # OpenSSH requires AuthorizedKeysCommand to be owned only by root.
6969+ # Referencing `userdbctl` directly from the Nix store won't work, as
7070+ # `/nix/store` is owned by the `nixbld` group.
7171+ security.wrappers = lib.mkIf cfg.enableSSHSupport {
7272+ userdbctl = {
7373+ owner = "root";
7474+ group = "root";
7575+ source = lib.getExe' config.systemd.package "userdbctl";
7676+ };
7777+ };
7878+7979+ services.openssh = lib.mkIf cfg.enableSSHSupport {
8080+ authorizedKeysCommand = "/run/wrappers/bin/userdbctl ssh-authorized-keys %u";
8181+ authorizedKeysCommandUser = "root";
8282+ };
1783 };
1884}
+41-6
nixos/modules/tasks/filesystems.nix
···8484 mountPoint = mkOption {
8585 example = "/mnt/usb";
8686 type = nonEmptyWithoutTrailingSlash;
8787- description = "Location of the mounted file system.";
8787+ default = name;
8888+ description = ''
8989+ Location where the file system will be mounted.
9090+9191+ This is called `mountpoint` in {manpage}`mount(8)` and `fs_file` in {manpage}`fstab(5)`
9292+ '';
8893 };
89949095 stratis.poolUuid = mkOption {
9196 type = types.uniq (types.nullOr types.str);
9297 description = ''
9398 UUID of the stratis pool that the fs is located in
9999+100100+ This is only relevant if you are using [stratis](https://stratis-storage.github.io/).
94101 '';
95102 example = "04c68063-90a5-4235-b9dd-6180098a20d9";
96103 default = null;
···100107 default = null;
101108 example = "/dev/sda";
102109 type = types.nullOr nonEmptyStr;
103103- description = "Location of the device.";
110110+ description = ''
111111+ The device as passed to `mount`.
112112+113113+ This can be any of:
114114+115115+ - a filename of a block special device such as `/dev/sdc3`
116116+ - a tag such as `UUID=fdd68895-c307-4549-8c9c-90e44c71f5b7`
117117+ - (for bind mounts only) the source path
118118+ - something else depending on the {option}`fsType`. For example, `nfs` device may look like `knuth.cwi.nl:/dir`
119119+120120+ This is called `device` in {manpage}`mount(8)` and `fs_spec` in {manpage}`fstab(5)`.
121121+ '';
104122 };
105123106124 fsType = mkOption {
107125 default = "auto";
108126 example = "ext3";
109127 type = nonEmptyStr;
110110- description = "Type of the file system.";
128128+ description = ''
129129+ Type of the file system.
130130+131131+ This is the `fstype` passed to `-t` in the {manpage}`mount(8)` command, and is called `fs_vfstype` in {manpage}`fstab(5)`.
132132+ '';
111133 };
112134113135 options = mkOption {
···115137 example = [ "data=journal" ];
116138 description = ''
117139 Options used to mount the file system.
118118- See {manpage}`mount(8)` for common options.
140140+141141+ This is called `options` in {manpage}`mount(8)` and `fs_mntops` in {manpage}`fstab(5)`
142142+143143+ Some options that can be used for all mounts are documented in {manpage}`mount(8)` under `FILESYSTEM-INDEPENDENT MOUNT OPTIONS`.
144144+145145+ Options that systemd understands are documented in {manpage}`systemd.mount(5)` under `FSTAB`.
146146+147147+ Each filesystem supports additional options, see the docs for that filesystem.
119148 '';
120149 type = types.nonEmptyListOf nonEmptyStr;
121150 };
···131160 to this list, any other filesystem whose mount point is a parent of
132161 the path will be mounted before this filesystem. The paths do not need
133162 to actually be the {option}`mountPoint` of some other filesystem.
163163+164164+ This is useful for mounts which require keys and/or configuration files residing on another filesystem.
134165 '';
135166 };
136167137168 };
138169139170 config = {
140140- mountPoint = mkDefault name;
141171 device = mkIf (elem config.fsType specialFSTypes) (mkDefault config.fsType);
142172 };
143173···153183 default = null;
154184 example = "root-partition";
155185 type = types.nullOr nonEmptyStr;
156156- description = "Label of the device (if any).";
186186+ description = ''
187187+ Label of the device. This simply sets {option}`device` to
188188+ `/dev/disk/by-id/''${label}`. Note that devices will not
189189+ have a label unless they contain a filesystem which
190190+ supports labels, such as ext4 or fat32.
191191+ '';
157192 };
158193159194 autoFormat = mkOption {
+109-78
nixos/tests/systemd-homed.nix
···11-{ pkgs, lib, ... }:
21let
33- password = "foobarfoo";
44- newPass = "barfoobar";
22+ username = "test-homed-user";
33+ initialPassword = "foobarfoo";
44+ newPassword = "barfoobar";
55in
66+67{
78 name = "systemd-homed";
88- nodes.machine =
99- { config, pkgs, ... }:
1010- {
1111- services.homed.enable = true;
1291313- users.users.test-normal-user = {
1414- extraGroups = [ "wheel" ];
1515- isNormalUser = true;
1616- initialPassword = password;
1010+ nodes = {
1111+ machine =
1212+ { ... }:
1313+ {
1414+ services = {
1515+ homed.enable = true;
1616+ openssh.enable = true;
1717+ };
1818+1919+ # Prevent nixbld users from showing up as regular users, required for
2020+ # first boot prompt
2121+ nix.settings = {
2222+ experimental-features = [ "auto-allocate-uids" ];
2323+ auto-allocate-uids = true;
2424+ };
1725 };
1818- };
1919- testScript = ''
2020- def switchTTY(number):
2121- machine.send_key(f"alt-f{number}")
2222- machine.wait_until_succeeds(f"[ $(fgconsole) = {number} ]")
2323- machine.wait_for_unit(f"getty@tty{number}.service")
2424- machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{number}'")
25262626- machine.wait_for_unit("multi-user.target")
2727+ sshClient =
2828+ { pkgs, ... }:
2929+ {
3030+ services = {
3131+ homed.enable = true;
3232+ userdbd.silenceHighSystemUsers = true;
3333+ };
27342828- # Smoke test to make sure the pam changes didn't break regular users.
2929- machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
3030- with subtest("login as regular user"):
3131- switchTTY(2)
3232- machine.wait_until_tty_matches("2", "login: ")
3333- machine.send_chars("test-normal-user\n")
3434- machine.wait_until_tty_matches("2", "login: test-normal-user")
3535- machine.wait_until_tty_matches("2", "Password: ")
3636- machine.send_chars("${password}\n")
3737- machine.wait_until_succeeds("pgrep -u test-normal-user bash")
3838- machine.send_chars("whoami > /tmp/1\n")
3939- machine.wait_for_file("/tmp/1")
4040- assert "test-normal-user" in machine.succeed("cat /tmp/1")
3535+ # Regular user, should prevent first boot prompt
3636+ users.users.test-normal-user = {
3737+ extraGroups = [ "wheel" ];
3838+ isNormalUser = true;
3939+ inherit initialPassword;
4040+ };
4141+ };
4242+ };
41434242- with subtest("create homed encrypted user"):
4343- # TODO: Figure out how to pass password manually.
4444- #
4545- # This environment variable is used for homed internal testing
4646- # and is not documented.
4747- machine.succeed("NEWPASSWORD=${password} homectl create --shell=/run/current-system/sw/bin/bash --storage=luks -G wheel test-homed-user")
4444+ testScript = ''
4545+ start_all()
4646+4747+ with subtest("create systemd-homed user on first boot prompt"):
4848+ machine.wait_for_unit("systemd-homed.service")
4949+ machine.wait_until_tty_matches("1", "-- Press any key to proceed --")
5050+ machine.send_chars(" ")
5151+ machine.wait_until_tty_matches("1", "Please enter user name")
5252+ machine.send_chars("${username}\n")
5353+ machine.wait_until_tty_matches("1", "Please enter an auxiliary group")
5454+ machine.send_chars("wheel\n")
5555+ machine.wait_until_tty_matches("1", "Please enter an auxiliary group")
5656+ machine.send_chars("\n")
5757+ machine.wait_until_tty_matches("1", "Please enter the shell to use")
5858+ machine.send_chars("/bin/sh\n")
5959+ machine.wait_until_tty_matches("1", "Please enter new password")
6060+ machine.send_chars("${initialPassword}\n")
6161+ machine.wait_until_tty_matches("1", "(repeat)")
6262+ machine.send_chars("${initialPassword}\n")
48634964 with subtest("login as homed user"):
5050- switchTTY(3)
5151- machine.wait_until_tty_matches("3", "login: ")
5252- machine.send_chars("test-homed-user\n")
5353- machine.wait_until_tty_matches("3", "login: test-homed-user")
5454- machine.wait_until_tty_matches("3", "Password: ")
5555- machine.send_chars("${password}\n")
5656- machine.wait_until_succeeds("pgrep -t tty3 -u test-homed-user bash")
6565+ machine.wait_until_tty_matches("1", "login: ")
6666+ machine.send_chars("${username}\n")
6767+ machine.wait_until_tty_matches("1", "Password: ")
6868+ machine.send_chars("${initialPassword}\n")
6969+ machine.wait_until_succeeds("pgrep -u ${username} -t tty1 sh")
5770 machine.send_chars("whoami > /tmp/2\n")
5871 machine.wait_for_file("/tmp/2")
5959- assert "test-homed-user" in machine.succeed("cat /tmp/2")
7272+ assert "${username}" in machine.succeed("cat /tmp/2")
7373+7474+ # Smoke test to make sure the pam changes didn't break regular users.
7575+ # Since homed is also enabled in the sshClient, it also tests the first
7676+ # boot prompt did not occur.
7777+ with subtest("login as regular user"):
7878+ sshClient.wait_until_tty_matches("1", "login: ")
7979+ sshClient.send_chars("test-normal-user\n")
8080+ sshClient.wait_until_tty_matches("1", "Password: ")
8181+ sshClient.send_chars("${initialPassword}\n")
8282+ sshClient.wait_until_succeeds("pgrep -u test-normal-user bash")
8383+ sshClient.send_chars("whoami > /tmp/1\n")
8484+ sshClient.wait_for_file("/tmp/1")
8585+ assert "test-normal-user" in sshClient.succeed("cat /tmp/1")
8686+8787+ with subtest("add homed ssh authorized key"):
8888+ sshClient.send_chars('ssh-keygen -t ed25519 -f /tmp/id_ed25519 -N ""\n')
8989+ sshClient.wait_for_file("/tmp/id_ed25519.pub")
9090+ public_key = sshClient.succeed('cat /tmp/id_ed25519.pub')
9191+ public_key = public_key.strip()
9292+ machine.succeed(f"homectl update ${username} --offline --ssh-authorized-keys '{public_key}'")
9393+ machine.succeed("userdbctl ssh-authorized-keys ${username} | grep ed25519")
60946195 with subtest("change homed user password"):
6262- switchTTY(4)
6363- machine.wait_until_tty_matches("4", "login: ")
6464- machine.send_chars("test-homed-user\n")
6565- machine.wait_until_tty_matches("4", "login: test-homed-user")
6666- machine.wait_until_tty_matches("4", "Password: ")
6767- machine.send_chars("${password}\n")
6868- machine.wait_until_succeeds("pgrep -t tty4 -u test-homed-user bash")
6969- machine.send_chars("passwd\n")
9696+ machine.send_chars("passwd; echo $? > /tmp/3\n")
7097 # homed does it in a weird order, it asks for new passes, then it asks
7198 # for the old one.
7272- machine.sleep(2)
7373- machine.send_chars("${newPass}\n")
7474- machine.sleep(2)
7575- machine.send_chars("${newPass}\n")
9999+ machine.wait_until_tty_matches("1", "New password: ")
100100+ machine.send_chars("${newPassword}\n")
101101+ machine.wait_until_tty_matches("1", "Retype new password: ")
102102+ machine.send_chars("${newPassword}\n")
103103+ #machine.wait_until_tty_matches("1", "Password: ")
76104 machine.sleep(4)
7777- machine.send_chars("${password}\n")
7878- machine.wait_until_fails("pgrep -t tty4 passwd")
7979-8080- @polling_condition
8181- def not_logged_in_tty5():
8282- machine.fail("pgrep -t tty5 bash")
105105+ machine.send_chars("${initialPassword}\n")
106106+ machine.wait_for_file("/tmp/3")
107107+ assert "0\n" == machine.succeed("cat /tmp/3")
831088484- switchTTY(5)
8585- with not_logged_in_tty5: # type: ignore[union-attr]
8686- machine.wait_until_tty_matches("5", "login: ")
8787- machine.send_chars("test-homed-user\n")
8888- machine.wait_until_tty_matches("5", "login: test-homed-user")
8989- machine.wait_until_tty_matches("5", "Password: ")
9090- machine.send_chars("${password}\n")
9191- machine.wait_until_tty_matches("5", "Password incorrect or not sufficient for authentication of user test-homed-user.")
9292- machine.wait_until_tty_matches("5", "Sorry, try again: ")
9393- machine.send_chars("${newPass}\n")
9494- machine.send_chars("whoami > /tmp/4\n")
109109+ with subtest("escalate to root from homed user"):
110110+ # Also tests the user is in wheel.
111111+ machine.send_chars("sudo id | tee /tmp/4\n")
112112+ machine.wait_until_tty_matches("1", "password for ${username}")
113113+ machine.send_chars("${newPassword}\n")
95114 machine.wait_for_file("/tmp/4")
9696- assert "test-homed-user" in machine.succeed("cat /tmp/4")
115115+ machine.wait_until_succeeds("grep uid=0 /tmp/4")
971169898- with subtest("homed user should be in wheel according to NSS"):
9999- machine.succeed("userdbctl group wheel -s io.systemd.NameServiceSwitch | grep test-homed-user")
117117+ with subtest("log out and deactivate homed user's home area"):
118118+ machine.send_chars("exit\n")
119119+ machine.wait_until_succeeds("homectl inspect ${username} | grep 'State: inactive'")
120120+121121+ with subtest("ssh as homed user"):
122122+ sshClient.send_chars("ssh -o StrictHostKeyChecking=no -i /tmp/id_ed25519 ${username}@machine\n")
123123+ sshClient.wait_until_tty_matches("1", "Please enter password for user")
124124+ sshClient.send_chars("${newPassword}\n")
125125+ machine.wait_until_succeeds("pgrep -u ${username} sh")
126126+ sshClient.send_chars("whoami > /tmp/5\n")
127127+ machine.wait_for_file("/tmp/5")
128128+ assert "${username}" in machine.succeed("cat /tmp/5")
129129+ sshClient.send_chars("exit\n") # ssh
130130+ sshClient.send_chars("exit\n") # sh
100131 '';
101132}
···4444 npm run build --workspace=packages/bruno-graphql-docs
4545 npm run build --workspace=packages/bruno-converters
4646 npm run build --workspace=packages/bruno-query
4747+ npm run build --workspace=packages/bruno-filestore
4748 npm run build --workspace=packages/bruno-requests
48494950 npm run sandbox:bundle-libraries --workspace=packages/bruno-js