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