# This module provides configuration for the PAM (Pluggable # Authentication Modules) system. { config, lib, pkgs, ... }: with lib; let parentConfig = config; pamOpts = { config, name, ... }: let cfg = config; in let config = parentConfig; in { options = { name = mkOption { example = "sshd"; type = types.str; description = "Name of the PAM service."; }; unixAuth = mkOption { default = true; type = types.bool; description = '' Whether users can log in with passwords defined in /etc/shadow. ''; }; rootOK = mkOption { default = false; type = types.bool; description = '' If set, root doesn't need to authenticate (e.g. for the useradd service). ''; }; u2fAuth = mkOption { default = config.security.pam.enableU2F; type = types.bool; description = '' If set, users listed in ~/.yubico/u2f_keys are able to log in with the associated U2F key. ''; }; usbAuth = mkOption { default = config.security.pam.usb.enable; type = types.bool; description = '' If set, users listed in /etc/pamusb.conf are able to log in with the associated USB key. ''; }; otpwAuth = mkOption { default = config.security.pam.enableOTPW; type = types.bool; description = '' If set, the OTPW system will be used (if ~/.otpw exists). ''; }; fprintAuth = mkOption { default = config.services.fprintd.enable; type = types.bool; description = '' If set, fingerprint reader will be used (if exists and your fingerprints are enrolled). ''; }; oathAuth = mkOption { default = config.security.pam.enableOATH; type = types.bool; description = '' If set, the OATH Toolkit will be used. ''; }; sshAgentAuth = mkOption { default = false; type = types.bool; description = '' If set, the calling user's SSH agent is used to authenticate against the keys in the calling user's ~/.ssh/authorized_keys. This is useful for sudo on password-less remote systems. ''; }; startSession = mkOption { default = false; type = types.bool; description = '' If set, the service will register a new session with systemd's login manager. For local sessions, this will give the user access to audio devices, CD-ROM drives. In the default PolicyKit configuration, it also allows the user to reboot the system. ''; }; setLoginUid = mkOption { type = types.bool; description = '' Set the login uid of the process (/proc/self/loginuid) for auditing purposes. The login uid is only set by ‘entry points’ like login and sshd, not by commands like sudo. ''; }; forwardXAuth = mkOption { default = false; type = types.bool; description = '' Whether X authentication keys should be passed from the calling user to the target user (e.g. for su) ''; }; allowNullPassword = mkOption { default = false; type = types.bool; description = '' Whether to allow logging into accounts that have no password set (i.e., have an empty password field in /etc/passwd or /etc/group). This does not enable logging into disabled accounts (i.e., that have the password field set to !). Note that regardless of what the pam_unix documentation says, accounts with hashed empty passwords are always allowed to log in. ''; }; requireWheel = mkOption { default = false; type = types.bool; description = '' Whether to permit root access only to members of group wheel. ''; }; limits = mkOption { description = '' Attribute set describing resource limits. Defaults to the value of . ''; }; showMotd = mkOption { default = false; type = types.bool; description = "Whether to show the message of the day."; }; makeHomeDir = mkOption { default = false; type = types.bool; description = '' Whether to try to create home directories for users with $HOMEs pointing to nonexistent locations on session login. ''; }; updateWtmp = mkOption { default = false; type = types.bool; description = "Whether to update /var/log/wtmp."; }; logFailures = mkOption { default = false; type = types.bool; description = "Whether to log authentication failures in /var/log/faillog."; }; text = mkOption { type = types.nullOr types.lines; description = "Contents of the PAM service file."; }; }; config = { name = mkDefault name; setLoginUid = mkDefault cfg.startSession; limits = mkDefault config.security.pam.loginLimits; # !!! TODO: move the LDAP stuff to the LDAP module, and the # Samba stuff to the Samba module. This requires that the PAM # module provides the right hooks. text = mkDefault '' # Account management. account sufficient pam_unix.so ${optionalString config.users.ldap.enable "account sufficient ${pam_ldap}/lib/security/pam_ldap.so"} ${optionalString config.krb5.enable "account sufficient ${pam_krb5}/lib/security/pam_krb5.so"} # Authentication management. ${optionalString cfg.rootOK "auth sufficient pam_rootok.so"} ${optionalString cfg.requireWheel "auth required pam_wheel.so use_uid"} ${optionalString cfg.logFailures "auth required pam_tally.so"} ${optionalString (config.security.pam.enableSSHAgentAuth && cfg.sshAgentAuth) "auth sufficient ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so file=~/.ssh/authorized_keys:~/.ssh/authorized_keys2:/etc/ssh/authorized_keys.d/%u"} ${optionalString cfg.fprintAuth "auth sufficient ${pkgs.fprintd}/lib/security/pam_fprintd.so"} ${optionalString cfg.u2fAuth "auth sufficient ${pkgs.pam_u2f}/lib/security/pam_u2f.so"} ${optionalString cfg.usbAuth "auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so"} ${optionalString cfg.unixAuth "auth ${if config.security.pam.enableEcryptfs then "required" else "sufficient"} pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth"} ${optionalString config.security.pam.enableEcryptfs "auth required ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap"} ${optionalString cfg.otpwAuth "auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so"} ${optionalString cfg.oathAuth "auth sufficient ${pkgs.oathToolkit}/lib/security/pam_oath.so window=5 usersfile=/etc/users.oath"} ${optionalString config.users.ldap.enable "auth sufficient ${pam_ldap}/lib/security/pam_ldap.so use_first_pass"} ${optionalString config.krb5.enable '' auth [default=ignore success=1 service_err=reset] ${pam_krb5}/lib/security/pam_krb5.so use_first_pass auth [default=die success=done] ${pam_ccreds}/lib/security/pam_ccreds.so action=validate use_first_pass auth sufficient ${pam_ccreds}/lib/security/pam_ccreds.so action=store use_first_pass ''} ${optionalString (! config.security.pam.enableEcryptfs) "auth required pam_deny.so"} # Password management. ${optionalString config.security.pam.enableEcryptfs "password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"} password requisite pam_unix.so nullok sha512 ${optionalString config.users.ldap.enable "password sufficient ${pam_ldap}/lib/security/pam_ldap.so"} ${optionalString config.krb5.enable "password sufficient ${pam_krb5}/lib/security/pam_krb5.so use_first_pass"} ${optionalString config.services.samba.syncPasswordsByPam "password optional ${pkgs.samba}/lib/security/pam_smbpass.so nullok use_authtok try_first_pass"} # Session management. session required pam_env.so envfile=${config.system.build.pamEnvironment} session required pam_unix.so ${optionalString cfg.setLoginUid "session ${ if config.boot.isContainer then "optional" else "required" } pam_loginuid.so"} ${optionalString cfg.makeHomeDir "session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=/etc/skel umask=0022"} ${optionalString cfg.updateWtmp "session required ${pkgs.pam}/lib/security/pam_lastlog.so silent"} ${optionalString config.security.pam.enableEcryptfs "session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"} ${optionalString config.users.ldap.enable "session optional ${pam_ldap}/lib/security/pam_ldap.so"} ${optionalString config.krb5.enable "session optional ${pam_krb5}/lib/security/pam_krb5.so"} ${optionalString cfg.otpwAuth "session optional ${pkgs.otpw}/lib/security/pam_otpw.so"} ${optionalString cfg.oathAuth "session optional ${pkgs.oathToolkit}/lib/security/pam_oath.so window=5 usersfile=/etc/users.oath"} ${optionalString cfg.startSession "session optional ${pkgs.systemd}/lib/security/pam_systemd.so"} ${optionalString cfg.forwardXAuth "session optional pam_xauth.so xauthpath=${pkgs.xorg.xauth}/bin/xauth systemuser=99"} ${optionalString (cfg.limits != []) "session required ${pkgs.pam}/lib/security/pam_limits.so conf=${makeLimitsConf cfg.limits}"} ${optionalString (cfg.showMotd && config.users.motd != null) "session optional ${pkgs.pam}/lib/security/pam_motd.so motd=${motd}"} ''; }; }; inherit (pkgs) pam_krb5 pam_ccreds; pam_ldap = if config.users.ldap.daemon.enable then pkgs.nss_pam_ldapd else pkgs.pam_ldap; # Create a limits.conf(5) file. makeLimitsConf = limits: pkgs.writeText "limits.conf" (concatMapStrings ({ domain, type, item, value }: "${domain} ${type} ${item} ${toString value}\n") limits); motd = pkgs.writeText "motd" config.users.motd; makePAMService = pamService: { source = pkgs.writeText "${pamService.name}.pam" pamService.text; target = "pam.d/${pamService.name}"; }; in { ###### interface options = { security.pam.loginLimits = mkOption { default = []; example = [ { domain = "ftp"; type = "hard"; item = "nproc"; value = "0"; } { domain = "@student"; type = "-"; item = "maxlogins"; value = "4"; } ]; description = '' Define resource limits that should apply to users or groups. Each item in the list should be an attribute set with a domain, type, item, and value attribute. The syntax and semantics of these attributes must be that described in the limits.conf(5) man page. ''; }; security.pam.services = mkOption { default = []; type = types.loaOf types.optionSet; options = [ pamOpts ]; description = '' This option defines the PAM services. A service typically corresponds to a program that uses PAM, e.g. login or passwd. Each attribute of this set defines a PAM service, with the attribute name defining the name of the service. ''; }; security.pam.enableSSHAgentAuth = mkOption { default = false; description = '' Enable sudo logins if the user's SSH agent provides a key present in ~/.ssh/authorized_keys. This allows machines to exclusively use SSH keys instead of passwords. ''; }; security.pam.enableOTPW = mkOption { default = false; description = '' Enable the OTPW (one-time password) PAM module. ''; }; security.pam.enableOATH = mkOption { default = false; description = '' Enable the OATH (one-time password) PAM module. ''; }; security.pam.enableU2F = mkOption { default = false; description = '' Enable the U2F PAM module. ''; }; security.pam.enableEcryptfs = mkOption { default = false; description = '' Enable eCryptfs PAM module (mounting ecryptfs home directory on login). ''; }; users.motd = mkOption { default = null; example = "Today is Sweetmorn, the 4th day of The Aftermath in the YOLD 3178."; type = types.nullOr types.string; description = "Message of the day shown to users when they log in."; }; }; ###### implementation config = { environment.systemPackages = # Include the PAM modules in the system path mostly for the manpages. [ pkgs.pam ] ++ optional config.users.ldap.enable pam_ldap ++ optionals config.krb5.enable [pam_krb5 pam_ccreds] ++ optionals config.security.pam.enableOTPW [ pkgs.otpw ] ++ optionals config.security.pam.enableOATH [ pkgs.oathToolkit ] ++ optionals config.security.pam.enableU2F [ pkgs.pam_u2f ] ++ optionals config.security.pam.enableEcryptfs [ pkgs.ecryptfs ]; security.setuidPrograms = optionals config.security.pam.enableEcryptfs [ "mount.ecryptfs_private" "umount.ecryptfs_private" ]; environment.etc = mapAttrsToList (n: v: makePAMService v) config.security.pam.services; security.setuidOwners = [ { program = "unix_chkpwd"; source = "${pkgs.pam}/sbin/unix_chkpwd.orig"; owner = "root"; setuid = true; } ]; security.pam.services = { other.text = '' auth required pam_warn.so auth required pam_deny.so account required pam_warn.so account required pam_deny.so password required pam_warn.so password required pam_deny.so session required pam_warn.so session required pam_deny.so ''; # Most of these should be moved to specific modules. cups = {}; ftp = {}; i3lock = {}; screen = {}; vlock = {}; xlock = {}; xscreensaver = {}; }; }; }