grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / security / pam.nix
blob2ff08cbfde81237154c77d262b7dc0d14caef190
1 # This module provides configuration for the PAM (Pluggable
2 # Authentication Modules) system.
3 { config, lib, pkgs, ... }:
4 let
6   moduleSettingsType = with lib.types; attrsOf (nullOr (oneOf [ bool str int pathInStore ]));
7   moduleSettingsDescription = ''
8     Boolean values render just the key if true, and nothing if false.
9     Null values are ignored.
10     All other values are rendered as key-value pairs.
11   '';
13   mkRulesTypeOption = type: lib.mkOption {
14     # These options are experimental and subject to breaking changes without notice.
15     description = ''
16       PAM `${type}` rules for this service.
18       Attribute keys are the name of each rule.
19     '';
20     type = lib.types.attrsOf (lib.types.submodule ({ name, config, ... }: {
21       options = {
22         name = lib.mkOption {
23           type = lib.types.str;
24           description = ''
25             Name of this rule.
26           '';
27           internal = true;
28           readOnly = true;
29         };
30         enable = lib.mkOption {
31           type = lib.types.bool;
32           default = true;
33           description = ''
34             Whether this rule is added to the PAM service config file.
35           '';
36         };
37         order = lib.mkOption {
38           type = lib.types.int;
39           description = ''
40             Order of this rule in the service file. Rules are arranged in ascending order of this value.
42             ::: {.warning}
43             The `order` values for the built-in rules are subject to change. If you assign a constant value to this option, a system update could silently reorder your rule. You could be locked out of your system, or your system could be left wide open. When using this option, set it to a relative offset from another rule's `order` value:
45             ```nix
46             {
47               security.pam.services.login.rules.auth.foo.order =
48                 config.security.pam.services.login.rules.auth.unix.order + 10;
49             }
50             ```
51             :::
52           '';
53         };
54         control = lib.mkOption {
55           type = lib.types.str;
56           description = ''
57             Indicates the behavior of the PAM-API should the module fail to succeed in its authentication task. See `control` in {manpage}`pam.conf(5)` for details.
58           '';
59         };
60         modulePath = lib.mkOption {
61           type = lib.types.str;
62           description = ''
63             Either the full filename of the PAM to be used by the application (it begins with a '/'), or a relative pathname from the default module location. See `module-path` in {manpage}`pam.conf(5)` for details.
64           '';
65         };
66         args = lib.mkOption {
67           type = lib.types.listOf lib.types.str;
68           description = ''
69             Tokens that can be used to modify the specific behavior of the given PAM. Such arguments will be documented for each individual module. See `module-arguments` in {manpage}`pam.conf(5)` for details.
71             Escaping rules for spaces and square brackets are automatically applied.
73             {option}`settings` are automatically added as {option}`args`. It's recommended to use the {option}`settings` option whenever possible so that arguments can be overridden.
74           '';
75         };
76         settings = lib.mkOption {
77           type = moduleSettingsType;
78           default = {};
79           description = ''
80             Settings to add as `module-arguments`.
82             ${moduleSettingsDescription}
83           '';
84         };
85       };
86       config = {
87         inherit name;
88         # Formats an attrset of settings as args for use as `module-arguments`.
89         args = lib.concatLists (lib.flip lib.mapAttrsToList config.settings (name: value:
90           if lib.isBool value
91           then lib.optional value name
92           else lib.optional (value != null) "${name}=${toString value}"
93         ));
94       };
95     }));
96   };
98   package = config.security.pam.package;
99   parentConfig = config;
101   pamOpts = { config, name, ... }: let cfg = config; in let config = parentConfig; in {
103     imports = [
104       (lib.mkRenamedOptionModule [ "enableKwallet" ] [ "kwallet" "enable" ])
105     ];
107     options = {
109       name = lib.mkOption {
110         example = "sshd";
111         type = lib.types.str;
112         description = "Name of the PAM service.";
113       };
115       rules = lib.mkOption {
116         # This option is experimental and subject to breaking changes without notice.
117         visible = false;
119         description = ''
120           PAM rules for this service.
122           ::: {.warning}
123           This option and its suboptions are experimental and subject to breaking changes without notice.
125           If you use this option in your system configuration, you will need to manually monitor this module for any changes. Otherwise, failure to adjust your configuration properly could lead to you being locked out of your system, or worse, your system could be left wide open to attackers.
127           If you share configuration examples that use this option, you MUST include this warning so that users are informed.
129           You may freely use this option within `nixpkgs`, and future changes will account for those use sites.
130           :::
131         '';
132         type = lib.types.submodule {
133           options = lib.genAttrs [ "account" "auth" "password" "session" ] mkRulesTypeOption;
134         };
135       };
137       unixAuth = lib.mkOption {
138         default = true;
139         type = lib.types.bool;
140         description = ''
141           Whether users can log in with passwords defined in
142           {file}`/etc/shadow`.
143         '';
144       };
146       rootOK = lib.mkOption {
147         default = false;
148         type = lib.types.bool;
149         description = ''
150           If set, root doesn't need to authenticate (e.g. for the
151           {command}`useradd` service).
152         '';
153       };
155       p11Auth = lib.mkOption {
156         default = config.security.pam.p11.enable;
157         defaultText = lib.literalExpression "config.security.pam.p11.enable";
158         type = lib.types.bool;
159         description = ''
160           If set, keys listed in
161           {file}`~/.ssh/authorized_keys` and
162           {file}`~/.eid/authorized_certificates`
163           can be used to log in with the associated PKCS#11 tokens.
164         '';
165       };
167       u2fAuth = lib.mkOption {
168         default = config.security.pam.u2f.enable;
169         defaultText = lib.literalExpression "config.security.pam.u2f.enable";
170         type = lib.types.bool;
171         description = ''
172           If set, users listed in
173           {file}`$XDG_CONFIG_HOME/Yubico/u2f_keys` (or
174           {file}`$HOME/.config/Yubico/u2f_keys` if XDG variable is
175           not set) are able to log in with the associated U2F key. Path can be
176           changed using {option}`security.pam.u2f.authFile` option.
177         '';
178       };
180       usshAuth = lib.mkOption {
181         default = false;
182         type = lib.types.bool;
183         description = ''
184           If set, users with an SSH certificate containing an authorized principal
185           in their SSH agent are able to log in. Specific options are controlled
186           using the {option}`security.pam.ussh` options.
188           Note that the  {option}`security.pam.ussh.enable` must also be
189           set for this option to take effect.
190         '';
191       };
193       yubicoAuth = lib.mkOption {
194         default = config.security.pam.yubico.enable;
195         defaultText = lib.literalExpression "config.security.pam.yubico.enable";
196         type = lib.types.bool;
197         description = ''
198           If set, users listed in
199           {file}`~/.yubico/authorized_yubikeys`
200           are able to log in with the associated Yubikey tokens.
201         '';
202       };
204       googleAuthenticator = {
205         enable = lib.mkOption {
206           default = false;
207           type = lib.types.bool;
208           description = ''
209             If set, users with enabled Google Authenticator (created
210             {file}`~/.google_authenticator`) will be required
211             to provide Google Authenticator token to log in.
212           '';
213         };
214       };
216       otpwAuth = lib.mkOption {
217         default = config.security.pam.enableOTPW;
218         defaultText = lib.literalExpression "config.security.pam.enableOTPW";
219         type = lib.types.bool;
220         description = ''
221           If set, the OTPW system will be used (if
222           {file}`~/.otpw` exists).
223         '';
224       };
226       googleOsLoginAccountVerification = lib.mkOption {
227         default = false;
228         type = lib.types.bool;
229         description = ''
230           If set, will use the Google OS Login PAM modules
231           (`pam_oslogin_login`,
232           `pam_oslogin_admin`) to verify possible OS Login
233           users and set sudoers configuration accordingly.
234           This only makes sense to enable for the `sshd` PAM
235           service.
236         '';
237       };
239       googleOsLoginAuthentication = lib.mkOption {
240         default = false;
241         type = lib.types.bool;
242         description = ''
243           If set, will use the `pam_oslogin_login`'s user
244           authentication methods to authenticate users using 2FA.
245           This only makes sense to enable for the `sshd` PAM
246           service.
247         '';
248       };
250       mysqlAuth = lib.mkOption {
251         default = config.users.mysql.enable;
252         defaultText = lib.literalExpression "config.users.mysql.enable";
253         type = lib.types.bool;
254         description = ''
255           If set, the `pam_mysql` module will be used to
256           authenticate users against a MySQL/MariaDB database.
257         '';
258       };
260       fprintAuth = lib.mkOption {
261         default = config.services.fprintd.enable;
262         defaultText = lib.literalExpression "config.services.fprintd.enable";
263         type = lib.types.bool;
264         description = ''
265           If set, fingerprint reader will be used (if exists and
266           your fingerprints are enrolled).
267         '';
268       };
270       oathAuth = lib.mkOption {
271         default = config.security.pam.oath.enable;
272         defaultText = lib.literalExpression "config.security.pam.oath.enable";
273         type = lib.types.bool;
274         description = ''
275           If set, the OATH Toolkit will be used.
276         '';
277       };
279       sshAgentAuth = lib.mkOption {
280         default = false;
281         type = lib.types.bool;
282         description = ''
283           If set, the calling user's SSH agent is used to authenticate
284           against the keys in the calling user's
285           {file}`~/.ssh/authorized_keys`.  This is useful
286           for {command}`sudo` on password-less remote systems.
287         '';
288       };
290       duoSecurity = {
291         enable = lib.mkOption {
292           default = false;
293           type = lib.types.bool;
294           description = ''
295             If set, use the Duo Security pam module
296             `pam_duo` for authentication.  Requires
297             configuration of {option}`security.duosec` options.
298           '';
299         };
300       };
302       startSession = lib.mkOption {
303         default = false;
304         type = lib.types.bool;
305         description = ''
306           If set, the service will register a new session with
307           systemd's login manager.  For local sessions, this will give
308           the user access to audio devices, CD-ROM drives.  In the
309           default PolicyKit configuration, it also allows the user to
310           reboot the system.
311         '';
312       };
314       setEnvironment = lib.mkOption {
315         type = lib.types.bool;
316         default = true;
317         description = ''
318           Whether the service should set the environment variables
319           listed in {option}`environment.sessionVariables`
320           using `pam_env.so`.
321         '';
322       };
324       setLoginUid = lib.mkOption {
325         type = lib.types.bool;
326         description = ''
327           Set the login uid of the process
328           ({file}`/proc/self/loginuid`) for auditing
329           purposes.  The login uid is only set by â€˜entry points’ like
330           {command}`login` and {command}`sshd`, not by
331           commands like {command}`sudo`.
332         '';
333       };
335       ttyAudit = {
336         enable = lib.mkOption {
337           type = lib.types.bool;
338           default = false;
339           description = ''
340             Enable or disable TTY auditing for specified users
341           '';
342         };
344         enablePattern = lib.mkOption {
345           type = lib.types.nullOr lib.types.str;
346           default = null;
347           description = ''
348             For each user matching one of comma-separated
349             glob patterns, enable TTY auditing
350           '';
351         };
353         disablePattern = lib.mkOption {
354           type = lib.types.nullOr lib.types.str;
355           default = null;
356           description = ''
357             For each user matching one of comma-separated
358             glob patterns, disable TTY auditing
359           '';
360         };
362         openOnly = lib.mkOption {
363           type = lib.types.bool;
364           default = false;
365           description = ''
366             Set the TTY audit flag when opening the session,
367             but do not restore it when closing the session.
368             Using this option is necessary for some services
369             that don't fork() to run the authenticated session,
370             such as sudo.
371           '';
372         };
373       };
375       forwardXAuth = lib.mkOption {
376         default = false;
377         type = lib.types.bool;
378         description = ''
379           Whether X authentication keys should be passed from the
380           calling user to the target user (e.g. for
381           {command}`su`)
382         '';
383       };
385       pamMount = lib.mkOption {
386         default = config.security.pam.mount.enable;
387         defaultText = lib.literalExpression "config.security.pam.mount.enable";
388         type = lib.types.bool;
389         description = ''
390           Enable PAM mount (pam_mount) system to mount filesystems on user login.
391         '';
392       };
394       allowNullPassword = lib.mkOption {
395         default = false;
396         type = lib.types.bool;
397         description = ''
398           Whether to allow logging into accounts that have no password
399           set (i.e., have an empty password field in
400           {file}`/etc/passwd` or
401           {file}`/etc/group`).  This does not enable
402           logging into disabled accounts (i.e., that have the password
403           field set to `!`).  Note that regardless of
404           what the pam_unix documentation says, accounts with hashed
405           empty passwords are always allowed to log in.
406         '';
407       };
409       nodelay = lib.mkOption {
410         default = false;
411         type = lib.types.bool;
412         description = ''
413           Whether the delay after typing a wrong password should be disabled.
414         '';
415       };
417       requireWheel = lib.mkOption {
418         default = false;
419         type = lib.types.bool;
420         description = ''
421           Whether to permit root access only to members of group wheel.
422         '';
423       };
425       limits = lib.mkOption {
426         default = [];
427         type = limitsType;
428         description = ''
429           Attribute set describing resource limits.  Defaults to the
430           value of {option}`security.pam.loginLimits`.
431           The meaning of the values is explained in {manpage}`limits.conf(5)`.
432         '';
433       };
435       showMotd = lib.mkOption {
436         default = false;
437         type = lib.types.bool;
438         description = "Whether to show the message of the day.";
439       };
441       makeHomeDir = lib.mkOption {
442         default = false;
443         type = lib.types.bool;
444         description = ''
445           Whether to try to create home directories for users
446           with `$HOME`s pointing to nonexistent
447           locations on session login.
448         '';
449       };
451       updateWtmp = lib.mkOption {
452         default = false;
453         type = lib.types.bool;
454         description = "Whether to update {file}`/var/log/wtmp`.";
455       };
457       logFailures = lib.mkOption {
458         default = false;
459         type = lib.types.bool;
460         description = "Whether to log authentication failures in {file}`/var/log/faillog`.";
461       };
463       enableAppArmor = lib.mkOption {
464         default = false;
465         type = lib.types.bool;
466         description = ''
467           Enable support for attaching AppArmor profiles at the
468           user/group level, e.g., as part of a role based access
469           control scheme.
470         '';
471       };
473       kwallet = {
474         enable = lib.mkOption {
475           default = false;
476           type = lib.types.bool;
477           description = ''
478             If enabled, pam_wallet will attempt to automatically unlock the
479             user's default KDE wallet upon login. If the user has no wallet named
480             "kdewallet", or the login password does not match their wallet
481             password, KDE will prompt separately after login.
482           '';
483         };
485         package = lib.mkPackageOption pkgs.plasma5Packages "kwallet-pam" {
486           pkgsText = "pkgs.plasma5Packages";
487         };
489         forceRun = lib.mkEnableOption null // {
490           description = ''
491             The `force_run` option is used to tell the PAM module for KWallet
492             to forcefully run even if no graphical session (such as a GUI
493             display manager) is detected. This is useful for when you are
494             starting an X Session or a Wayland Session from a TTY. If you
495             intend to log-in from a TTY, it is recommended that you enable
496             this option **and** ensure that `plasma-kwallet-pam.service` is
497             started by `graphical-session.target`.
498           '';
499         };
500       };
502       sssdStrictAccess = lib.mkOption {
503         default = false;
504         type = lib.types.bool;
505         description = "enforce sssd access control";
506       };
508       enableGnomeKeyring = lib.mkOption {
509         default = false;
510         type = lib.types.bool;
511         description = ''
512           If enabled, pam_gnome_keyring will attempt to automatically unlock the
513           user's default Gnome keyring upon login. If the user login password does
514           not match their keyring password, Gnome Keyring will prompt separately
515           after login.
516         '';
517       };
519       failDelay = {
520         enable = lib.mkOption {
521           type = lib.types.bool;
522           default = false;
523           description = ''
524             If enabled, this will replace the `FAIL_DELAY` setting from `login.defs`.
525             Change the delay on failure per-application.
526             '';
527         };
529         delay = lib.mkOption {
530           default = 3000000;
531           type = lib.types.int;
532           example = 1000000;
533           description = "The delay time (in microseconds) on failure.";
534         };
535       };
537       gnupg = {
538         enable = lib.mkOption {
539           type = lib.types.bool;
540           default = false;
541           description = ''
542             If enabled, pam_gnupg will attempt to automatically unlock the
543             user's GPG keys with the login password via
544             {command}`gpg-agent`. The keygrips of all keys to be
545             unlocked should be written to {file}`~/.pam-gnupg`,
546             and can be queried with {command}`gpg -K --with-keygrip`.
547             Presetting passphrases must be enabled by adding
548             `allow-preset-passphrase` in
549             {file}`~/.gnupg/gpg-agent.conf`.
550           '';
551         };
553         noAutostart = lib.mkOption {
554           type = lib.types.bool;
555           default = false;
556           description = ''
557             Don't start {command}`gpg-agent` if it is not running.
558             Useful in conjunction with starting {command}`gpg-agent` as
559             a systemd user service.
560           '';
561         };
563         storeOnly = lib.mkOption {
564           type = lib.types.bool;
565           default = false;
566           description = ''
567             Don't send the password immediately after login, but store for PAM
568             `session`.
569           '';
570         };
571       };
573       zfs = lib.mkOption {
574         default = config.security.pam.zfs.enable;
575         defaultText = lib.literalExpression "config.security.pam.zfs.enable";
576         type = lib.types.bool;
577         description = ''
578           Enable unlocking and mounting of encrypted ZFS home dataset at login.
579         '';
580       };
582       text = lib.mkOption {
583         type = lib.types.nullOr lib.types.lines;
584         description = "Contents of the PAM service file.";
585       };
587     };
589     # The resulting /etc/pam.d/* file contents are verified in
590     # nixos/tests/pam/pam-file-contents.nix. Please update tests there when
591     # changing the derivation.
592     config = {
593       name = lib.mkDefault name;
594       setLoginUid = lib.mkDefault cfg.startSession;
595       limits = lib.mkDefault config.security.pam.loginLimits;
597       text = let
598         ensureUniqueOrder = type: rules:
599           let
600             checkPair = a: b: assert lib.assertMsg (a.order != b.order) "security.pam.services.${name}.rules.${type}: rules '${a.name}' and '${b.name}' cannot have the same order value (${toString a.order})"; b;
601             checked = lib.zipListsWith checkPair rules (lib.drop 1 rules);
602           in lib.take 1 rules ++ checked;
603         # Formats a string for use in `module-arguments`. See `man pam.conf`.
604         formatModuleArgument = token:
605           if lib.hasInfix " " token
606           then "[${lib.replaceStrings ["]"] ["\\]"] token}]"
607           else token;
608         formatRules = type: lib.pipe cfg.rules.${type} [
609           lib.attrValues
610           (lib.filter (rule: rule.enable))
611           (lib.sort (a: b: a.order < b.order))
612           (ensureUniqueOrder type)
613           (map (rule: lib.concatStringsSep " " (
614             [ type rule.control rule.modulePath ]
615             ++ map formatModuleArgument rule.args
616             ++ [ "# ${rule.name} (order ${toString rule.order})" ]
617           )))
618           (lib.concatStringsSep "\n")
619         ];
620       in lib.mkDefault ''
621         # Account management.
622         ${formatRules "account"}
624         # Authentication management.
625         ${formatRules "auth"}
627         # Password management.
628         ${formatRules "password"}
630         # Session management.
631         ${formatRules "session"}
632       '';
634       # !!! TODO: move the LDAP stuff to the LDAP module, and the
635       # Samba stuff to the Samba module.  This requires that the PAM
636       # module provides the right hooks.
637       rules = let
638         autoOrderRules = lib.flip lib.pipe [
639           (lib.imap1 (index: rule: rule // { order = lib.mkDefault (10000 + index * 100); } ))
640           (map (rule: lib.nameValuePair rule.name (removeAttrs rule [ "name" ])))
641           lib.listToAttrs
642         ];
643       in {
644         account = autoOrderRules [
645           { name = "ldap"; enable = use_ldap; control = "sufficient"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; }
646           { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
647             config_file = "/etc/security/pam_mysql.conf";
648           }; }
649           { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${config.services.kanidm.package}/lib/pam_kanidm.so"; settings = {
650             ignore_unknown_user = true;
651           }; }
652           { name = "sss"; enable = config.services.sssd.enable; control = if cfg.sssdStrictAccess then "[default=bad success=ok user_unknown=ignore]" else "sufficient"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; }
653           { name = "krb5"; enable = config.security.pam.krb5.enable; control = "sufficient"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; }
654           { name = "oslogin_login"; enable = cfg.googleOsLoginAccountVerification; control = "[success=ok ignore=ignore default=die]"; modulePath = "${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so"; }
655           { name = "oslogin_admin"; enable = cfg.googleOsLoginAccountVerification; control = "[success=ok default=ignore]"; modulePath = "${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_admin.so"; }
656           { name = "systemd_home"; enable = config.services.homed.enable; control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
657           # The required pam_unix.so module has to come after all the sufficient modules
658           # because otherwise, the account lookup will fail if the user does not exist
659           # locally, for example with MySQL- or LDAP-auth.
660           { name = "unix"; control = "required"; modulePath = "${package}/lib/security/pam_unix.so"; }
661         ];
663         auth = autoOrderRules ([
664           { name = "oslogin_login"; enable = cfg.googleOsLoginAuthentication; control = "[success=done perm_denied=die default=ignore]"; modulePath = "${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so"; }
665           { name = "rootok"; enable = cfg.rootOK; control = "sufficient"; modulePath = "${package}/lib/security/pam_rootok.so"; }
666           { name = "wheel"; enable = cfg.requireWheel; control = "required"; modulePath = "${package}/lib/security/pam_wheel.so"; settings = {
667             use_uid = true;
668           }; }
669           { name = "faillock"; enable = cfg.logFailures; control = "required"; modulePath = "${package}/lib/security/pam_faillock.so"; }
670           { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
671             config_file = "/etc/security/pam_mysql.conf";
672           }; }
673           { name = "ssh_agent_auth"; enable = config.security.pam.sshAgentAuth.enable && cfg.sshAgentAuth; control = "sufficient"; modulePath = "${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so"; settings = {
674             file = lib.concatStringsSep ":" config.security.pam.sshAgentAuth.authorizedKeysFiles;
675           }; }
676           (let p11 = config.security.pam.p11; in { name = "p11"; enable = cfg.p11Auth; control = p11.control; modulePath = "${pkgs.pam_p11}/lib/security/pam_p11.so"; args = [
677             "${pkgs.opensc}/lib/opensc-pkcs11.so"
678           ]; })
679           (let u2f = config.security.pam.u2f; in { name = "u2f"; enable = cfg.u2fAuth; control = u2f.control; modulePath = "${pkgs.pam_u2f}/lib/security/pam_u2f.so"; inherit (u2f) settings; })
680           (let ussh = config.security.pam.ussh; in { name = "ussh"; enable = config.security.pam.ussh.enable && cfg.usshAuth; control = ussh.control; modulePath = "${pkgs.pam_ussh}/lib/security/pam_ussh.so"; settings = {
681             ca_file = ussh.caFile;
682             authorized_principals = ussh.authorizedPrincipals;
683             authorized_principals_file = ussh.authorizedPrincipalsFile;
684             inherit (ussh) group;
685           }; })
686           (let oath = config.security.pam.oath; in { name = "oath"; enable = cfg.oathAuth; control = "requisite"; modulePath = "${pkgs.oath-toolkit}/lib/security/pam_oath.so"; settings = {
687             inherit (oath) window digits;
688             usersfile = oath.usersFile;
689           }; })
690           (let yubi = config.security.pam.yubico; in { name = "yubico"; enable = cfg.yubicoAuth; control = yubi.control; modulePath = "${pkgs.yubico-pam}/lib/security/pam_yubico.so"; settings = {
691             inherit (yubi) mode debug;
692             chalresp_path = yubi.challengeResponsePath;
693             id = lib.mkIf (yubi.mode == "client") yubi.id;
694           }; })
695           (let dp9ik = config.security.pam.dp9ik; in { name = "p9"; enable = dp9ik.enable; control = dp9ik.control; modulePath = "${pkgs.pam_dp9ik}/lib/security/pam_p9.so"; args = [
696             dp9ik.authserver
697           ]; })
698           { name = "fprintd"; enable = cfg.fprintAuth; control = "sufficient"; modulePath = "${config.services.fprintd.package}/lib/security/pam_fprintd.so"; }
699         ] ++
700           # Modules in this block require having the password set in PAM_AUTHTOK.
701           # pam_unix is marked as 'sufficient' on NixOS which means nothing will run
702           # after it succeeds. Certain modules need to run after pam_unix
703           # prompts the user for password so we run it once with 'optional' at an
704           # earlier point and it will run again with 'sufficient' further down.
705           # We use try_first_pass the second time to avoid prompting password twice.
706           #
707           # The same principle applies to systemd-homed
708           (lib.optionals ((cfg.unixAuth || config.services.homed.enable) &&
709             (config.security.pam.enableEcryptfs
710               || config.security.pam.enableFscrypt
711               || cfg.pamMount
712               || cfg.kwallet.enable
713               || cfg.enableGnomeKeyring
714               || config.services.intune.enable
715               || cfg.googleAuthenticator.enable
716               || cfg.gnupg.enable
717               || cfg.failDelay.enable
718               || cfg.duoSecurity.enable
719               || cfg.zfs))
720             [
721               { name = "systemd_home-early"; enable = config.services.homed.enable; control = "optional"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
722               { name = "unix-early"; enable = cfg.unixAuth; control = "optional"; modulePath = "${package}/lib/security/pam_unix.so"; settings = {
723                 nullok = cfg.allowNullPassword;
724                 inherit (cfg) nodelay;
725                 likeauth = true;
726               }; }
727               { name = "ecryptfs"; enable = config.security.pam.enableEcryptfs; control = "optional"; modulePath = "${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"; settings = {
728                 unwrap = true;
729               }; }
730               { name = "fscrypt"; enable = config.security.pam.enableFscrypt; control = "optional"; modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so"; }
731               { name = "zfs_key"; enable = cfg.zfs; control = "optional"; modulePath = "${config.boot.zfs.package}/lib/security/pam_zfs_key.so"; settings = {
732                 inherit (config.security.pam.zfs) homes;
733               }; }
734               { name = "mount"; enable = cfg.pamMount; control = "optional"; modulePath = "${pkgs.pam_mount}/lib/security/pam_mount.so"; settings = {
735                 disable_interactive = true;
736               }; }
737               { name = "kwallet"; enable = cfg.kwallet.enable; control = "optional"; modulePath = "${cfg.kwallet.package}/lib/security/pam_kwallet5.so"; }
738               { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so"; }
739               { name = "intune"; enable = config.services.intune.enable; control = "optional"; modulePath = "${pkgs.intune-portal}/lib/security/pam_intune.so"; }
740               { name = "gnupg"; enable = cfg.gnupg.enable; control = "optional"; modulePath = "${pkgs.pam_gnupg}/lib/security/pam_gnupg.so"; settings = {
741                 store-only = cfg.gnupg.storeOnly;
742               }; }
743               { name = "faildelay"; enable = cfg.failDelay.enable; control = "optional"; modulePath = "${package}/lib/security/pam_faildelay.so"; settings = {
744                 inherit (cfg.failDelay) delay;
745               }; }
746               { name = "google_authenticator"; enable = cfg.googleAuthenticator.enable; control = "required"; modulePath = "${pkgs.google-authenticator}/lib/security/pam_google_authenticator.so"; settings = {
747                 no_increment_hotp = true;
748               }; }
749               { name = "duo"; enable = cfg.duoSecurity.enable; control = "required"; modulePath = "${pkgs.duo-unix}/lib/security/pam_duo.so"; }
750             ]) ++ [
751           { name = "systemd_home"; enable = config.services.homed.enable; control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
752           { name = "unix"; enable = cfg.unixAuth; control = "sufficient"; modulePath = "${package}/lib/security/pam_unix.so"; settings = {
753             nullok = cfg.allowNullPassword;
754             inherit (cfg) nodelay;
755             likeauth = true;
756             try_first_pass = true;
757           }; }
758           { name = "otpw"; enable = cfg.otpwAuth; control = "sufficient"; modulePath = "${pkgs.otpw}/lib/security/pam_otpw.so"; }
759           { name = "ldap"; enable = use_ldap; control = "sufficient"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; settings = {
760             use_first_pass = true;
761           }; }
762           { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${config.services.kanidm.package}/lib/pam_kanidm.so"; settings = {
763             ignore_unknown_user = true;
764             use_first_pass = true;
765           }; }
766           { name = "sss"; enable = config.services.sssd.enable; control = "sufficient"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; settings = {
767             use_first_pass = true;
768           }; }
769           { name = "krb5"; enable = config.security.pam.krb5.enable; control = "[default=ignore success=1 service_err=reset]"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; settings = {
770             use_first_pass = true;
771           }; }
772           { name = "ccreds-validate"; enable = config.security.pam.krb5.enable; control = "[default=die success=done]"; modulePath = "${pam_ccreds}/lib/security/pam_ccreds.so"; settings = {
773             action = "validate";
774             use_first_pass = true;
775           }; }
776           { name = "ccreds-store"; enable = config.security.pam.krb5.enable; control = "sufficient"; modulePath = "${pam_ccreds}/lib/security/pam_ccreds.so"; settings = {
777             action = "store";
778             use_first_pass = true;
779           }; }
780           { name = "deny"; control = "required"; modulePath = "${package}/lib/security/pam_deny.so"; }
781         ]);
783         password = autoOrderRules [
784           { name = "systemd_home"; enable = config.services.homed.enable; control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
785           { name = "unix"; control = "sufficient"; modulePath = "${package}/lib/security/pam_unix.so"; settings = {
786             nullok = true;
787             yescrypt = true;
788           }; }
789           { name = "ecryptfs"; enable = config.security.pam.enableEcryptfs; control = "optional"; modulePath = "${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"; }
790           { name = "fscrypt"; enable = config.security.pam.enableFscrypt; control = "optional"; modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so"; }
791           { name = "zfs_key"; enable = cfg.zfs; control = "optional"; modulePath = "${config.boot.zfs.package}/lib/security/pam_zfs_key.so"; settings = {
792             inherit (config.security.pam.zfs) homes;
793           }; }
794           { name = "mount"; enable = cfg.pamMount; control = "optional"; modulePath = "${pkgs.pam_mount}/lib/security/pam_mount.so"; }
795           { name = "ldap"; enable = use_ldap; control = "sufficient"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; }
796           { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
797             config_file = "/etc/security/pam_mysql.conf";
798           }; }
799           { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${config.services.kanidm.package}/lib/pam_kanidm.so"; }
800           { name = "sss"; enable = config.services.sssd.enable; control = "sufficient"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; }
801           { name = "krb5"; enable = config.security.pam.krb5.enable; control = "sufficient"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; settings = {
802             use_first_pass = true;
803           }; }
804           { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so"; settings = {
805             use_authtok = true;
806           }; }
807         ];
809         session = autoOrderRules [
810           { name = "env"; enable = cfg.setEnvironment; control = "required"; modulePath = "${package}/lib/security/pam_env.so"; settings = {
811             conffile = "/etc/pam/environment";
812             readenv = 0;
813           }; }
814           { name = "unix"; control = "required"; modulePath = "${package}/lib/security/pam_unix.so"; }
815           { name = "loginuid"; enable = cfg.setLoginUid; control = if config.boot.isContainer then "optional" else "required"; modulePath = "${package}/lib/security/pam_loginuid.so"; }
816           { name = "tty_audit"; enable = cfg.ttyAudit.enable; control = "required"; modulePath = "${package}/lib/security/pam_tty_audit.so"; settings = {
817             open_only = cfg.ttyAudit.openOnly;
818             enable = cfg.ttyAudit.enablePattern;
819             disable = cfg.ttyAudit.disablePattern;
820           }; }
821           { name = "systemd_home"; enable = config.services.homed.enable; control = "required"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
822           { name = "mkhomedir"; enable = cfg.makeHomeDir; control = "required"; modulePath = "${package}/lib/security/pam_mkhomedir.so"; settings = {
823             silent = true;
824             skel = config.security.pam.makeHomeDir.skelDirectory;
825             inherit (config.security.pam.makeHomeDir) umask;
826           }; }
827           { name = "lastlog"; enable = cfg.updateWtmp; control = "required"; modulePath = "${package}/lib/security/pam_lastlog.so"; settings = {
828             silent = true;
829           }; }
830           { name = "ecryptfs"; enable = config.security.pam.enableEcryptfs; control = "optional"; modulePath = "${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"; }
831           # Work around https://github.com/systemd/systemd/issues/8598
832           # Skips the pam_fscrypt module for systemd-user sessions which do not have a password
833           # anyways.
834           # See also https://github.com/google/fscrypt/issues/95
835           { name = "fscrypt-skip-systemd"; enable = config.security.pam.enableFscrypt; control = "[success=1 default=ignore]"; modulePath = "${package}/lib/security/pam_succeed_if.so"; args = [
836             "service" "=" "systemd-user"
837           ]; }
838           { name = "fscrypt"; enable = config.security.pam.enableFscrypt; control = "optional"; modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so"; }
839           { name = "zfs_key-skip-systemd"; enable = cfg.zfs; control = "[success=1 default=ignore]"; modulePath = "${package}/lib/security/pam_succeed_if.so"; args = [
840             "service" "=" "systemd-user"
841           ]; }
842           { name = "zfs_key"; enable = cfg.zfs; control = "optional"; modulePath = "${config.boot.zfs.package}/lib/security/pam_zfs_key.so"; settings = {
843             inherit (config.security.pam.zfs) homes;
844             nounmount = config.security.pam.zfs.noUnmount;
845           }; }
846           { name = "mount"; enable = cfg.pamMount; control = "optional"; modulePath = "${pkgs.pam_mount}/lib/security/pam_mount.so"; settings = {
847             disable_interactive = true;
848           }; }
849           { name = "ldap"; enable = use_ldap; control = "optional"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; }
850           { name = "mysql"; enable = cfg.mysqlAuth; control = "optional"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
851             config_file = "/etc/security/pam_mysql.conf";
852           }; }
853           { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "optional"; modulePath = "${config.services.kanidm.package}/lib/pam_kanidm.so"; }
854           { name = "sss"; enable = config.services.sssd.enable; control = "optional"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; }
855           { name = "krb5"; enable = config.security.pam.krb5.enable; control = "optional"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; }
856           { name = "otpw"; enable = cfg.otpwAuth; control = "optional"; modulePath = "${pkgs.otpw}/lib/security/pam_otpw.so"; }
857           { name = "systemd"; enable = cfg.startSession; control = "optional"; modulePath = "${config.systemd.package}/lib/security/pam_systemd.so"; }
858           { name = "xauth"; enable = cfg.forwardXAuth; control = "optional"; modulePath = "${package}/lib/security/pam_xauth.so"; settings = {
859             xauthpath = "${pkgs.xorg.xauth}/bin/xauth";
860             systemuser = 99;
861           }; }
862           { name = "limits"; enable = cfg.limits != []; control = "required"; modulePath = "${package}/lib/security/pam_limits.so"; settings = {
863             conf = "${makeLimitsConf cfg.limits}";
864           }; }
865           { name = "motd"; enable = cfg.showMotd && (config.users.motd != "" || config.users.motdFile != null); control = "optional"; modulePath = "${package}/lib/security/pam_motd.so"; settings = {
866             inherit motd;
867           }; }
868           { name = "apparmor"; enable = cfg.enableAppArmor && config.security.apparmor.enable; control = "optional"; modulePath = "${pkgs.apparmor-pam}/lib/security/pam_apparmor.so"; settings = {
869             order = "user,group,default";
870             debug = true;
871           }; }
872           { name = "kwallet"; enable = cfg.kwallet.enable; control = "optional"; modulePath = "${cfg.kwallet.package}/lib/security/pam_kwallet5.so"; settings = lib.mkIf cfg.kwallet.forceRun { force_run = true; }; }
873           { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so"; settings = {
874             auto_start = true;
875           }; }
876           { name = "gnupg"; enable = cfg.gnupg.enable; control = "optional"; modulePath = "${pkgs.pam_gnupg}/lib/security/pam_gnupg.so"; settings = {
877             no-autostart = cfg.gnupg.noAutostart;
878           }; }
879           { name = "intune"; enable = config.services.intune.enable; control = "optional"; modulePath = "${pkgs.intune-portal}/lib/security/pam_intune.so"; }
880         ];
881       };
882     };
884   };
887   inherit (pkgs) pam_krb5 pam_ccreds;
889   use_ldap = (config.users.ldap.enable && config.users.ldap.loginPam);
890   pam_ldap = if config.users.ldap.daemon.enable then pkgs.nss_pam_ldapd else pkgs.pam_ldap;
892   # Create a limits.conf(5) file.
893   makeLimitsConf = limits:
894     pkgs.writeText "limits.conf"
895        (lib.concatMapStrings ({ domain, type, item, value }:
896          "${domain} ${type} ${item} ${toString value}\n")
897          limits);
899   limitsType = with lib.types; listOf (submodule ({ ... }: {
900     options = {
901       domain = lib.mkOption {
902         description = "Username, groupname, or wildcard this limit applies to";
903         example = "@wheel";
904         type = str;
905       };
907       type = lib.mkOption {
908         description = "Type of this limit";
909         type = enum [ "-" "hard" "soft" ];
910         default = "-";
911       };
913       item = lib.mkOption {
914         description = "Item this limit applies to";
915         type = enum [
916           "core"
917           "data"
918           "fsize"
919           "memlock"
920           "nofile"
921           "rss"
922           "stack"
923           "cpu"
924           "nproc"
925           "as"
926           "maxlogins"
927           "maxsyslogins"
928           "priority"
929           "locks"
930           "sigpending"
931           "msgqueue"
932           "nice"
933           "rtprio"
934         ];
935       };
937       value = lib.mkOption {
938         description = "Value of this limit";
939         type = oneOf [ str int ];
940       };
941     };
942   }));
944   motd = if config.users.motdFile == null
945          then pkgs.writeText "motd" config.users.motd
946          else config.users.motdFile;
948   makePAMService = name: service:
949     { name = "pam.d/${name}";
950       value.source = pkgs.writeText "${name}.pam" service.text;
951     };
953   optionalSudoConfigForSSHAgentAuth = lib.optionalString config.security.pam.sshAgentAuth.enable ''
954     # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic.
955     Defaults env_keep+=SSH_AUTH_SOCK
956   '';
962   meta.maintainers = [ lib.maintainers.majiir ];
964   imports = [
965     (lib.mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ])
966     (lib.mkRenamedOptionModule [ "security" "pam" "enableSSHAgentAuth" ] [ "security" "pam" "sshAgentAuth" "enable" ])
967     (lib.mkRenamedOptionModule [ "security" "pam" "u2f" "authFile" ] [ "security" "pam" "u2f" "settings" "authfile" ])
968     (lib.mkRenamedOptionModule [ "security" "pam" "u2f" "appId" ] [ "security" "pam" "u2f" "settings" "appid" ])
969     (lib.mkRenamedOptionModule [ "security" "pam" "u2f" "origin" ] [ "security" "pam" "u2f" "settings" "origin" ])
970     (lib.mkRenamedOptionModule [ "security" "pam" "u2f" "debug" ] [ "security" "pam" "u2f" "settings" "debug" ])
971     (lib.mkRenamedOptionModule [ "security" "pam" "u2f" "interactive" ] [ "security" "pam" "u2f" "settings" "interactive" ])
972     (lib.mkRenamedOptionModule [ "security" "pam" "u2f" "cue" ] [ "security" "pam" "u2f" "settings" "cue" ])
973   ];
975   ###### interface
977   options = {
979     security.pam.package = lib.mkPackageOption pkgs "pam" { };
981     security.pam.loginLimits = lib.mkOption {
982       default = [];
983       type = limitsType;
984       example =
985         [ { domain = "ftp";
986             type   = "hard";
987             item   = "nproc";
988             value  = "0";
989           }
990           { domain = "@student";
991             type   = "-";
992             item   = "maxlogins";
993             value  = "4";
994           }
995        ];
997      description = ''
998        Define resource limits that should apply to users or groups.
999        Each item in the list should be an attribute set with a
1000        {var}`domain`, {var}`type`,
1001        {var}`item`, and {var}`value`
1002        attribute.  The syntax and semantics of these attributes
1003        must be that described in {manpage}`limits.conf(5)`.
1005        Note that these limits do not apply to systemd services,
1006        whose limits can be changed via {option}`systemd.extraConfig`
1007        instead.
1008      '';
1009     };
1011     security.pam.services = lib.mkOption {
1012       default = {};
1013       type = with lib.types; attrsOf (submodule pamOpts);
1014       description = ''
1015           This option defines the PAM services.  A service typically
1016           corresponds to a program that uses PAM,
1017           e.g. {command}`login` or {command}`passwd`.
1018           Each attribute of this set defines a PAM service, with the attribute name
1019           defining the name of the service.
1020         '';
1021     };
1023     security.pam.makeHomeDir.skelDirectory = lib.mkOption {
1024       type = lib.types.str;
1025       default = "/var/empty";
1026       example =  "/etc/skel";
1027       description = ''
1028         Path to skeleton directory whose contents are copied to home
1029         directories newly created by `pam_mkhomedir`.
1030       '';
1031     };
1033     security.pam.makeHomeDir.umask = lib.mkOption {
1034       type = lib.types.str;
1035       default = "0077";
1036       example = "0022";
1037       description = ''
1038         The user file mode creation mask to use on home directories
1039         newly created by `pam_mkhomedir`.
1040       '';
1041     };
1043     security.pam.sshAgentAuth = {
1044       enable = lib.mkEnableOption ''
1045         authenticating using a signature performed by the ssh-agent.
1046         This allows using SSH keys exclusively, instead of passwords, for instance on remote machines
1047       '';
1049       authorizedKeysFiles = lib.mkOption {
1050         type = with lib.types; listOf str;
1051         description = ''
1052           A list of paths to files in OpenSSH's `authorized_keys` format, containing
1053           the keys that will be trusted by the `pam_ssh_agent_auth` module.
1055           The following patterns are expanded when interpreting the path:
1056           - `%f` and `%H` respectively expand to the fully-qualified and short hostname ;
1057           - `%u` expands to the username ;
1058           - `~` or `%h` expands to the user's home directory.
1060           ::: {.note}
1061           Specifying user-writeable files here result in an insecure configuration:  a malicious process
1062           can then edit such an authorized_keys file and bypass the ssh-agent-based authentication.
1064           See [issue #31611](https://github.com/NixOS/nixpkgs/issues/31611)
1065           :::
1066         '';
1067         default = [ "/etc/ssh/authorized_keys.d/%u" ];
1068       };
1069     };
1071     security.pam.enableOTPW = lib.mkEnableOption "the OTPW (one-time password) PAM module";
1073     security.pam.dp9ik = {
1074       enable = lib.mkEnableOption ''
1075           the dp9ik pam module provided by tlsclient.
1077           If set, users can be authenticated against the 9front
1078           authentication server given in {option}`security.pam.dp9ik.authserver`
1079         '';
1080       control = lib.mkOption {
1081         default = "sufficient";
1082         type = lib.types.str;
1083         description = ''
1084           This option sets the pam "control" used for this module.
1085         '';
1086       };
1087       authserver = lib.mkOption {
1088         default = null;
1089         type = with lib.types; nullOr str;
1090         description = ''
1091           This controls the hostname for the 9front authentication server
1092           that users will be authenticated against.
1093         '';
1094       };
1095     };
1097     security.pam.krb5 = {
1098       enable = lib.mkOption {
1099         default = config.security.krb5.enable;
1100         defaultText = lib.literalExpression "config.security.krb5.enable";
1101         type = lib.types.bool;
1102         description = ''
1103           Enables Kerberos PAM modules (`pam-krb5`,
1104           `pam-ccreds`).
1106           If set, users can authenticate with their Kerberos password.
1107           This requires a valid Kerberos configuration
1108           (`config.security.krb5.enable` should be set to
1109           `true`).
1111           Note that the Kerberos PAM modules are not necessary when using SSS
1112           to handle Kerberos authentication.
1113         '';
1114       };
1115     };
1117     security.pam.p11 = {
1118       enable = lib.mkOption {
1119         default = false;
1120         type = lib.types.bool;
1121         description = ''
1122           Enables P11 PAM (`pam_p11`) module.
1124           If set, users can log in with SSH keys and PKCS#11 tokens.
1126           More information can be found [here](https://github.com/OpenSC/pam_p11).
1127         '';
1128       };
1130       control = lib.mkOption {
1131         default = "sufficient";
1132         type = lib.types.enum [ "required" "requisite" "sufficient" "lib.optional" ];
1133         description = ''
1134           This option sets pam "control".
1135           If you want to have multi factor authentication, use "required".
1136           If you want to use the PKCS#11 device instead of the regular password,
1137           use "sufficient".
1139           Read
1140           {manpage}`pam.conf(5)`
1141           for better understanding of this option.
1142         '';
1143       };
1144     };
1146     security.pam.u2f = {
1147       enable = lib.mkOption {
1148         default = false;
1149         type = lib.types.bool;
1150         description = ''
1151           Enables U2F PAM (`pam-u2f`) module.
1153           If set, users listed in
1154           {file}`$XDG_CONFIG_HOME/Yubico/u2f_keys` (or
1155           {file}`$HOME/.config/Yubico/u2f_keys` if XDG variable is
1156           not set) are able to log in with the associated U2F key. The path can
1157           be changed using {option}`security.pam.u2f.authFile` option.
1159           File format is:
1160           `username:first_keyHandle,first_public_key: second_keyHandle,second_public_key`
1161           This file can be generated using {command}`pamu2fcfg` command.
1163           More information can be found [here](https://developers.yubico.com/pam-u2f/).
1164         '';
1165       };
1167       control = lib.mkOption {
1168         default = "sufficient";
1169         type = lib.types.enum [ "required" "requisite" "sufficient" "optional" ];
1170         description = ''
1171           This option sets pam "control".
1172           If you want to have multi factor authentication, use "required".
1173           If you want to use U2F device instead of regular password, use "sufficient".
1175           Read
1176           {manpage}`pam.conf(5)`
1177           for better understanding of this option.
1178         '';
1179       };
1181       settings = lib.mkOption {
1182         type = lib.types.submodule {
1183           freeformType = moduleSettingsType;
1185           options = {
1186             authfile = lib.mkOption {
1187               default = null;
1188               type = with lib.types; nullOr path;
1189               description = ''
1190                 By default `pam-u2f` module reads the keys from
1191                 {file}`$XDG_CONFIG_HOME/Yubico/u2f_keys` (or
1192                 {file}`$HOME/.config/Yubico/u2f_keys` if XDG variable is
1193                 not set).
1195                 If you want to change auth file locations or centralize database (for
1196                 example use {file}`/etc/u2f-mappings`) you can set this
1197                 option.
1199                 File format is:
1200                 `username:first_keyHandle,first_public_key: second_keyHandle,second_public_key`
1201                 This file can be generated using {command}`pamu2fcfg` command.
1203                 More information can be found [here](https://developers.yubico.com/pam-u2f/).
1204               '';
1205             };
1207             appid = lib.mkOption {
1208               default = null;
1209               type = with lib.types; nullOr str;
1210               description = ''
1211                   By default `pam-u2f` module sets the application
1212                   ID to `pam://$HOSTNAME`.
1214                   When using {command}`pamu2fcfg`, you can specify your
1215                   application ID with the `-i` flag.
1217                   More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
1218               '';
1219             };
1221             origin = lib.mkOption {
1222               default = null;
1223               type = with lib.types; nullOr str;
1224               description = ''
1225                   By default `pam-u2f` module sets the origin
1226                   to `pam://$HOSTNAME`.
1227                   Setting origin to an host independent value will allow you to
1228                   reuse credentials across machines
1230                   When using {command}`pamu2fcfg`, you can specify your
1231                   application ID with the `-o` flag.
1233                   More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
1234               '';
1235             };
1237             debug = lib.mkOption {
1238               default = false;
1239               type = lib.types.bool;
1240               description = ''
1241                 Debug output to stderr.
1242               '';
1243             };
1245             interactive = lib.mkOption {
1246               default = false;
1247               type = lib.types.bool;
1248               description = ''
1249                 Set to prompt a message and wait before testing the presence of a U2F device.
1250                 Recommended if your device doesn’t have a tactile trigger.
1251               '';
1252             };
1254             cue = lib.mkOption {
1255               default = false;
1256               type = lib.types.bool;
1257               description = ''
1258                 By default `pam-u2f` module does not inform user
1259                 that he needs to use the u2f device, it just waits without a prompt.
1261                 If you set this option to `true`,
1262                 `cue` option is added to `pam-u2f`
1263                 module and reminder message will be displayed.
1264               '';
1265             };
1266           };
1267         };
1268         default = { };
1269         example = {
1270           authfile = "/etc/u2f_keys";
1271           authpending_file = "";
1272           userpresence = 0;
1273           pinverification = 1;
1274         };
1275         description = ''
1276           Options to pass to the PAM module.
1278           ${moduleSettingsDescription}
1279         '';
1280       };
1281     };
1283     security.pam.ussh = {
1284       enable = lib.mkOption {
1285         default = false;
1286         type = lib.types.bool;
1287         description = ''
1288           Enables Uber's USSH PAM (`pam-ussh`) module.
1290           This is similar to `pam-ssh-agent`, except that
1291           the presence of a CA-signed SSH key with a valid principal is checked
1292           instead.
1294           Note that this module must both be enabled using this option and on a
1295           per-PAM-service level as well (using `usshAuth`).
1297           More information can be found [here](https://github.com/uber/pam-ussh).
1298         '';
1299       };
1301       caFile = lib.mkOption {
1302         default = null;
1303         type = with lib.types; nullOr path;
1304         description = ''
1305           By default `pam-ussh` reads the trusted user CA keys
1306           from {file}`/etc/ssh/trusted_user_ca`.
1308           This should be set the same as your `TrustedUserCAKeys`
1309           option for sshd.
1310         '';
1311       };
1313       authorizedPrincipals = lib.mkOption {
1314         default = null;
1315         type = with lib.types; nullOr commas;
1316         description = ''
1317           Comma-separated list of authorized principals to permit; if the user
1318           presents a certificate with one of these principals, then they will be
1319           authorized.
1321           Note that `pam-ussh` also requires that the certificate
1322           contain a principal matching the user's username. The principals from
1323           this list are in addition to those principals.
1325           Mutually exclusive with `authorizedPrincipalsFile`.
1326         '';
1327       };
1329       authorizedPrincipalsFile = lib.mkOption {
1330         default = null;
1331         type = with lib.types; nullOr path;
1332         description = ''
1333           Path to a list of principals; if the user presents a certificate with
1334           one of these principals, then they will be authorized.
1336           Note that `pam-ussh` also requires that the certificate
1337           contain a principal matching the user's username. The principals from
1338           this file are in addition to those principals.
1340           Mutually exclusive with `authorizedPrincipals`.
1341         '';
1342       };
1344       group = lib.mkOption {
1345         default = null;
1346         type = with lib.types; nullOr str;
1347         description = ''
1348           If set, then the authenticating user must be a member of this group
1349           to use this module.
1350         '';
1351       };
1353       control = lib.mkOption {
1354         default = "sufficient";
1355         type = lib.types.enum [ "required" "requisite" "sufficient" "optional" ];
1356         description = ''
1357           This option sets pam "control".
1358           If you want to have multi factor authentication, use "required".
1359           If you want to use the SSH certificate instead of the regular password,
1360           use "sufficient".
1362           Read
1363           {manpage}`pam.conf(5)`
1364           for better understanding of this option.
1365         '';
1366       };
1367     };
1369     security.pam.yubico = {
1370       enable = lib.mkOption {
1371         default = false;
1372         type = lib.types.bool;
1373         description = ''
1374           Enables Yubico PAM (`yubico-pam`) module.
1376           If set, users listed in
1377           {file}`~/.yubico/authorized_yubikeys`
1378           are able to log in with the associated Yubikey tokens.
1380           The file must have only one line:
1381           `username:yubikey_token_id1:yubikey_token_id2`
1382           More information can be found [here](https://developers.yubico.com/yubico-pam/).
1383         '';
1384       };
1385       control = lib.mkOption {
1386         default = "sufficient";
1387         type = lib.types.enum [ "required" "requisite" "sufficient" "optional" ];
1388         description = ''
1389           This option sets pam "control".
1390           If you want to have multi factor authentication, use "required".
1391           If you want to use Yubikey instead of regular password, use "sufficient".
1393           Read
1394           {manpage}`pam.conf(5)`
1395           for better understanding of this option.
1396         '';
1397       };
1398       id = lib.mkOption {
1399         example = "42";
1400         type = lib.types.str;
1401         description = "client id";
1402       };
1404       debug = lib.mkOption {
1405         default = false;
1406         type = lib.types.bool;
1407         description = ''
1408           Debug output to stderr.
1409         '';
1410       };
1411       mode = lib.mkOption {
1412         default = "client";
1413         type = lib.types.enum [ "client" "challenge-response" ];
1414         description = ''
1415           Mode of operation.
1417           Use "client" for online validation with a YubiKey validation service such as
1418           the YubiCloud.
1420           Use "challenge-response" for offline validation using YubiKeys with HMAC-SHA-1
1421           Challenge-Response configurations. See the man-page ykpamcfg(1) for further
1422           details on how to configure offline Challenge-Response validation.
1424           More information can be found [here](https://developers.yubico.com/yubico-pam/Authentication_Using_Challenge-Response.html).
1425         '';
1426       };
1427       challengeResponsePath = lib.mkOption {
1428         default = null;
1429         type = lib.types.nullOr lib.types.path;
1430         description = ''
1431           If not null, set the path used by yubico pam module where the challenge expected response is stored.
1433           More information can be found [here](https://developers.yubico.com/yubico-pam/Authentication_Using_Challenge-Response.html).
1434         '';
1435       };
1436     };
1438     security.pam.zfs = {
1439       enable = lib.mkOption {
1440         default = false;
1441         type = lib.types.bool;
1442         description = ''
1443           Enable unlocking and mounting of encrypted ZFS home dataset at login.
1444         '';
1445       };
1447       homes = lib.mkOption {
1448         example = "rpool/home";
1449         default = "rpool/home";
1450         type = lib.types.str;
1451         description = ''
1452           Prefix of home datasets. This value will be concatenated with
1453           `"/" + <username>` in order to determine the home dataset to unlock.
1454         '';
1455       };
1457       noUnmount = lib.mkOption {
1458         default = false;
1459         type = lib.types.bool;
1460         description = ''
1461           Do not unmount home dataset on logout.
1462         '';
1463       };
1464     };
1466     security.pam.enableEcryptfs = lib.mkEnableOption "eCryptfs PAM module (mounting ecryptfs home directory on login)";
1467     security.pam.enableFscrypt = lib.mkEnableOption ''
1468       fscrypt, to automatically unlock directories with the user's login password.
1470       This also enables a service at security.pam.services.fscrypt which is used by
1471       fscrypt to verify the user's password when setting up a new protector. If you
1472       use something other than pam_unix to verify user passwords, please remember to
1473       adjust this PAM service
1474     '';
1476     users.motd = lib.mkOption {
1477       default = "";
1478       example = "Today is Sweetmorn, the 4th day of The Aftermath in the YOLD 3178.";
1479       type = lib.types.lines;
1480       description = "Message of the day shown to users when they log in.";
1481     };
1483     users.motdFile = lib.mkOption {
1484       default = null;
1485       example = "/etc/motd";
1486       type = lib.types.nullOr lib.types.path;
1487       description = "A file containing the message of the day shown to users when they log in.";
1488     };
1489   };
1492   ###### implementation
1494   config = {
1495     assertions = [
1496       {
1497         assertion = config.users.motd == "" || config.users.motdFile == null;
1498         message = ''
1499           Only one of users.motd and users.motdFile can be set.
1500         '';
1501       }
1502       {
1503         assertion = config.security.pam.zfs.enable -> config.boot.zfs.enabled;
1504         message = ''
1505           `security.pam.zfs.enable` requires enabling ZFS (`boot.zfs.enabled`).
1506         '';
1507       }
1508       {
1509         assertion = with config.security.pam.sshAgentAuth; enable -> authorizedKeysFiles != [];
1510         message = ''
1511           `security.pam.enableSSHAgentAuth` requires `services.openssh.authorizedKeysFiles` to be a non-empty list.
1512           Did you forget to set `services.openssh.enable` ?
1513         '';
1514       }
1515     ];
1517     warnings = lib.optional
1518       (with lib; with config.security.pam.sshAgentAuth;
1519         enable && lib.any (s: lib.hasPrefix "%h" s || lib.hasPrefix "~" s) authorizedKeysFiles)
1520       ''config.security.pam.sshAgentAuth.authorizedKeysFiles contains files in the user's home directory.
1522         Specifying user-writeable files there result in an insecure configuration:
1523         a malicious process can then edit such an authorized_keys file and bypass the ssh-agent-based authentication.
1524         See https://github.com/NixOS/nixpkgs/issues/31611
1525       '';
1527     environment.systemPackages =
1528       # Include the PAM modules in the system path mostly for the manpages.
1529       [ package ]
1530       ++ lib.optional config.users.ldap.enable pam_ldap
1531       ++ lib.optional config.services.kanidm.enablePam config.services.kanidm.package
1532       ++ lib.optional config.services.sssd.enable pkgs.sssd
1533       ++ lib.optionals config.security.pam.krb5.enable [pam_krb5 pam_ccreds]
1534       ++ lib.optionals config.security.pam.enableOTPW [ pkgs.otpw ]
1535       ++ lib.optionals config.security.pam.oath.enable [ pkgs.oath-toolkit ]
1536       ++ lib.optionals config.security.pam.p11.enable [ pkgs.pam_p11 ]
1537       ++ lib.optionals config.security.pam.enableFscrypt [ pkgs.fscrypt-experimental ]
1538       ++ lib.optionals config.security.pam.u2f.enable [ pkgs.pam_u2f ];
1540     boot.supportedFilesystems = lib.optionals config.security.pam.enableEcryptfs [ "ecryptfs" ];
1542     security.wrappers = {
1543       unix_chkpwd = {
1544         setuid = true;
1545         owner = "root";
1546         group = "root";
1547         source = "${package}/bin/unix_chkpwd";
1548       };
1549     };
1551     environment.etc = lib.mapAttrs' makePAMService config.security.pam.services;
1553     security.pam.services =
1554       { other.text =
1555           ''
1556             auth     required pam_warn.so
1557             auth     required pam_deny.so
1558             account  required pam_warn.so
1559             account  required pam_deny.so
1560             password required pam_warn.so
1561             password required pam_deny.so
1562             session  required pam_warn.so
1563             session  required pam_deny.so
1564           '';
1566         # Most of these should be moved to specific modules.
1567         i3lock = {};
1568         i3lock-color = {};
1569         vlock = {};
1570         xlock = {};
1571         xscreensaver = {};
1573         runuser = { rootOK = true; unixAuth = false; setEnvironment = false; };
1575         /* FIXME: should runuser -l start a systemd session? Currently
1576            it complains "Cannot create session: Already running in a
1577            session". */
1578         runuser-l = { rootOK = true; unixAuth = false; };
1579       } // lib.optionalAttrs (config.security.pam.enableFscrypt) {
1580         # Allow fscrypt to verify login passphrase
1581         fscrypt = {};
1582       };
1584     security.apparmor.includes."abstractions/pam" =
1585       lib.concatMapStrings
1586         (name: "r ${config.environment.etc."pam.d/${name}".source},\n")
1587         (lib.attrNames config.security.pam.services) +
1588       (with lib; pipe config.security.pam.services [
1589         lib.attrValues
1590         (catAttrs "rules")
1591         (lib.concatMap lib.attrValues)
1592         (lib.concatMap lib.attrValues)
1593         (lib.filter (rule: rule.enable))
1594         (lib.catAttrs "modulePath")
1595         # TODO(@uninsane): replace this warning + lib.filter with just an assertion
1596         (map (modulePath: lib.warnIfNot
1597           (lib.hasPrefix "/" modulePath)
1598           ''non-absolute PAM modulePath "${modulePath}" is unsupported by apparmor and will be treated as an error by future versions of nixpkgs; see <https://github.com/NixOS/nixpkgs/pull/314791>''
1599           modulePath
1600         ))
1601         (lib.filter (lib.hasPrefix "/"))
1602         lib.unique
1603         (map (module: "mr ${module},"))
1604         concatLines
1605       ]);
1607     security.sudo.extraConfig = optionalSudoConfigForSSHAgentAuth;
1608     security.sudo-rs.extraConfig = optionalSudoConfigForSSHAgentAuth;
1609   };