1# Configuration for the pwdutils suite of tools: passwd, useradd, etc.
2{ config, lib, utils, pkgs, ... }:
3with lib;
4let
5 cfg = config.security.loginDefs;
6in
7{
8 options = with types; {
9 security.loginDefs = {
10 package = mkPackageOptionMD pkgs "shadow" { };
11
12 chfnRestrict = mkOption {
13 description = mdDoc ''
14 Use chfn SUID to allow non-root users to change their account GECOS information.
15 '';
16 type = nullOr str;
17 default = null;
18 };
19
20 settings = mkOption {
21 description = mdDoc ''
22 Config options for the /etc/login.defs file, that defines
23 the site-specific configuration for the shadow password suite.
24 See login.defs(5) man page for available options.
25 '';
26 type = submodule {
27 freeformType = (pkgs.formats.keyValue { }).type;
28 /* There are three different sources for user/group id ranges, each of which gets
29 used by different programs:
30 - The login.defs file, used by the useradd, groupadd and newusers commands
31 - The update-users-groups.pl file, used by NixOS in the activation phase to
32 decide on which ids to use for declaratively defined users without a static
33 id
34 - Systemd compile time options -Dsystem-uid-max= and -Dsystem-gid-max=, used
35 by systemd for features like ConditionUser=@system and systemd-sysusers
36 */
37 options = {
38 DEFAULT_HOME = mkOption {
39 description = mdDoc "Indicate if login is allowed if we can't cd to the home directory.";
40 default = "yes";
41 type = enum [ "yes" "no" ];
42 };
43
44 ENCRYPT_METHOD = mkOption {
45 description = mdDoc "This defines the system default encryption algorithm for encrypting passwords.";
46 # The default crypt() method, keep in sync with the PAM default
47 default = "YESCRYPT";
48 type = enum [ "YESCRYPT" "SHA512" "SHA256" "MD5" "DES"];
49 };
50
51 SYS_UID_MIN = mkOption {
52 description = mdDoc "Range of user IDs used for the creation of system users by useradd or newusers.";
53 default = 400;
54 type = int;
55 };
56
57 SYS_UID_MAX = mkOption {
58 description = mdDoc "Range of user IDs used for the creation of system users by useradd or newusers.";
59 default = 999;
60 type = int;
61 };
62
63 UID_MIN = mkOption {
64 description = mdDoc "Range of user IDs used for the creation of regular users by useradd or newusers.";
65 default = 1000;
66 type = int;
67 };
68
69 UID_MAX = mkOption {
70 description = mdDoc "Range of user IDs used for the creation of regular users by useradd or newusers.";
71 default = 29999;
72 type = int;
73 };
74
75 SYS_GID_MIN = mkOption {
76 description = mdDoc "Range of group IDs used for the creation of system groups by useradd, groupadd, or newusers";
77 default = 400;
78 type = int;
79 };
80
81 SYS_GID_MAX = mkOption {
82 description = mdDoc "Range of group IDs used for the creation of system groups by useradd, groupadd, or newusers";
83 default = 999;
84 type = int;
85 };
86
87 GID_MIN = mkOption {
88 description = mdDoc "Range of group IDs used for the creation of regular groups by useradd, groupadd, or newusers.";
89 default = 1000;
90 type = int;
91 };
92
93 GID_MAX = mkOption {
94 description = mdDoc "Range of group IDs used for the creation of regular groups by useradd, groupadd, or newusers.";
95 default = 29999;
96 type = int;
97 };
98
99 TTYGROUP = mkOption {
100 description = mdDoc ''
101 The terminal permissions: the login tty will be owned by the TTYGROUP group,
102 and the permissions will be set to TTYPERM'';
103 default = "tty";
104 type = str;
105 };
106
107 TTYPERM = mkOption {
108 description = mdDoc ''
109 The terminal permissions: the login tty will be owned by the TTYGROUP group,
110 and the permissions will be set to TTYPERM'';
111 default = "0620";
112 type = str;
113 };
114
115 # Ensure privacy for newly created home directories.
116 UMASK = mkOption {
117 description = mdDoc "The file mode creation mask is initialized to this value.";
118 default = "077";
119 type = str;
120 };
121 };
122 };
123 default = { };
124 };
125 };
126
127 users.defaultUserShell = mkOption {
128 description = mdDoc ''
129 This option defines the default shell assigned to user
130 accounts. This can be either a full system path or a shell package.
131
132 This must not be a store path, since the path is
133 used outside the store (in particular in /etc/passwd).
134 '';
135 example = literalExpression "pkgs.zsh";
136 type = either path shellPackage;
137 };
138 };
139
140 ###### implementation
141
142 config = {
143 assertions = [
144 {
145 assertion = cfg.settings.SYS_UID_MIN <= cfg.settings.SYS_UID_MAX;
146 message = "SYS_UID_MIN must be less than or equal to SYS_UID_MAX";
147 }
148 {
149 assertion = cfg.settings.UID_MIN <= cfg.settings.UID_MAX;
150 message = "UID_MIN must be less than or equal to UID_MAX";
151 }
152 {
153 assertion = cfg.settings.SYS_GID_MIN <= cfg.settings.SYS_GID_MAX;
154 message = "SYS_GID_MIN must be less than or equal to SYS_GID_MAX";
155 }
156 {
157 assertion = cfg.settings.GID_MIN <= cfg.settings.GID_MAX;
158 message = "GID_MIN must be less than or equal to GID_MAX";
159 }
160 ];
161
162 security.loginDefs.settings.CHFN_RESTRICT =
163 mkIf (cfg.chfnRestrict != null) cfg.chfnRestrict;
164
165 environment.systemPackages = optional config.users.mutableUsers cfg.package
166 ++ optional (types.shellPackage.check config.users.defaultUserShell) config.users.defaultUserShell
167 ++ optional (cfg.chfnRestrict != null) pkgs.util-linux;
168
169 environment.etc =
170 # Create custom toKeyValue generator
171 # see https://man7.org/linux/man-pages/man5/login.defs.5.html for config specification
172 let
173 toKeyValue = generators.toKeyValue {
174 mkKeyValue = generators.mkKeyValueDefault { } " ";
175 };
176 in
177 {
178 # /etc/login.defs: global configuration for pwdutils.
179 # You cannot login without it!
180 "login.defs".source = pkgs.writeText "login.defs" (toKeyValue cfg.settings);
181
182 # /etc/default/useradd: configuration for useradd.
183 "default/useradd".source = pkgs.writeText "useradd" ''
184 GROUP=100
185 HOME=/home
186 SHELL=${utils.toShellPath config.users.defaultUserShell}
187 '';
188 };
189
190 security.pam.services = {
191 chsh = { rootOK = true; };
192 chfn = { rootOK = true; };
193 su = {
194 rootOK = true;
195 forwardXAuth = true;
196 logFailures = true;
197 };
198 passwd = { };
199 # Note: useradd, groupadd etc. aren't setuid root, so it
200 # doesn't really matter what the PAM config says as long as it
201 # lets root in.
202 useradd.rootOK = true;
203 usermod.rootOK = true;
204 userdel.rootOK = true;
205 groupadd.rootOK = true;
206 groupmod.rootOK = true;
207 groupmems.rootOK = true;
208 groupdel.rootOK = true;
209 login = {
210 startSession = true;
211 allowNullPassword = true;
212 showMotd = true;
213 updateWtmp = true;
214 };
215 chpasswd = { rootOK = true; };
216 };
217
218 security.wrappers =
219 let
220 mkSetuidRoot = source: {
221 setuid = true;
222 owner = "root";
223 group = "root";
224 inherit source;
225 };
226 in
227 {
228 su = mkSetuidRoot "${cfg.package.su}/bin/su";
229 sg = mkSetuidRoot "${cfg.package.out}/bin/sg";
230 newgrp = mkSetuidRoot "${cfg.package.out}/bin/newgrp";
231 newuidmap = mkSetuidRoot "${cfg.package.out}/bin/newuidmap";
232 newgidmap = mkSetuidRoot "${cfg.package.out}/bin/newgidmap";
233 }
234 // optionalAttrs config.users.mutableUsers {
235 chsh = mkSetuidRoot "${cfg.package.out}/bin/chsh";
236 passwd = mkSetuidRoot "${cfg.package.out}/bin/passwd";
237 };
238 };
239}