vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / security / pam.nix
blobacd0673877b88936973bb31e9cf405c4978b3e59
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       rssh = lib.mkOption {
291         default = false;
292         type = lib.types.bool;
293         description = ''
294           If set, the calling user's SSH agent is used to authenticate
295           against the configured keys. This module works in a manner
296           similar to pam_ssh_agent_auth, but supports a wider range
297           of SSH key types, including those protected by security
298           keys (FIDO2).
299         '';
300       };
302       duoSecurity = {
303         enable = lib.mkOption {
304           default = false;
305           type = lib.types.bool;
306           description = ''
307             If set, use the Duo Security pam module
308             `pam_duo` for authentication.  Requires
309             configuration of {option}`security.duosec` options.
310           '';
311         };
312       };
314       startSession = lib.mkOption {
315         default = false;
316         type = lib.types.bool;
317         description = ''
318           If set, the service will register a new session with
319           systemd's login manager.  For local sessions, this will give
320           the user access to audio devices, CD-ROM drives.  In the
321           default PolicyKit configuration, it also allows the user to
322           reboot the system.
323         '';
324       };
326       setEnvironment = lib.mkOption {
327         type = lib.types.bool;
328         default = true;
329         description = ''
330           Whether the service should set the environment variables
331           listed in {option}`environment.sessionVariables`
332           using `pam_env.so`.
333         '';
334       };
336       setLoginUid = lib.mkOption {
337         type = lib.types.bool;
338         description = ''
339           Set the login uid of the process
340           ({file}`/proc/self/loginuid`) for auditing
341           purposes.  The login uid is only set by â€˜entry points’ like
342           {command}`login` and {command}`sshd`, not by
343           commands like {command}`sudo`.
344         '';
345       };
347       ttyAudit = {
348         enable = lib.mkOption {
349           type = lib.types.bool;
350           default = false;
351           description = ''
352             Enable or disable TTY auditing for specified users
353           '';
354         };
356         enablePattern = lib.mkOption {
357           type = lib.types.nullOr lib.types.str;
358           default = null;
359           description = ''
360             For each user matching one of comma-separated
361             glob patterns, enable TTY auditing
362           '';
363         };
365         disablePattern = lib.mkOption {
366           type = lib.types.nullOr lib.types.str;
367           default = null;
368           description = ''
369             For each user matching one of comma-separated
370             glob patterns, disable TTY auditing
371           '';
372         };
374         openOnly = lib.mkOption {
375           type = lib.types.bool;
376           default = false;
377           description = ''
378             Set the TTY audit flag when opening the session,
379             but do not restore it when closing the session.
380             Using this option is necessary for some services
381             that don't fork() to run the authenticated session,
382             such as sudo.
383           '';
384         };
385       };
387       forwardXAuth = lib.mkOption {
388         default = false;
389         type = lib.types.bool;
390         description = ''
391           Whether X authentication keys should be passed from the
392           calling user to the target user (e.g. for
393           {command}`su`)
394         '';
395       };
397       pamMount = lib.mkOption {
398         default = config.security.pam.mount.enable;
399         defaultText = lib.literalExpression "config.security.pam.mount.enable";
400         type = lib.types.bool;
401         description = ''
402           Enable PAM mount (pam_mount) system to mount filesystems on user login.
403         '';
404       };
406       allowNullPassword = lib.mkOption {
407         default = false;
408         type = lib.types.bool;
409         description = ''
410           Whether to allow logging into accounts that have no password
411           set (i.e., have an empty password field in
412           {file}`/etc/passwd` or
413           {file}`/etc/group`).  This does not enable
414           logging into disabled accounts (i.e., that have the password
415           field set to `!`).  Note that regardless of
416           what the pam_unix documentation says, accounts with hashed
417           empty passwords are always allowed to log in.
418         '';
419       };
421       nodelay = lib.mkOption {
422         default = false;
423         type = lib.types.bool;
424         description = ''
425           Whether the delay after typing a wrong password should be disabled.
426         '';
427       };
429       requireWheel = lib.mkOption {
430         default = false;
431         type = lib.types.bool;
432         description = ''
433           Whether to permit root access only to members of group wheel.
434         '';
435       };
437       limits = lib.mkOption {
438         default = [];
439         type = limitsType;
440         description = ''
441           Attribute set describing resource limits.  Defaults to the
442           value of {option}`security.pam.loginLimits`.
443           The meaning of the values is explained in {manpage}`limits.conf(5)`.
444         '';
445       };
447       showMotd = lib.mkOption {
448         default = false;
449         type = lib.types.bool;
450         description = "Whether to show the message of the day.";
451       };
453       makeHomeDir = lib.mkOption {
454         default = false;
455         type = lib.types.bool;
456         description = ''
457           Whether to try to create home directories for users
458           with `$HOME`s pointing to nonexistent
459           locations on session login.
460         '';
461       };
463       updateWtmp = lib.mkOption {
464         default = false;
465         type = lib.types.bool;
466         description = "Whether to update {file}`/var/log/wtmp`.";
467       };
469       logFailures = lib.mkOption {
470         default = false;
471         type = lib.types.bool;
472         description = "Whether to log authentication failures in {file}`/var/log/faillog`.";
473       };
475       enableAppArmor = lib.mkOption {
476         default = false;
477         type = lib.types.bool;
478         description = ''
479           Enable support for attaching AppArmor profiles at the
480           user/group level, e.g., as part of a role based access
481           control scheme.
482         '';
483       };
485       kwallet = {
486         enable = lib.mkOption {
487           default = false;
488           type = lib.types.bool;
489           description = ''
490             If enabled, pam_wallet will attempt to automatically unlock the
491             user's default KDE wallet upon login. If the user has no wallet named
492             "kdewallet", or the login password does not match their wallet
493             password, KDE will prompt separately after login.
494           '';
495         };
497         package = lib.mkPackageOption pkgs.plasma5Packages "kwallet-pam" {
498           pkgsText = "pkgs.plasma5Packages";
499         };
501         forceRun = lib.mkEnableOption null // {
502           description = ''
503             The `force_run` option is used to tell the PAM module for KWallet
504             to forcefully run even if no graphical session (such as a GUI
505             display manager) is detected. This is useful for when you are
506             starting an X Session or a Wayland Session from a TTY. If you
507             intend to log-in from a TTY, it is recommended that you enable
508             this option **and** ensure that `plasma-kwallet-pam.service` is
509             started by `graphical-session.target`.
510           '';
511         };
512       };
514       sssdStrictAccess = lib.mkOption {
515         default = false;
516         type = lib.types.bool;
517         description = "enforce sssd access control";
518       };
520       enableGnomeKeyring = lib.mkOption {
521         default = false;
522         type = lib.types.bool;
523         description = ''
524           If enabled, pam_gnome_keyring will attempt to automatically unlock the
525           user's default Gnome keyring upon login. If the user login password does
526           not match their keyring password, Gnome Keyring will prompt separately
527           after login.
528         '';
529       };
531       failDelay = {
532         enable = lib.mkOption {
533           type = lib.types.bool;
534           default = false;
535           description = ''
536             If enabled, this will replace the `FAIL_DELAY` setting from `login.defs`.
537             Change the delay on failure per-application.
538             '';
539         };
541         delay = lib.mkOption {
542           default = 3000000;
543           type = lib.types.int;
544           example = 1000000;
545           description = "The delay time (in microseconds) on failure.";
546         };
547       };
549       gnupg = {
550         enable = lib.mkOption {
551           type = lib.types.bool;
552           default = false;
553           description = ''
554             If enabled, pam_gnupg will attempt to automatically unlock the
555             user's GPG keys with the login password via
556             {command}`gpg-agent`. The keygrips of all keys to be
557             unlocked should be written to {file}`~/.pam-gnupg`,
558             and can be queried with {command}`gpg -K --with-keygrip`.
559             Presetting passphrases must be enabled by adding
560             `allow-preset-passphrase` in
561             {file}`~/.gnupg/gpg-agent.conf`.
562           '';
563         };
565         noAutostart = lib.mkOption {
566           type = lib.types.bool;
567           default = false;
568           description = ''
569             Don't start {command}`gpg-agent` if it is not running.
570             Useful in conjunction with starting {command}`gpg-agent` as
571             a systemd user service.
572           '';
573         };
575         storeOnly = lib.mkOption {
576           type = lib.types.bool;
577           default = false;
578           description = ''
579             Don't send the password immediately after login, but store for PAM
580             `session`.
581           '';
582         };
583       };
585       zfs = lib.mkOption {
586         default = config.security.pam.zfs.enable;
587         defaultText = lib.literalExpression "config.security.pam.zfs.enable";
588         type = lib.types.bool;
589         description = ''
590           Enable unlocking and mounting of encrypted ZFS home dataset at login.
591         '';
592       };
594       text = lib.mkOption {
595         type = lib.types.nullOr lib.types.lines;
596         description = "Contents of the PAM service file.";
597       };
599     };
601     # The resulting /etc/pam.d/* file contents are verified in
602     # nixos/tests/pam/pam-file-contents.nix. Please update tests there when
603     # changing the derivation.
604     config = {
605       name = lib.mkDefault name;
606       setLoginUid = lib.mkDefault cfg.startSession;
607       limits = lib.mkDefault config.security.pam.loginLimits;
609       text = let
610         ensureUniqueOrder = type: rules:
611           let
612             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;
613             checked = lib.zipListsWith checkPair rules (lib.drop 1 rules);
614           in lib.take 1 rules ++ checked;
615         # Formats a string for use in `module-arguments`. See `man pam.conf`.
616         formatModuleArgument = token:
617           if lib.hasInfix " " token
618           then "[${lib.replaceStrings ["]"] ["\\]"] token}]"
619           else token;
620         formatRules = type: lib.pipe cfg.rules.${type} [
621           lib.attrValues
622           (lib.filter (rule: rule.enable))
623           (lib.sort (a: b: a.order < b.order))
624           (ensureUniqueOrder type)
625           (map (rule: lib.concatStringsSep " " (
626             [ type rule.control rule.modulePath ]
627             ++ map formatModuleArgument rule.args
628             ++ [ "# ${rule.name} (order ${toString rule.order})" ]
629           )))
630           (lib.concatStringsSep "\n")
631         ];
632       in lib.mkDefault ''
633         # Account management.
634         ${formatRules "account"}
636         # Authentication management.
637         ${formatRules "auth"}
639         # Password management.
640         ${formatRules "password"}
642         # Session management.
643         ${formatRules "session"}
644       '';
646       # !!! TODO: move the LDAP stuff to the LDAP module, and the
647       # Samba stuff to the Samba module.  This requires that the PAM
648       # module provides the right hooks.
649       rules = let
650         autoOrderRules = lib.flip lib.pipe [
651           (lib.imap1 (index: rule: rule // { order = lib.mkDefault (10000 + index * 100); } ))
652           (map (rule: lib.nameValuePair rule.name (removeAttrs rule [ "name" ])))
653           lib.listToAttrs
654         ];
655       in {
656         account = autoOrderRules [
657           { name = "ldap"; enable = use_ldap; control = "sufficient"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; }
658           { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
659             config_file = "/etc/security/pam_mysql.conf";
660           }; }
661           { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${config.services.kanidm.package}/lib/pam_kanidm.so"; settings = {
662             ignore_unknown_user = true;
663           }; }
664           { 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"; }
665           { name = "krb5"; enable = config.security.pam.krb5.enable; control = "sufficient"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; }
666           { name = "oslogin_login"; enable = cfg.googleOsLoginAccountVerification; control = "[success=ok ignore=ignore default=die]"; modulePath = "${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so"; }
667           { name = "oslogin_admin"; enable = cfg.googleOsLoginAccountVerification; control = "[success=ok default=ignore]"; modulePath = "${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_admin.so"; }
668           { name = "systemd_home"; enable = config.services.homed.enable; control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
669           # The required pam_unix.so module has to come after all the sufficient modules
670           # because otherwise, the account lookup will fail if the user does not exist
671           # locally, for example with MySQL- or LDAP-auth.
672           { name = "unix"; control = "required"; modulePath = "${package}/lib/security/pam_unix.so"; }
673         ];
675         auth = autoOrderRules ([
676           { 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"; }
677           { name = "rootok"; enable = cfg.rootOK; control = "sufficient"; modulePath = "${package}/lib/security/pam_rootok.so"; }
678           { name = "wheel"; enable = cfg.requireWheel; control = "required"; modulePath = "${package}/lib/security/pam_wheel.so"; settings = {
679             use_uid = true;
680           }; }
681           { name = "faillock"; enable = cfg.logFailures; control = "required"; modulePath = "${package}/lib/security/pam_faillock.so"; }
682           { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
683             config_file = "/etc/security/pam_mysql.conf";
684           }; }
685           { 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 = {
686             file = lib.concatStringsSep ":" config.security.pam.sshAgentAuth.authorizedKeysFiles;
687           }; }
688           (let inherit (config.security.pam) rssh; in { name = "rssh"; enable = rssh.enable && cfg.rssh; control = "sufficient"; modulePath = "${pkgs.pam_rssh}/lib/libpam_rssh.so"; inherit (rssh) settings; })
689           (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 = [
690             "${pkgs.opensc}/lib/opensc-pkcs11.so"
691           ]; })
692           (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; })
693           (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 = {
694             ca_file = ussh.caFile;
695             authorized_principals = ussh.authorizedPrincipals;
696             authorized_principals_file = ussh.authorizedPrincipalsFile;
697             inherit (ussh) group;
698           }; })
699           (let oath = config.security.pam.oath; in { name = "oath"; enable = cfg.oathAuth; control = "requisite"; modulePath = "${pkgs.oath-toolkit}/lib/security/pam_oath.so"; settings = {
700             inherit (oath) window digits;
701             usersfile = oath.usersFile;
702           }; })
703           (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 = {
704             inherit (yubi) mode debug;
705             chalresp_path = yubi.challengeResponsePath;
706             id = lib.mkIf (yubi.mode == "client") yubi.id;
707           }; })
708           (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 = [
709             dp9ik.authserver
710           ]; })
711           { name = "fprintd"; enable = cfg.fprintAuth; control = "sufficient"; modulePath = "${config.services.fprintd.package}/lib/security/pam_fprintd.so"; }
712         ] ++
713           # Modules in this block require having the password set in PAM_AUTHTOK.
714           # pam_unix is marked as 'sufficient' on NixOS which means nothing will run
715           # after it succeeds. Certain modules need to run after pam_unix
716           # prompts the user for password so we run it once with 'optional' at an
717           # earlier point and it will run again with 'sufficient' further down.
718           # We use try_first_pass the second time to avoid prompting password twice.
719           #
720           # The same principle applies to systemd-homed
721           (lib.optionals ((cfg.unixAuth || config.services.homed.enable) &&
722             (config.security.pam.enableEcryptfs
723               || config.security.pam.enableFscrypt
724               || cfg.pamMount
725               || cfg.kwallet.enable
726               || cfg.enableGnomeKeyring
727               || config.services.intune.enable
728               || cfg.googleAuthenticator.enable
729               || cfg.gnupg.enable
730               || cfg.failDelay.enable
731               || cfg.duoSecurity.enable
732               || cfg.zfs))
733             [
734               { name = "systemd_home-early"; enable = config.services.homed.enable; control = "optional"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
735               { name = "unix-early"; enable = cfg.unixAuth; control = "optional"; modulePath = "${package}/lib/security/pam_unix.so"; settings = {
736                 nullok = cfg.allowNullPassword;
737                 inherit (cfg) nodelay;
738                 likeauth = true;
739               }; }
740               { name = "ecryptfs"; enable = config.security.pam.enableEcryptfs; control = "optional"; modulePath = "${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"; settings = {
741                 unwrap = true;
742               }; }
743               { name = "fscrypt"; enable = config.security.pam.enableFscrypt; control = "optional"; modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so"; }
744               { name = "zfs_key"; enable = cfg.zfs; control = "optional"; modulePath = "${config.boot.zfs.package}/lib/security/pam_zfs_key.so"; settings = {
745                 inherit (config.security.pam.zfs) homes;
746               }; }
747               { name = "mount"; enable = cfg.pamMount; control = "optional"; modulePath = "${pkgs.pam_mount}/lib/security/pam_mount.so"; settings = {
748                 disable_interactive = true;
749               }; }
750               { name = "kwallet"; enable = cfg.kwallet.enable; control = "optional"; modulePath = "${cfg.kwallet.package}/lib/security/pam_kwallet5.so"; }
751               { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so"; }
752               { name = "intune"; enable = config.services.intune.enable; control = "optional"; modulePath = "${pkgs.intune-portal}/lib/security/pam_intune.so"; }
753               { name = "gnupg"; enable = cfg.gnupg.enable; control = "optional"; modulePath = "${pkgs.pam_gnupg}/lib/security/pam_gnupg.so"; settings = {
754                 store-only = cfg.gnupg.storeOnly;
755               }; }
756               { name = "faildelay"; enable = cfg.failDelay.enable; control = "optional"; modulePath = "${package}/lib/security/pam_faildelay.so"; settings = {
757                 inherit (cfg.failDelay) delay;
758               }; }
759               { name = "google_authenticator"; enable = cfg.googleAuthenticator.enable; control = "required"; modulePath = "${pkgs.google-authenticator}/lib/security/pam_google_authenticator.so"; settings = {
760                 no_increment_hotp = true;
761               }; }
762               { name = "duo"; enable = cfg.duoSecurity.enable; control = "required"; modulePath = "${pkgs.duo-unix}/lib/security/pam_duo.so"; }
763             ]) ++ [
764           { name = "systemd_home"; enable = config.services.homed.enable; control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
765           { name = "unix"; enable = cfg.unixAuth; control = "sufficient"; modulePath = "${package}/lib/security/pam_unix.so"; settings = {
766             nullok = cfg.allowNullPassword;
767             inherit (cfg) nodelay;
768             likeauth = true;
769             try_first_pass = true;
770           }; }
771           { name = "otpw"; enable = cfg.otpwAuth; control = "sufficient"; modulePath = "${pkgs.otpw}/lib/security/pam_otpw.so"; }
772           { name = "ldap"; enable = use_ldap; control = "sufficient"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; settings = {
773             use_first_pass = true;
774           }; }
775           { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${config.services.kanidm.package}/lib/pam_kanidm.so"; settings = {
776             ignore_unknown_user = true;
777             use_first_pass = true;
778           }; }
779           { name = "sss"; enable = config.services.sssd.enable; control = "sufficient"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; settings = {
780             use_first_pass = true;
781           }; }
782           { 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 = {
783             use_first_pass = true;
784           }; }
785           { name = "ccreds-validate"; enable = config.security.pam.krb5.enable; control = "[default=die success=done]"; modulePath = "${pam_ccreds}/lib/security/pam_ccreds.so"; settings = {
786             action = "validate";
787             use_first_pass = true;
788           }; }
789           { name = "ccreds-store"; enable = config.security.pam.krb5.enable; control = "sufficient"; modulePath = "${pam_ccreds}/lib/security/pam_ccreds.so"; settings = {
790             action = "store";
791             use_first_pass = true;
792           }; }
793           { name = "deny"; control = "required"; modulePath = "${package}/lib/security/pam_deny.so"; }
794         ]);
796         password = autoOrderRules [
797           { name = "systemd_home"; enable = config.services.homed.enable; control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
798           { name = "unix"; control = "sufficient"; modulePath = "${package}/lib/security/pam_unix.so"; settings = {
799             nullok = true;
800             yescrypt = true;
801           }; }
802           { name = "ecryptfs"; enable = config.security.pam.enableEcryptfs; control = "optional"; modulePath = "${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"; }
803           { name = "fscrypt"; enable = config.security.pam.enableFscrypt; control = "optional"; modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so"; }
804           { name = "zfs_key"; enable = cfg.zfs; control = "optional"; modulePath = "${config.boot.zfs.package}/lib/security/pam_zfs_key.so"; settings = {
805             inherit (config.security.pam.zfs) homes;
806           }; }
807           { name = "mount"; enable = cfg.pamMount; control = "optional"; modulePath = "${pkgs.pam_mount}/lib/security/pam_mount.so"; }
808           { name = "ldap"; enable = use_ldap; control = "sufficient"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; }
809           { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
810             config_file = "/etc/security/pam_mysql.conf";
811           }; }
812           { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${config.services.kanidm.package}/lib/pam_kanidm.so"; }
813           { name = "sss"; enable = config.services.sssd.enable; control = "sufficient"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; }
814           { name = "krb5"; enable = config.security.pam.krb5.enable; control = "sufficient"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; settings = {
815             use_first_pass = true;
816           }; }
817           { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so"; settings = {
818             use_authtok = true;
819           }; }
820         ];
822         session = autoOrderRules [
823           { name = "env"; enable = cfg.setEnvironment; control = "required"; modulePath = "${package}/lib/security/pam_env.so"; settings = {
824             conffile = "/etc/pam/environment";
825             readenv = 0;
826           }; }
827           { name = "unix"; control = "required"; modulePath = "${package}/lib/security/pam_unix.so"; }
828           { name = "loginuid"; enable = cfg.setLoginUid; control = if config.boot.isContainer then "optional" else "required"; modulePath = "${package}/lib/security/pam_loginuid.so"; }
829           { name = "tty_audit"; enable = cfg.ttyAudit.enable; control = "required"; modulePath = "${package}/lib/security/pam_tty_audit.so"; settings = {
830             open_only = cfg.ttyAudit.openOnly;
831             enable = cfg.ttyAudit.enablePattern;
832             disable = cfg.ttyAudit.disablePattern;
833           }; }
834           { name = "systemd_home"; enable = config.services.homed.enable; control = "required"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
835           { name = "mkhomedir"; enable = cfg.makeHomeDir; control = "required"; modulePath = "${package}/lib/security/pam_mkhomedir.so"; settings = {
836             silent = true;
837             skel = config.security.pam.makeHomeDir.skelDirectory;
838             inherit (config.security.pam.makeHomeDir) umask;
839           }; }
840           { name = "lastlog"; enable = cfg.updateWtmp; control = "required"; modulePath = "${package}/lib/security/pam_lastlog.so"; settings = {
841             silent = true;
842           }; }
843           { name = "ecryptfs"; enable = config.security.pam.enableEcryptfs; control = "optional"; modulePath = "${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"; }
844           # Work around https://github.com/systemd/systemd/issues/8598
845           # Skips the pam_fscrypt module for systemd-user sessions which do not have a password
846           # anyways.
847           # See also https://github.com/google/fscrypt/issues/95
848           { name = "fscrypt-skip-systemd"; enable = config.security.pam.enableFscrypt; control = "[success=1 default=ignore]"; modulePath = "${package}/lib/security/pam_succeed_if.so"; args = [
849             "service" "=" "systemd-user"
850           ]; }
851           { name = "fscrypt"; enable = config.security.pam.enableFscrypt; control = "optional"; modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so"; }
852           { name = "zfs_key-skip-systemd"; enable = cfg.zfs; control = "[success=1 default=ignore]"; modulePath = "${package}/lib/security/pam_succeed_if.so"; args = [
853             "service" "=" "systemd-user"
854           ]; }
855           { name = "zfs_key"; enable = cfg.zfs; control = "optional"; modulePath = "${config.boot.zfs.package}/lib/security/pam_zfs_key.so"; settings = {
856             inherit (config.security.pam.zfs) homes;
857             nounmount = config.security.pam.zfs.noUnmount;
858           }; }
859           { name = "mount"; enable = cfg.pamMount; control = "optional"; modulePath = "${pkgs.pam_mount}/lib/security/pam_mount.so"; settings = {
860             disable_interactive = true;
861           }; }
862           { name = "ldap"; enable = use_ldap; control = "optional"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; }
863           { name = "mysql"; enable = cfg.mysqlAuth; control = "optional"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
864             config_file = "/etc/security/pam_mysql.conf";
865           }; }
866           { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "optional"; modulePath = "${config.services.kanidm.package}/lib/pam_kanidm.so"; }
867           { name = "sss"; enable = config.services.sssd.enable; control = "optional"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; }
868           { name = "krb5"; enable = config.security.pam.krb5.enable; control = "optional"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; }
869           { name = "otpw"; enable = cfg.otpwAuth; control = "optional"; modulePath = "${pkgs.otpw}/lib/security/pam_otpw.so"; }
870           { name = "systemd"; enable = cfg.startSession; control = "optional"; modulePath = "${config.systemd.package}/lib/security/pam_systemd.so"; }
871           { name = "xauth"; enable = cfg.forwardXAuth; control = "optional"; modulePath = "${package}/lib/security/pam_xauth.so"; settings = {
872             xauthpath = "${pkgs.xorg.xauth}/bin/xauth";
873             systemuser = 99;
874           }; }
875           { name = "limits"; enable = cfg.limits != []; control = "required"; modulePath = "${package}/lib/security/pam_limits.so"; settings = {
876             conf = "${makeLimitsConf cfg.limits}";
877           }; }
878           { name = "motd"; enable = cfg.showMotd && (config.users.motd != "" || config.users.motdFile != null); control = "optional"; modulePath = "${package}/lib/security/pam_motd.so"; settings = {
879             inherit motd;
880           }; }
881           { name = "apparmor"; enable = cfg.enableAppArmor && config.security.apparmor.enable; control = "optional"; modulePath = "${pkgs.apparmor-pam}/lib/security/pam_apparmor.so"; settings = {
882             order = "user,group,default";
883             debug = true;
884           }; }
885           { 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; }; }
886           { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so"; settings = {
887             auto_start = true;
888           }; }
889           { name = "gnupg"; enable = cfg.gnupg.enable; control = "optional"; modulePath = "${pkgs.pam_gnupg}/lib/security/pam_gnupg.so"; settings = {
890             no-autostart = cfg.gnupg.noAutostart;
891           }; }
892           { name = "intune"; enable = config.services.intune.enable; control = "optional"; modulePath = "${pkgs.intune-portal}/lib/security/pam_intune.so"; }
893         ];
894       };
895     };
897   };
900   inherit (pkgs) pam_krb5 pam_ccreds;
902   use_ldap = (config.users.ldap.enable && config.users.ldap.loginPam);
903   pam_ldap = if config.users.ldap.daemon.enable then pkgs.nss_pam_ldapd else pkgs.pam_ldap;
905   # Create a limits.conf(5) file.
906   makeLimitsConf = limits:
907     pkgs.writeText "limits.conf"
908        (lib.concatMapStrings ({ domain, type, item, value }:
909          "${domain} ${type} ${item} ${toString value}\n")
910          limits);
912   limitsType = with lib.types; listOf (submodule ({ ... }: {
913     options = {
914       domain = lib.mkOption {
915         description = "Username, groupname, or wildcard this limit applies to";
916         example = "@wheel";
917         type = str;
918       };
920       type = lib.mkOption {
921         description = "Type of this limit";
922         type = enum [ "-" "hard" "soft" ];
923         default = "-";
924       };
926       item = lib.mkOption {
927         description = "Item this limit applies to";
928         type = enum [
929           "core"
930           "data"
931           "fsize"
932           "memlock"
933           "nofile"
934           "rss"
935           "stack"
936           "cpu"
937           "nproc"
938           "as"
939           "maxlogins"
940           "maxsyslogins"
941           "priority"
942           "locks"
943           "sigpending"
944           "msgqueue"
945           "nice"
946           "rtprio"
947         ];
948       };
950       value = lib.mkOption {
951         description = "Value of this limit";
952         type = oneOf [ str int ];
953       };
954     };
955   }));
957   motd = if config.users.motdFile == null
958          then pkgs.writeText "motd" config.users.motd
959          else config.users.motdFile;
961   makePAMService = name: service:
962     { name = "pam.d/${name}";
963       value.source = pkgs.writeText "${name}.pam" service.text;
964     };
966   optionalSudoConfigForSSHAgentAuth = lib.optionalString
967     (config.security.pam.sshAgentAuth.enable || config.security.pam.rssh.enable) ''
968     # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so and libpam_rssh.so can do their magic.
969     Defaults env_keep+=SSH_AUTH_SOCK
970   '';
976   meta.maintainers = [ lib.maintainers.majiir ];
978   imports = [
979     (lib.mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ])
980     (lib.mkRenamedOptionModule [ "security" "pam" "enableSSHAgentAuth" ] [ "security" "pam" "sshAgentAuth" "enable" ])
981     (lib.mkRenamedOptionModule [ "security" "pam" "u2f" "authFile" ] [ "security" "pam" "u2f" "settings" "authfile" ])
982     (lib.mkRenamedOptionModule [ "security" "pam" "u2f" "appId" ] [ "security" "pam" "u2f" "settings" "appid" ])
983     (lib.mkRenamedOptionModule [ "security" "pam" "u2f" "origin" ] [ "security" "pam" "u2f" "settings" "origin" ])
984     (lib.mkRenamedOptionModule [ "security" "pam" "u2f" "debug" ] [ "security" "pam" "u2f" "settings" "debug" ])
985     (lib.mkRenamedOptionModule [ "security" "pam" "u2f" "interactive" ] [ "security" "pam" "u2f" "settings" "interactive" ])
986     (lib.mkRenamedOptionModule [ "security" "pam" "u2f" "cue" ] [ "security" "pam" "u2f" "settings" "cue" ])
987   ];
989   ###### interface
991   options = {
993     security.pam.package = lib.mkPackageOption pkgs "pam" { };
995     security.pam.loginLimits = lib.mkOption {
996       default = [];
997       type = limitsType;
998       example =
999         [ { domain = "ftp";
1000             type   = "hard";
1001             item   = "nproc";
1002             value  = "0";
1003           }
1004           { domain = "@student";
1005             type   = "-";
1006             item   = "maxlogins";
1007             value  = "4";
1008           }
1009        ];
1011      description = ''
1012        Define resource limits that should apply to users or groups.
1013        Each item in the list should be an attribute set with a
1014        {var}`domain`, {var}`type`,
1015        {var}`item`, and {var}`value`
1016        attribute.  The syntax and semantics of these attributes
1017        must be that described in {manpage}`limits.conf(5)`.
1019        Note that these limits do not apply to systemd services,
1020        whose limits can be changed via {option}`systemd.extraConfig`
1021        instead.
1022      '';
1023     };
1025     security.pam.services = lib.mkOption {
1026       default = {};
1027       type = with lib.types; attrsOf (submodule pamOpts);
1028       description = ''
1029           This option defines the PAM services.  A service typically
1030           corresponds to a program that uses PAM,
1031           e.g. {command}`login` or {command}`passwd`.
1032           Each attribute of this set defines a PAM service, with the attribute name
1033           defining the name of the service.
1034         '';
1035     };
1037     security.pam.makeHomeDir.skelDirectory = lib.mkOption {
1038       type = lib.types.str;
1039       default = "/var/empty";
1040       example =  "/etc/skel";
1041       description = ''
1042         Path to skeleton directory whose contents are copied to home
1043         directories newly created by `pam_mkhomedir`.
1044       '';
1045     };
1047     security.pam.makeHomeDir.umask = lib.mkOption {
1048       type = lib.types.str;
1049       default = "0077";
1050       example = "0022";
1051       description = ''
1052         The user file mode creation mask to use on home directories
1053         newly created by `pam_mkhomedir`.
1054       '';
1055     };
1057     security.pam.sshAgentAuth = {
1058       enable = lib.mkEnableOption ''
1059         authenticating using a signature performed by the ssh-agent.
1060         This allows using SSH keys exclusively, instead of passwords, for instance on remote machines
1061       '';
1063       authorizedKeysFiles = lib.mkOption {
1064         type = with lib.types; listOf str;
1065         description = ''
1066           A list of paths to files in OpenSSH's `authorized_keys` format, containing
1067           the keys that will be trusted by the `pam_ssh_agent_auth` module.
1069           The following patterns are expanded when interpreting the path:
1070           - `%f` and `%H` respectively expand to the fully-qualified and short hostname ;
1071           - `%u` expands to the username ;
1072           - `~` or `%h` expands to the user's home directory.
1074           ::: {.note}
1075           Specifying user-writeable files here result in an insecure configuration:  a malicious process
1076           can then edit such an authorized_keys file and bypass the ssh-agent-based authentication.
1078           See [issue #31611](https://github.com/NixOS/nixpkgs/issues/31611)
1079           :::
1080         '';
1081         default = [ "/etc/ssh/authorized_keys.d/%u" ];
1082       };
1083     };
1085     security.pam.rssh = {
1086       enable = lib.mkEnableOption "authenticating using a signature performed by the ssh-agent";
1088       settings = lib.mkOption {
1089         type = lib.types.submodule {
1090           freeformType = moduleSettingsType;
1091           options = {
1092             auth_key_file = lib.mkOption {
1093               type = with lib.types; nullOr nonEmptyStr;
1094               description = ''
1095                 Path to file with trusted public keys in OpenSSH's `authorized_keys` format. The following
1096                 variables are expanded to the respective PAM items:
1098                 - `service`: `PAM_SERVICE`, the service name,
1099                 - `user`: `PAM_USER`, the username of the entity under whose identity service will be given,
1100                 - `tty`: `PAM_TTY`, the terminal name,
1101                 - `rhost`: `PAM_RHOST`, the requesting hostname, and
1102                 - `ruser`: `PAM_RUSER`, the requesting entity.
1104                 These PAM items are explained in {manpage}`pam_get_item(3)`.
1106                 Variables may be specified as `$var`, `''${var}` or `''${var:defaultValue}`.
1108                 ::: {.note}
1109                 Specifying user-writeable files here results in an insecure configuration: a malicious process
1110                 can then edit such an `authorized_keys` file and bypass the ssh-agent-based authentication.
1112                 This option is ignored if {option}`security.pam.rssh.settings.authorized_keys_command` is set.
1114                 If both this option and {option}`security.pam.rssh.settings.authorized_keys_command` are unset,
1115                 the keys will be read from `''${HOME}/.ssh/authorized_keys`, which should be considered
1116                 insecure.
1117               '';
1118               default = "/etc/ssh/authorized_keys.d/$ruser";
1119             };
1120           };
1121         };
1123         default = { };
1124         description = ''
1125           Options to pass to the pam_rssh module. Refer to
1126           <https://github.com/z4yx/pam_rssh/blob/main/README.md#optional-arguments>
1127           for supported values.
1129           ${moduleSettingsDescription}
1130         '';
1131       };
1132     };
1134     security.pam.enableOTPW = lib.mkEnableOption "the OTPW (one-time password) PAM module";
1136     security.pam.dp9ik = {
1137       enable = lib.mkEnableOption ''
1138           the dp9ik pam module provided by tlsclient.
1140           If set, users can be authenticated against the 9front
1141           authentication server given in {option}`security.pam.dp9ik.authserver`
1142         '';
1143       control = lib.mkOption {
1144         default = "sufficient";
1145         type = lib.types.str;
1146         description = ''
1147           This option sets the pam "control" used for this module.
1148         '';
1149       };
1150       authserver = lib.mkOption {
1151         default = null;
1152         type = with lib.types; nullOr str;
1153         description = ''
1154           This controls the hostname for the 9front authentication server
1155           that users will be authenticated against.
1156         '';
1157       };
1158     };
1160     security.pam.krb5 = {
1161       enable = lib.mkOption {
1162         default = config.security.krb5.enable;
1163         defaultText = lib.literalExpression "config.security.krb5.enable";
1164         type = lib.types.bool;
1165         description = ''
1166           Enables Kerberos PAM modules (`pam-krb5`,
1167           `pam-ccreds`).
1169           If set, users can authenticate with their Kerberos password.
1170           This requires a valid Kerberos configuration
1171           (`config.security.krb5.enable` should be set to
1172           `true`).
1174           Note that the Kerberos PAM modules are not necessary when using SSS
1175           to handle Kerberos authentication.
1176         '';
1177       };
1178     };
1180     security.pam.p11 = {
1181       enable = lib.mkOption {
1182         default = false;
1183         type = lib.types.bool;
1184         description = ''
1185           Enables P11 PAM (`pam_p11`) module.
1187           If set, users can log in with SSH keys and PKCS#11 tokens.
1189           More information can be found [here](https://github.com/OpenSC/pam_p11).
1190         '';
1191       };
1193       control = lib.mkOption {
1194         default = "sufficient";
1195         type = lib.types.enum [ "required" "requisite" "sufficient" "lib.optional" ];
1196         description = ''
1197           This option sets pam "control".
1198           If you want to have multi factor authentication, use "required".
1199           If you want to use the PKCS#11 device instead of the regular password,
1200           use "sufficient".
1202           Read
1203           {manpage}`pam.conf(5)`
1204           for better understanding of this option.
1205         '';
1206       };
1207     };
1209     security.pam.u2f = {
1210       enable = lib.mkOption {
1211         default = false;
1212         type = lib.types.bool;
1213         description = ''
1214           Enables U2F PAM (`pam-u2f`) module.
1216           If set, users listed in
1217           {file}`$XDG_CONFIG_HOME/Yubico/u2f_keys` (or
1218           {file}`$HOME/.config/Yubico/u2f_keys` if XDG variable is
1219           not set) are able to log in with the associated U2F key. The path can
1220           be changed using {option}`security.pam.u2f.authFile` option.
1222           File format is:
1223           `username:first_keyHandle,first_public_key: second_keyHandle,second_public_key`
1224           This file can be generated using {command}`pamu2fcfg` command.
1226           More information can be found [here](https://developers.yubico.com/pam-u2f/).
1227         '';
1228       };
1230       control = lib.mkOption {
1231         default = "sufficient";
1232         type = lib.types.enum [ "required" "requisite" "sufficient" "optional" ];
1233         description = ''
1234           This option sets pam "control".
1235           If you want to have multi factor authentication, use "required".
1236           If you want to use U2F device instead of regular password, use "sufficient".
1238           Read
1239           {manpage}`pam.conf(5)`
1240           for better understanding of this option.
1241         '';
1242       };
1244       settings = lib.mkOption {
1245         type = lib.types.submodule {
1246           freeformType = moduleSettingsType;
1248           options = {
1249             authfile = lib.mkOption {
1250               default = null;
1251               type = with lib.types; nullOr path;
1252               description = ''
1253                 By default `pam-u2f` module reads the keys from
1254                 {file}`$XDG_CONFIG_HOME/Yubico/u2f_keys` (or
1255                 {file}`$HOME/.config/Yubico/u2f_keys` if XDG variable is
1256                 not set).
1258                 If you want to change auth file locations or centralize database (for
1259                 example use {file}`/etc/u2f-mappings`) you can set this
1260                 option.
1262                 File format is:
1263                 `username:first_keyHandle,first_public_key: second_keyHandle,second_public_key`
1264                 This file can be generated using {command}`pamu2fcfg` command.
1266                 More information can be found [here](https://developers.yubico.com/pam-u2f/).
1267               '';
1268             };
1270             appid = lib.mkOption {
1271               default = null;
1272               type = with lib.types; nullOr str;
1273               description = ''
1274                   By default `pam-u2f` module sets the application
1275                   ID to `pam://$HOSTNAME`.
1277                   When using {command}`pamu2fcfg`, you can specify your
1278                   application ID with the `-i` flag.
1280                   More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
1281               '';
1282             };
1284             origin = lib.mkOption {
1285               default = null;
1286               type = with lib.types; nullOr str;
1287               description = ''
1288                   By default `pam-u2f` module sets the origin
1289                   to `pam://$HOSTNAME`.
1290                   Setting origin to an host independent value will allow you to
1291                   reuse credentials across machines
1293                   When using {command}`pamu2fcfg`, you can specify your
1294                   application ID with the `-o` flag.
1296                   More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
1297               '';
1298             };
1300             debug = lib.mkOption {
1301               default = false;
1302               type = lib.types.bool;
1303               description = ''
1304                 Debug output to stderr.
1305               '';
1306             };
1308             interactive = lib.mkOption {
1309               default = false;
1310               type = lib.types.bool;
1311               description = ''
1312                 Set to prompt a message and wait before testing the presence of a U2F device.
1313                 Recommended if your device doesn’t have a tactile trigger.
1314               '';
1315             };
1317             cue = lib.mkOption {
1318               default = false;
1319               type = lib.types.bool;
1320               description = ''
1321                 By default `pam-u2f` module does not inform user
1322                 that he needs to use the u2f device, it just waits without a prompt.
1324                 If you set this option to `true`,
1325                 `cue` option is added to `pam-u2f`
1326                 module and reminder message will be displayed.
1327               '';
1328             };
1329           };
1330         };
1331         default = { };
1332         example = {
1333           authfile = "/etc/u2f_keys";
1334           authpending_file = "";
1335           userpresence = 0;
1336           pinverification = 1;
1337         };
1338         description = ''
1339           Options to pass to the PAM module.
1341           ${moduleSettingsDescription}
1342         '';
1343       };
1344     };
1346     security.pam.ussh = {
1347       enable = lib.mkOption {
1348         default = false;
1349         type = lib.types.bool;
1350         description = ''
1351           Enables Uber's USSH PAM (`pam-ussh`) module.
1353           This is similar to `pam-ssh-agent`, except that
1354           the presence of a CA-signed SSH key with a valid principal is checked
1355           instead.
1357           Note that this module must both be enabled using this option and on a
1358           per-PAM-service level as well (using `usshAuth`).
1360           More information can be found [here](https://github.com/uber/pam-ussh).
1361         '';
1362       };
1364       caFile = lib.mkOption {
1365         default = null;
1366         type = with lib.types; nullOr path;
1367         description = ''
1368           By default `pam-ussh` reads the trusted user CA keys
1369           from {file}`/etc/ssh/trusted_user_ca`.
1371           This should be set the same as your `TrustedUserCAKeys`
1372           option for sshd.
1373         '';
1374       };
1376       authorizedPrincipals = lib.mkOption {
1377         default = null;
1378         type = with lib.types; nullOr commas;
1379         description = ''
1380           Comma-separated list of authorized principals to permit; if the user
1381           presents a certificate with one of these principals, then they will be
1382           authorized.
1384           Note that `pam-ussh` also requires that the certificate
1385           contain a principal matching the user's username. The principals from
1386           this list are in addition to those principals.
1388           Mutually exclusive with `authorizedPrincipalsFile`.
1389         '';
1390       };
1392       authorizedPrincipalsFile = lib.mkOption {
1393         default = null;
1394         type = with lib.types; nullOr path;
1395         description = ''
1396           Path to a list of principals; if the user presents a certificate with
1397           one of these principals, then they will be authorized.
1399           Note that `pam-ussh` also requires that the certificate
1400           contain a principal matching the user's username. The principals from
1401           this file are in addition to those principals.
1403           Mutually exclusive with `authorizedPrincipals`.
1404         '';
1405       };
1407       group = lib.mkOption {
1408         default = null;
1409         type = with lib.types; nullOr str;
1410         description = ''
1411           If set, then the authenticating user must be a member of this group
1412           to use this module.
1413         '';
1414       };
1416       control = lib.mkOption {
1417         default = "sufficient";
1418         type = lib.types.enum [ "required" "requisite" "sufficient" "optional" ];
1419         description = ''
1420           This option sets pam "control".
1421           If you want to have multi factor authentication, use "required".
1422           If you want to use the SSH certificate instead of the regular password,
1423           use "sufficient".
1425           Read
1426           {manpage}`pam.conf(5)`
1427           for better understanding of this option.
1428         '';
1429       };
1430     };
1432     security.pam.yubico = {
1433       enable = lib.mkOption {
1434         default = false;
1435         type = lib.types.bool;
1436         description = ''
1437           Enables Yubico PAM (`yubico-pam`) module.
1439           If set, users listed in
1440           {file}`~/.yubico/authorized_yubikeys`
1441           are able to log in with the associated Yubikey tokens.
1443           The file must have only one line:
1444           `username:yubikey_token_id1:yubikey_token_id2`
1445           More information can be found [here](https://developers.yubico.com/yubico-pam/).
1446         '';
1447       };
1448       control = lib.mkOption {
1449         default = "sufficient";
1450         type = lib.types.enum [ "required" "requisite" "sufficient" "optional" ];
1451         description = ''
1452           This option sets pam "control".
1453           If you want to have multi factor authentication, use "required".
1454           If you want to use Yubikey instead of regular password, use "sufficient".
1456           Read
1457           {manpage}`pam.conf(5)`
1458           for better understanding of this option.
1459         '';
1460       };
1461       id = lib.mkOption {
1462         example = "42";
1463         type = lib.types.str;
1464         description = "client id";
1465       };
1467       debug = lib.mkOption {
1468         default = false;
1469         type = lib.types.bool;
1470         description = ''
1471           Debug output to stderr.
1472         '';
1473       };
1474       mode = lib.mkOption {
1475         default = "client";
1476         type = lib.types.enum [ "client" "challenge-response" ];
1477         description = ''
1478           Mode of operation.
1480           Use "client" for online validation with a YubiKey validation service such as
1481           the YubiCloud.
1483           Use "challenge-response" for offline validation using YubiKeys with HMAC-SHA-1
1484           Challenge-Response configurations. See the man-page ykpamcfg(1) for further
1485           details on how to configure offline Challenge-Response validation.
1487           More information can be found [here](https://developers.yubico.com/yubico-pam/Authentication_Using_Challenge-Response.html).
1488         '';
1489       };
1490       challengeResponsePath = lib.mkOption {
1491         default = null;
1492         type = lib.types.nullOr lib.types.path;
1493         description = ''
1494           If not null, set the path used by yubico pam module where the challenge expected response is stored.
1496           More information can be found [here](https://developers.yubico.com/yubico-pam/Authentication_Using_Challenge-Response.html).
1497         '';
1498       };
1499     };
1501     security.pam.zfs = {
1502       enable = lib.mkOption {
1503         default = false;
1504         type = lib.types.bool;
1505         description = ''
1506           Enable unlocking and mounting of encrypted ZFS home dataset at login.
1507         '';
1508       };
1510       homes = lib.mkOption {
1511         example = "rpool/home";
1512         default = "rpool/home";
1513         type = lib.types.str;
1514         description = ''
1515           Prefix of home datasets. This value will be concatenated with
1516           `"/" + <username>` in order to determine the home dataset to unlock.
1517         '';
1518       };
1520       noUnmount = lib.mkOption {
1521         default = false;
1522         type = lib.types.bool;
1523         description = ''
1524           Do not unmount home dataset on logout.
1525         '';
1526       };
1527     };
1529     security.pam.enableEcryptfs = lib.mkEnableOption "eCryptfs PAM module (mounting ecryptfs home directory on login)";
1530     security.pam.enableFscrypt = lib.mkEnableOption ''
1531       fscrypt, to automatically unlock directories with the user's login password.
1533       This also enables a service at security.pam.services.fscrypt which is used by
1534       fscrypt to verify the user's password when setting up a new protector. If you
1535       use something other than pam_unix to verify user passwords, please remember to
1536       adjust this PAM service
1537     '';
1539     users.motd = lib.mkOption {
1540       default = "";
1541       example = "Today is Sweetmorn, the 4th day of The Aftermath in the YOLD 3178.";
1542       type = lib.types.lines;
1543       description = "Message of the day shown to users when they log in.";
1544     };
1546     users.motdFile = lib.mkOption {
1547       default = null;
1548       example = "/etc/motd";
1549       type = lib.types.nullOr lib.types.path;
1550       description = "A file containing the message of the day shown to users when they log in.";
1551     };
1552   };
1555   ###### implementation
1557   config = {
1558     assertions = [
1559       {
1560         assertion = config.users.motd == "" || config.users.motdFile == null;
1561         message = ''
1562           Only one of users.motd and users.motdFile can be set.
1563         '';
1564       }
1565       {
1566         assertion = config.security.pam.zfs.enable -> config.boot.zfs.enabled;
1567         message = ''
1568           `security.pam.zfs.enable` requires enabling ZFS (`boot.zfs.enabled`).
1569         '';
1570       }
1571       {
1572         assertion = with config.security.pam.sshAgentAuth; enable -> authorizedKeysFiles != [];
1573         message = ''
1574           `security.pam.enableSSHAgentAuth` requires `services.openssh.authorizedKeysFiles` to be a non-empty list.
1575           Did you forget to set `services.openssh.enable` ?
1576         '';
1577       }
1578       {
1579         assertion = with config.security.pam.rssh;
1580           enable -> (settings.auth_key_file or null != null || settings.authorized_keys_command or null != null);
1581         message = ''
1582           security.pam.rssh.enable requires either security.pam.rssh.settings.auth_key_file or
1583           security.pam.rssh.settings.authorized_keys_command to be set.
1584         '';
1585       }
1586     ];
1588     warnings = lib.optional
1589       (with config.security.pam.sshAgentAuth;
1590         enable && lib.any (s: lib.hasPrefix "%h" s || lib.hasPrefix "~" s) authorizedKeysFiles)
1591       ''config.security.pam.sshAgentAuth.authorizedKeysFiles contains files in the user's home directory.
1593         Specifying user-writeable files there result in an insecure configuration:
1594         a malicious process can then edit such an authorized_keys file and bypass the ssh-agent-based authentication.
1595         See https://github.com/NixOS/nixpkgs/issues/31611
1596       '' ++ lib.optional
1597       (with config.security.pam.rssh;
1598         enable && settings.auth_key_file or null != null && settings.authorized_keys_command or null != null) ''
1599         security.pam.rssh.settings.auth_key_file will be ignored as
1600         security.pam.rssh.settings.authorized_keys_command has been specified.
1601         Explictly set the former to null to silence this warning.
1602       '';
1604     environment.systemPackages =
1605       # Include the PAM modules in the system path mostly for the manpages.
1606       [ package ]
1607       ++ lib.optional config.users.ldap.enable pam_ldap
1608       ++ lib.optional config.services.kanidm.enablePam config.services.kanidm.package
1609       ++ lib.optional config.services.sssd.enable pkgs.sssd
1610       ++ lib.optionals config.security.pam.krb5.enable [pam_krb5 pam_ccreds]
1611       ++ lib.optionals config.security.pam.enableOTPW [ pkgs.otpw ]
1612       ++ lib.optionals config.security.pam.oath.enable [ pkgs.oath-toolkit ]
1613       ++ lib.optionals config.security.pam.p11.enable [ pkgs.pam_p11 ]
1614       ++ lib.optionals config.security.pam.enableFscrypt [ pkgs.fscrypt-experimental ]
1615       ++ lib.optionals config.security.pam.u2f.enable [ pkgs.pam_u2f ];
1617     boot.supportedFilesystems = lib.optionals config.security.pam.enableEcryptfs [ "ecryptfs" ];
1619     security.wrappers = {
1620       unix_chkpwd = {
1621         setuid = true;
1622         owner = "root";
1623         group = "root";
1624         source = "${package}/bin/unix_chkpwd";
1625       };
1626     };
1628     environment.etc = lib.mapAttrs' makePAMService config.security.pam.services;
1630     security.pam.services =
1631       { other.text =
1632           ''
1633             auth     required pam_warn.so
1634             auth     required pam_deny.so
1635             account  required pam_warn.so
1636             account  required pam_deny.so
1637             password required pam_warn.so
1638             password required pam_deny.so
1639             session  required pam_warn.so
1640             session  required pam_deny.so
1641           '';
1643         # Most of these should be moved to specific modules.
1644         i3lock = {};
1645         i3lock-color = {};
1646         vlock = {};
1647         xlock = {};
1648         xscreensaver = {};
1650         runuser = { rootOK = true; unixAuth = false; setEnvironment = false; };
1652         /* FIXME: should runuser -l start a systemd session? Currently
1653            it complains "Cannot create session: Already running in a
1654            session". */
1655         runuser-l = { rootOK = true; unixAuth = false; };
1656       } // lib.optionalAttrs (config.security.pam.enableFscrypt) {
1657         # Allow fscrypt to verify login passphrase
1658         fscrypt = {};
1659       };
1661     security.apparmor.includes."abstractions/pam" =
1662       lib.concatMapStrings
1663         (name: "r ${config.environment.etc."pam.d/${name}".source},\n")
1664         (lib.attrNames config.security.pam.services) +
1665       (with lib; pipe config.security.pam.services [
1666         lib.attrValues
1667         (catAttrs "rules")
1668         (lib.concatMap lib.attrValues)
1669         (lib.concatMap lib.attrValues)
1670         (lib.filter (rule: rule.enable))
1671         (lib.catAttrs "modulePath")
1672         # TODO(@uninsane): replace this warning + lib.filter with just an assertion
1673         (map (modulePath: lib.warnIfNot
1674           (lib.hasPrefix "/" modulePath)
1675           ''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>''
1676           modulePath
1677         ))
1678         (lib.filter (lib.hasPrefix "/"))
1679         lib.unique
1680         (map (module: "mr ${module},"))
1681         concatLines
1682       ]);
1684     security.sudo.extraConfig = optionalSudoConfigForSSHAgentAuth;
1685     security.sudo-rs.extraConfig = optionalSudoConfigForSSHAgentAuth;
1686   };