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
238 enableGnomeKeyring = mkOption {
239 default = false;
240 type = types.bool;
241 description = ''
242 If enabled, pam_gnome_keyring will attempt to automatically unlock the
243 user's default Gnome keyring upon login. If the user login password does
244 not match their keyring password, Gnome Keyring will prompt separately
245 after login.
246 '';
247 };
248
249 text = mkOption {
250 type = types.nullOr types.lines;
251 description = "Contents of the PAM service file.";
252 };
253
254 };
255
256 config = {
257 name = mkDefault name;
258 setLoginUid = mkDefault cfg.startSession;
259 limits = mkDefault config.security.pam.loginLimits;
260
261 # !!! TODO: move the LDAP stuff to the LDAP module, and the
262 # Samba stuff to the Samba module. This requires that the PAM
263 # module provides the right hooks.
264 text = mkDefault
265 (''
266 # Account management.
267 account sufficient pam_unix.so
268 ${optionalString use_ldap
269 "account sufficient ${pam_ldap}/lib/security/pam_ldap.so"}
270 ${optionalString config.services.sssd.enable
271 "account sufficient ${pkgs.sssd}/lib/security/pam_sss.so"}
272 ${optionalString config.krb5.enable
273 "account sufficient ${pam_krb5}/lib/security/pam_krb5.so"}
274
275 # Authentication management.
276 ${optionalString cfg.rootOK
277 "auth sufficient pam_rootok.so"}
278 ${optionalString cfg.requireWheel
279 "auth required pam_wheel.so use_uid"}
280 ${optionalString cfg.logFailures
281 "auth required pam_tally.so"}
282 ${optionalString (config.security.pam.enableSSHAgentAuth && cfg.sshAgentAuth)
283 "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"}
284 ${optionalString cfg.fprintAuth
285 "auth sufficient ${pkgs.fprintd}/lib/security/pam_fprintd.so"}
286 ${optionalString cfg.u2fAuth
287 "auth sufficient ${pkgs.pam_u2f}/lib/security/pam_u2f.so"}
288 ${optionalString cfg.usbAuth
289 "auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so"}
290 ${let oath = config.security.pam.oath; in optionalString cfg.oathAuth
291 "auth requisite ${pkgs.oathToolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}"}
292 '' +
293 # Modules in this block require having the password set in PAM_AUTHTOK.
294 # pam_unix is marked as 'sufficient' on NixOS which means nothing will run
295 # after it succeeds. Certain modules need to run after pam_unix
296 # prompts the user for password so we run it once with 'required' at an
297 # earlier point and it will run again with 'sufficient' further down.
298 # We use try_first_pass the second time to avoid prompting password twice
299 (optionalString (cfg.unixAuth &&
300 (config.security.pam.enableEcryptfs
301 || cfg.pamMount
302 || cfg.enableKwallet
303 || cfg.enableGnomeKeyring
304 || cfg.googleAuthenticator.enable)) ''
305 auth required pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth
306 ${optionalString config.security.pam.enableEcryptfs
307 "auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap"}
308 ${optionalString cfg.pamMount
309 "auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
310 ${optionalString cfg.enableKwallet
311 ("auth optional ${pkgs.plasma5.kwallet-pam}/lib/security/pam_kwallet5.so" +
312 " kwalletd=${pkgs.libsForQt5.kwallet.bin}/bin/kwalletd5")}
313 ${optionalString cfg.enableGnomeKeyring
314 ("auth optional ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so")}
315 ${optionalString cfg.googleAuthenticator.enable
316 "auth required ${pkgs.googleAuthenticator}/lib/security/pam_google_authenticator.so no_increment_hotp"}
317 '') + ''
318 ${optionalString cfg.unixAuth
319 "auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth try_first_pass"}
320 ${optionalString cfg.otpwAuth
321 "auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so"}
322 ${optionalString use_ldap
323 "auth sufficient ${pam_ldap}/lib/security/pam_ldap.so use_first_pass"}
324 ${optionalString config.services.sssd.enable
325 "auth sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_first_pass"}
326 ${optionalString config.krb5.enable ''
327 auth [default=ignore success=1 service_err=reset] ${pam_krb5}/lib/security/pam_krb5.so use_first_pass
328 auth [default=die success=done] ${pam_ccreds}/lib/security/pam_ccreds.so action=validate use_first_pass
329 auth sufficient ${pam_ccreds}/lib/security/pam_ccreds.so action=store use_first_pass
330 ''}
331 auth required pam_deny.so
332
333 # Password management.
334 password requisite pam_unix.so nullok sha512
335 ${optionalString config.security.pam.enableEcryptfs
336 "password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"}
337 ${optionalString cfg.pamMount
338 "password optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
339 ${optionalString use_ldap
340 "password sufficient ${pam_ldap}/lib/security/pam_ldap.so"}
341 ${optionalString config.services.sssd.enable
342 "password sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_authtok"}
343 ${optionalString config.krb5.enable
344 "password sufficient ${pam_krb5}/lib/security/pam_krb5.so use_first_pass"}
345 ${optionalString config.services.samba.syncPasswordsByPam
346 "password optional ${pkgs.samba}/lib/security/pam_smbpass.so nullok use_authtok try_first_pass"}
347
348 # Session management.
349 ${optionalString cfg.setEnvironment ''
350 session required pam_env.so envfile=${config.system.build.pamEnvironment}
351 ''}
352 session required pam_unix.so
353 ${optionalString cfg.setLoginUid
354 "session ${
355 if config.boot.isContainer then "optional" else "required"
356 } pam_loginuid.so"}
357 ${optionalString cfg.makeHomeDir
358 "session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=${config.security.pam.makeHomeDir.skelDirectory} umask=0022"}
359 ${optionalString cfg.updateWtmp
360 "session required ${pkgs.pam}/lib/security/pam_lastlog.so silent"}
361 ${optionalString config.security.pam.enableEcryptfs
362 "session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"}
363 ${optionalString use_ldap
364 "session optional ${pam_ldap}/lib/security/pam_ldap.so"}
365 ${optionalString config.services.sssd.enable
366 "session optional ${pkgs.sssd}/lib/security/pam_sss.so"}
367 ${optionalString config.krb5.enable
368 "session optional ${pam_krb5}/lib/security/pam_krb5.so"}
369 ${optionalString cfg.otpwAuth
370 "session optional ${pkgs.otpw}/lib/security/pam_otpw.so"}
371 ${optionalString cfg.startSession
372 "session optional ${pkgs.systemd}/lib/security/pam_systemd.so"}
373 ${optionalString cfg.forwardXAuth
374 "session optional pam_xauth.so xauthpath=${pkgs.xorg.xauth}/bin/xauth systemuser=99"}
375 ${optionalString (cfg.limits != [])
376 "session required ${pkgs.pam}/lib/security/pam_limits.so conf=${makeLimitsConf cfg.limits}"}
377 ${optionalString (cfg.showMotd && config.users.motd != null)
378 "session optional ${pkgs.pam}/lib/security/pam_motd.so motd=${motd}"}
379 ${optionalString cfg.pamMount
380 "session optional ${pkgs.pam_mount}/lib/security/pam_mount.so"}
381 ${optionalString (cfg.enableAppArmor && config.security.apparmor.enable)
382 "session optional ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so order=user,group,default debug"}
383 ${optionalString (cfg.enableKwallet)
384 ("session optional ${pkgs.plasma5.kwallet-pam}/lib/security/pam_kwallet5.so" +
385 " kwalletd=${pkgs.libsForQt5.kwallet.bin}/bin/kwalletd5")}
386 ${optionalString (cfg.enableGnomeKeyring)
387 "session optional ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so auto_start"}
388 ${optionalString (config.virtualisation.lxc.lxcfs.enable)
389 "session optional ${pkgs.lxcfs}/lib/security/pam_cgfs.so -c freezer,memory,name=systemd,unified,cpuset"}
390 '');
391 };
392
393 };
394
395
396 inherit (pkgs) pam_krb5 pam_ccreds;
397
398 use_ldap = (config.users.ldap.enable && config.users.ldap.loginPam);
399 pam_ldap = if config.users.ldap.daemon.enable then pkgs.nss_pam_ldapd else pkgs.pam_ldap;
400
401 # Create a limits.conf(5) file.
402 makeLimitsConf = limits:
403 pkgs.writeText "limits.conf"
404 (concatMapStrings ({ domain, type, item, value }:
405 "${domain} ${type} ${item} ${toString value}\n")
406 limits);
407
408 motd = pkgs.writeText "motd" config.users.motd;
409
410 makePAMService = pamService:
411 { source = pkgs.writeText "${pamService.name}.pam" pamService.text;
412 target = "pam.d/${pamService.name}";
413 };
414
415in
416
417{
418
419 ###### interface
420
421 options = {
422
423 security.pam.loginLimits = mkOption {
424 default = [];
425 example =
426 [ { domain = "ftp";
427 type = "hard";
428 item = "nproc";
429 value = "0";
430 }
431 { domain = "@student";
432 type = "-";
433 item = "maxlogins";
434 value = "4";
435 }
436 ];
437
438 description =
439 '' Define resource limits that should apply to users or groups.
440 Each item in the list should be an attribute set with a
441 <varname>domain</varname>, <varname>type</varname>,
442 <varname>item</varname>, and <varname>value</varname>
443 attribute. The syntax and semantics of these attributes
444 must be that described in the limits.conf(5) man page.
445 '';
446 };
447
448 security.pam.services = mkOption {
449 default = [];
450 type = with types; loaOf (submodule pamOpts);
451 description =
452 ''
453 This option defines the PAM services. A service typically
454 corresponds to a program that uses PAM,
455 e.g. <command>login</command> or <command>passwd</command>.
456 Each attribute of this set defines a PAM service, with the attribute name
457 defining the name of the service.
458 '';
459 };
460
461 security.pam.makeHomeDir.skelDirectory = mkOption {
462 type = types.str;
463 default = "/var/empty";
464 example = "/etc/skel";
465 description = ''
466 Path to skeleton directory whose contents are copied to home
467 directories newly created by <literal>pam_mkhomedir</literal>.
468 '';
469 };
470
471 security.pam.enableSSHAgentAuth = mkOption {
472 default = false;
473 description =
474 ''
475 Enable sudo logins if the user's SSH agent provides a key
476 present in <filename>~/.ssh/authorized_keys</filename>.
477 This allows machines to exclusively use SSH keys instead of
478 passwords.
479 '';
480 };
481
482 security.pam.enableOTPW = mkOption {
483 default = false;
484 description = ''
485 Enable the OTPW (one-time password) PAM module.
486 '';
487 };
488
489 security.pam.enableU2F = mkOption {
490 default = false;
491 description = ''
492 Enable the U2F PAM module.
493 '';
494 };
495
496 security.pam.enableEcryptfs = mkOption {
497 default = false;
498 description = ''
499 Enable eCryptfs PAM module (mounting ecryptfs home directory on login).
500 '';
501 };
502
503 users.motd = mkOption {
504 default = null;
505 example = "Today is Sweetmorn, the 4th day of The Aftermath in the YOLD 3178.";
506 type = types.nullOr types.lines;
507 description = "Message of the day shown to users when they log in.";
508 };
509
510 };
511
512
513 ###### implementation
514
515 config = {
516
517 environment.systemPackages =
518 # Include the PAM modules in the system path mostly for the manpages.
519 [ pkgs.pam ]
520 ++ optional config.users.ldap.enable pam_ldap
521 ++ optional config.services.sssd.enable pkgs.sssd
522 ++ optionals config.krb5.enable [pam_krb5 pam_ccreds]
523 ++ optionals config.security.pam.enableOTPW [ pkgs.otpw ]
524 ++ optionals config.security.pam.oath.enable [ pkgs.oathToolkit ]
525 ++ optionals config.security.pam.enableU2F [ pkgs.pam_u2f ];
526
527 boot.supportedFilesystems = optionals config.security.pam.enableEcryptfs [ "ecryptfs" ];
528
529 security.wrappers = {
530 unix_chkpwd = {
531 source = "${pkgs.pam}/sbin/unix_chkpwd.orig";
532 owner = "root";
533 setuid = true;
534 };
535 };
536
537 environment.etc =
538 mapAttrsToList (n: v: makePAMService v) config.security.pam.services;
539
540 security.pam.services =
541 { other.text =
542 ''
543 auth required pam_warn.so
544 auth required pam_deny.so
545 account required pam_warn.so
546 account required pam_deny.so
547 password required pam_warn.so
548 password required pam_deny.so
549 session required pam_warn.so
550 session required pam_deny.so
551 '';
552
553 # Most of these should be moved to specific modules.
554 cups = {};
555 ftp = {};
556 i3lock = {};
557 i3lock-color = {};
558 screen = {};
559 vlock = {};
560 xlock = {};
561 xscreensaver = {};
562
563 runuser = { rootOK = true; unixAuth = false; setEnvironment = false; };
564
565 /* FIXME: should runuser -l start a systemd session? Currently
566 it complains "Cannot create session: Already running in a
567 session". */
568 runuser-l = { rootOK = true; unixAuth = false; };
569 };
570
571 };
572
573}