1# This module provides configuration for the PAM (Pluggable
2# Authentication Modules) system.
3
4{ config, lib, pkgs, ... }:
5
6with lib;
7
8let
9 parentConfig = config;
10
11 pamOpts = { config, name, ... }: let cfg = config; in let config = parentConfig; in {
12
13 options = {
14
15 name = mkOption {
16 example = "sshd";
17 type = types.str;
18 description = "Name of the PAM service.";
19 };
20
21 unixAuth = mkOption {
22 default = true;
23 type = types.bool;
24 description = ''
25 Whether users can log in with passwords defined in
26 <filename>/etc/shadow</filename>.
27 '';
28 };
29
30 rootOK = mkOption {
31 default = false;
32 type = types.bool;
33 description = ''
34 If set, root doesn't need to authenticate (e.g. for the
35 <command>useradd</command> service).
36 '';
37 };
38
39 u2fAuth = mkOption {
40 default = config.security.pam.enableU2F;
41 type = types.bool;
42 description = ''
43 If set, users listed in
44 <filename>~/.yubico/u2f_keys</filename> are able to log in
45 with the associated U2F key.
46 '';
47 };
48
49 usbAuth = mkOption {
50 default = config.security.pam.usb.enable;
51 type = types.bool;
52 description = ''
53 If set, users listed in
54 <filename>/etc/pamusb.conf</filename> are able to log in
55 with the associated USB key.
56 '';
57 };
58
59 otpwAuth = mkOption {
60 default = config.security.pam.enableOTPW;
61 type = types.bool;
62 description = ''
63 If set, the OTPW system will be used (if
64 <filename>~/.otpw</filename> exists).
65 '';
66 };
67
68 fprintAuth = mkOption {
69 default = config.services.fprintd.enable;
70 type = types.bool;
71 description = ''
72 If set, fingerprint reader will be used (if exists and
73 your fingerprints are enrolled).
74 '';
75 };
76
77 oathAuth = mkOption {
78 default = config.security.pam.oath.enable;
79 type = types.bool;
80 description = ''
81 If set, the OATH Toolkit will be used.
82 '';
83 };
84
85 sshAgentAuth = mkOption {
86 default = false;
87 type = types.bool;
88 description = ''
89 If set, the calling user's SSH agent is used to authenticate
90 against the keys in the calling user's
91 <filename>~/.ssh/authorized_keys</filename>. This is useful
92 for <command>sudo</command> on password-less remote systems.
93 '';
94 };
95
96 startSession = mkOption {
97 default = false;
98 type = types.bool;
99 description = ''
100 If set, the service will register a new session with
101 systemd's login manager. For local sessions, this will give
102 the user access to audio devices, CD-ROM drives. In the
103 default PolicyKit configuration, it also allows the user to
104 reboot the system.
105 '';
106 };
107
108 setEnvironment = mkOption {
109 type = types.bool;
110 default = true;
111 description = ''
112 Whether the service should set the environment variables
113 listed in <option>environment.sessionVariables</option>
114 using <literal>pam_env.so</literal>.
115 '';
116 };
117
118 setLoginUid = mkOption {
119 type = types.bool;
120 description = ''
121 Set the login uid of the process
122 (<filename>/proc/self/loginuid</filename>) for auditing
123 purposes. The login uid is only set by ‘entry points’ like
124 <command>login</command> and <command>sshd</command>, not by
125 commands like <command>sudo</command>.
126 '';
127 };
128
129 forwardXAuth = mkOption {
130 default = false;
131 type = types.bool;
132 description = ''
133 Whether X authentication keys should be passed from the
134 calling user to the target user (e.g. for
135 <command>su</command>)
136 '';
137 };
138
139 pamMount = mkOption {
140 default = config.security.pam.mount.enable;
141 type = types.bool;
142 description = ''
143 Enable PAM mount (pam_mount) system to mount fileystems on user login.
144 '';
145 };
146
147 allowNullPassword = mkOption {
148 default = false;
149 type = types.bool;
150 description = ''
151 Whether to allow logging into accounts that have no password
152 set (i.e., have an empty password field in
153 <filename>/etc/passwd</filename> or
154 <filename>/etc/group</filename>). This does not enable
155 logging into disabled accounts (i.e., that have the password
156 field set to <literal>!</literal>). Note that regardless of
157 what the pam_unix documentation says, accounts with hashed
158 empty passwords are always allowed to log in.
159 '';
160 };
161
162 requireWheel = mkOption {
163 default = false;
164 type = types.bool;
165 description = ''
166 Whether to permit root access only to members of group wheel.
167 '';
168 };
169
170 limits = mkOption {
171 description = ''
172 Attribute set describing resource limits. Defaults to the
173 value of <option>security.pam.loginLimits</option>.
174 '';
175 };
176
177 showMotd = mkOption {
178 default = false;
179 type = types.bool;
180 description = "Whether to show the message of the day.";
181 };
182
183 makeHomeDir = mkOption {
184 default = false;
185 type = types.bool;
186 description = ''
187 Whether to try to create home directories for users
188 with <literal>$HOME</literal>s pointing to nonexistent
189 locations on session login.
190 '';
191 };
192
193 updateWtmp = mkOption {
194 default = false;
195 type = types.bool;
196 description = "Whether to update <filename>/var/log/wtmp</filename>.";
197 };
198
199 logFailures = mkOption {
200 default = false;
201 type = types.bool;
202 description = "Whether to log authentication failures in <filename>/var/log/faillog</filename>.";
203 };
204
205 enableAppArmor = mkOption {
206 default = false;
207 type = types.bool;
208 description = ''
209 Enable support for attaching AppArmor profiles at the
210 user/group level, e.g., as part of a role based access
211 control scheme.
212 '';
213 };
214
215 enableKwallet = mkOption {
216 default = false;
217 type = types.bool;
218 description = ''
219 If enabled, pam_wallet will attempt to automatically unlock the
220 user's default KDE wallet upon login. If the user has no wallet named
221 "kdewallet", or the login password does not match their wallet
222 password, KDE will prompt separately after login.
223 '';
224 };
225
226 text = mkOption {
227 type = types.nullOr types.lines;
228 description = "Contents of the PAM service file.";
229 };
230
231 };
232
233 config = {
234 name = mkDefault name;
235 setLoginUid = mkDefault cfg.startSession;
236 limits = mkDefault config.security.pam.loginLimits;
237
238 # !!! TODO: move the LDAP stuff to the LDAP module, and the
239 # Samba stuff to the Samba module. This requires that the PAM
240 # module provides the right hooks.
241 text = mkDefault
242 (''
243 # Account management.
244 account sufficient pam_unix.so
245 ${optionalString use_ldap
246 "account sufficient ${pam_ldap}/lib/security/pam_ldap.so"}
247 ${optionalString config.services.sssd.enable
248 "account sufficient ${pkgs.sssd}/lib/security/pam_sss.so"}
249 ${optionalString config.krb5.enable
250 "account sufficient ${pam_krb5}/lib/security/pam_krb5.so"}
251
252 # Authentication management.
253 ${optionalString cfg.rootOK
254 "auth sufficient pam_rootok.so"}
255 ${optionalString cfg.requireWheel
256 "auth required pam_wheel.so use_uid"}
257 ${optionalString cfg.logFailures
258 "auth required pam_tally.so"}
259 ${optionalString (config.security.pam.enableSSHAgentAuth && cfg.sshAgentAuth)
260 "auth sufficient ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so file=~/.ssh/authorized_keys:~/.ssh/authorized_keys2:/etc/ssh/authorized_keys.d/%u"}
261 ${optionalString cfg.fprintAuth
262 "auth sufficient ${pkgs.fprintd}/lib/security/pam_fprintd.so"}
263 ${optionalString cfg.u2fAuth
264 "auth sufficient ${pkgs.pam_u2f}/lib/security/pam_u2f.so"}
265 ${optionalString cfg.usbAuth
266 "auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so"}
267 ${let oath = config.security.pam.oath; in optionalString cfg.oathAuth
268 "auth requisite ${pkgs.oathToolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}"}
269 '' +
270 # Modules in this block require having the password set in PAM_AUTHTOK.
271 # pam_unix is marked as 'sufficient' on NixOS which means nothing will run
272 # after it succeeds. Certain modules need to run after pam_unix
273 # prompts the user for password so we run it once with 'required' at an
274 # earlier point and it will run again with 'sufficient' further down.
275 # We use try_first_pass the second time to avoid prompting password twice
276 (optionalString (cfg.unixAuth && (config.security.pam.enableEcryptfs || cfg.pamMount || cfg.enableKwallet)) ''
277 auth required pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth
278 ${optionalString config.security.pam.enableEcryptfs
279 "auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap"}
280 ${optionalString cfg.pamMount
281 "auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
282 ${optionalString cfg.enableKwallet
283 ("auth optional ${pkgs.plasma5.kwallet-pam}/lib/security/pam_kwallet5.so" +
284 " kwalletd=${pkgs.libsForQt5.kwallet.bin}/bin/kwalletd5")}
285 '') + ''
286 ${optionalString cfg.unixAuth
287 "auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth try_first_pass"}
288 ${optionalString cfg.otpwAuth
289 "auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so"}
290 ${optionalString use_ldap
291 "auth sufficient ${pam_ldap}/lib/security/pam_ldap.so use_first_pass"}
292 ${optionalString config.services.sssd.enable
293 "auth sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_first_pass"}
294 ${optionalString config.krb5.enable ''
295 auth [default=ignore success=1 service_err=reset] ${pam_krb5}/lib/security/pam_krb5.so use_first_pass
296 auth [default=die success=done] ${pam_ccreds}/lib/security/pam_ccreds.so action=validate use_first_pass
297 auth sufficient ${pam_ccreds}/lib/security/pam_ccreds.so action=store use_first_pass
298 ''}
299 auth required pam_deny.so
300
301 # Password management.
302 password requisite pam_unix.so nullok sha512
303 ${optionalString config.security.pam.enableEcryptfs
304 "password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"}
305 ${optionalString cfg.pamMount
306 "password optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
307 ${optionalString use_ldap
308 "password sufficient ${pam_ldap}/lib/security/pam_ldap.so"}
309 ${optionalString config.services.sssd.enable
310 "password sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_authtok"}
311 ${optionalString config.krb5.enable
312 "password sufficient ${pam_krb5}/lib/security/pam_krb5.so use_first_pass"}
313 ${optionalString config.services.samba.syncPasswordsByPam
314 "password optional ${pkgs.samba}/lib/security/pam_smbpass.so nullok use_authtok try_first_pass"}
315
316 # Session management.
317 ${optionalString cfg.setEnvironment ''
318 session required pam_env.so envfile=${config.system.build.pamEnvironment}
319 ''}
320 session required pam_unix.so
321 ${optionalString cfg.setLoginUid
322 "session ${
323 if config.boot.isContainer then "optional" else "required"
324 } pam_loginuid.so"}
325 ${optionalString cfg.makeHomeDir
326 "session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=${config.security.pam.makeHomeDir.skelDirectory} umask=0022"}
327 ${optionalString cfg.updateWtmp
328 "session required ${pkgs.pam}/lib/security/pam_lastlog.so silent"}
329 ${optionalString config.security.pam.enableEcryptfs
330 "session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"}
331 ${optionalString use_ldap
332 "session optional ${pam_ldap}/lib/security/pam_ldap.so"}
333 ${optionalString config.services.sssd.enable
334 "session optional ${pkgs.sssd}/lib/security/pam_sss.so"}
335 ${optionalString config.krb5.enable
336 "session optional ${pam_krb5}/lib/security/pam_krb5.so"}
337 ${optionalString cfg.otpwAuth
338 "session optional ${pkgs.otpw}/lib/security/pam_otpw.so"}
339 ${optionalString cfg.startSession
340 "session optional ${pkgs.systemd}/lib/security/pam_systemd.so"}
341 ${optionalString cfg.forwardXAuth
342 "session optional pam_xauth.so xauthpath=${pkgs.xorg.xauth}/bin/xauth systemuser=99"}
343 ${optionalString (cfg.limits != [])
344 "session required ${pkgs.pam}/lib/security/pam_limits.so conf=${makeLimitsConf cfg.limits}"}
345 ${optionalString (cfg.showMotd && config.users.motd != null)
346 "session optional ${pkgs.pam}/lib/security/pam_motd.so motd=${motd}"}
347 ${optionalString cfg.pamMount
348 "session optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
349 ${optionalString (cfg.enableAppArmor && config.security.apparmor.enable)
350 "session optional ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so order=user,group,default debug"}
351 ${optionalString (cfg.enableKwallet)
352 ("session optional ${pkgs.plasma5.kwallet-pam}/lib/security/pam_kwallet5.so" +
353 " kwalletd=${pkgs.libsForQt5.kwallet.bin}/bin/kwalletd5")}
354 '');
355 };
356
357 };
358
359
360 inherit (pkgs) pam_krb5 pam_ccreds;
361
362 use_ldap = (config.users.ldap.enable && config.users.ldap.loginPam);
363 pam_ldap = if config.users.ldap.daemon.enable then pkgs.nss_pam_ldapd else pkgs.pam_ldap;
364
365 # Create a limits.conf(5) file.
366 makeLimitsConf = limits:
367 pkgs.writeText "limits.conf"
368 (concatMapStrings ({ domain, type, item, value }:
369 "${domain} ${type} ${item} ${toString value}\n")
370 limits);
371
372 motd = pkgs.writeText "motd" config.users.motd;
373
374 makePAMService = pamService:
375 { source = pkgs.writeText "${pamService.name}.pam" pamService.text;
376 target = "pam.d/${pamService.name}";
377 };
378
379in
380
381{
382
383 ###### interface
384
385 options = {
386
387 security.pam.loginLimits = mkOption {
388 default = [];
389 example =
390 [ { domain = "ftp";
391 type = "hard";
392 item = "nproc";
393 value = "0";
394 }
395 { domain = "@student";
396 type = "-";
397 item = "maxlogins";
398 value = "4";
399 }
400 ];
401
402 description =
403 '' Define resource limits that should apply to users or groups.
404 Each item in the list should be an attribute set with a
405 <varname>domain</varname>, <varname>type</varname>,
406 <varname>item</varname>, and <varname>value</varname>
407 attribute. The syntax and semantics of these attributes
408 must be that described in the limits.conf(5) man page.
409 '';
410 };
411
412 security.pam.services = mkOption {
413 default = [];
414 type = with types; loaOf (submodule pamOpts);
415 description =
416 ''
417 This option defines the PAM services. A service typically
418 corresponds to a program that uses PAM,
419 e.g. <command>login</command> or <command>passwd</command>.
420 Each attribute of this set defines a PAM service, with the attribute name
421 defining the name of the service.
422 '';
423 };
424
425 security.pam.makeHomeDir.skelDirectory = mkOption {
426 type = types.str;
427 default = "/var/empty";
428 example = "/etc/skel";
429 description = ''
430 Path to skeleton directory whose contents are copied to home
431 directories newly created by <literal>pam_mkhomedir</literal>.
432 '';
433 };
434
435 security.pam.enableSSHAgentAuth = mkOption {
436 default = false;
437 description =
438 ''
439 Enable sudo logins if the user's SSH agent provides a key
440 present in <filename>~/.ssh/authorized_keys</filename>.
441 This allows machines to exclusively use SSH keys instead of
442 passwords.
443 '';
444 };
445
446 security.pam.enableOTPW = mkOption {
447 default = false;
448 description = ''
449 Enable the OTPW (one-time password) PAM module.
450 '';
451 };
452
453 security.pam.enableU2F = mkOption {
454 default = false;
455 description = ''
456 Enable the U2F PAM module.
457 '';
458 };
459
460 security.pam.enableEcryptfs = mkOption {
461 default = false;
462 description = ''
463 Enable eCryptfs PAM module (mounting ecryptfs home directory on login).
464 '';
465 };
466
467 users.motd = mkOption {
468 default = null;
469 example = "Today is Sweetmorn, the 4th day of The Aftermath in the YOLD 3178.";
470 type = types.nullOr types.lines;
471 description = "Message of the day shown to users when they log in.";
472 };
473
474 };
475
476
477 ###### implementation
478
479 config = {
480
481 environment.systemPackages =
482 # Include the PAM modules in the system path mostly for the manpages.
483 [ pkgs.pam ]
484 ++ optional config.users.ldap.enable pam_ldap
485 ++ optional config.services.sssd.enable pkgs.sssd
486 ++ optionals config.krb5.enable [pam_krb5 pam_ccreds]
487 ++ optionals config.security.pam.enableOTPW [ pkgs.otpw ]
488 ++ optionals config.security.pam.oath.enable [ pkgs.oathToolkit ]
489 ++ optionals config.security.pam.enableU2F [ pkgs.pam_u2f ]
490 ++ optionals config.security.pam.enableEcryptfs [ pkgs.ecryptfs ];
491
492 security.wrappers = {
493 unix_chkpwd = {
494 source = "${pkgs.pam}/sbin/unix_chkpwd.orig";
495 owner = "root";
496 setuid = true;
497 };
498 } // (if config.security.pam.enableEcryptfs then {
499 "mount.ecryptfs_private".source = "${pkgs.ecryptfs.out}/bin/mount.ecryptfs_private";
500 "umount.ecryptfs_private".source = "${pkgs.ecryptfs.out}/bin/umount.ecryptfs_private";
501 } else {});
502
503 environment.etc =
504 mapAttrsToList (n: v: makePAMService v) config.security.pam.services;
505
506 security.pam.services =
507 { other.text =
508 ''
509 auth required pam_warn.so
510 auth required pam_deny.so
511 account required pam_warn.so
512 account required pam_deny.so
513 password required pam_warn.so
514 password required pam_deny.so
515 session required pam_warn.so
516 session required pam_deny.so
517 '';
518
519 # Most of these should be moved to specific modules.
520 cups = {};
521 ftp = {};
522 i3lock = {};
523 i3lock-color = {};
524 screen = {};
525 vlock = {};
526 xlock = {};
527 xscreensaver = {};
528
529 runuser = { rootOK = true; unixAuth = false; setEnvironment = false; };
530
531 /* FIXME: should runuser -l start a systemd session? Currently
532 it complains "Cannot create session: Already running in a
533 session". */
534 runuser-l = { rootOK = true; unixAuth = false; };
535 };
536
537 };
538
539}