1# This module provides configuration for the PAM (Pluggable 2# Authentication Modules) system. 3 4{ config, lib, pkgs, ... }: 5 6with lib; 7 8let 9 parentConfig = config; 10 11 pamOpts = { config, name, ... }: let cfg = config; in let config = parentConfig; in { 12 13 options = { 14 15 name = mkOption { 16 example = "sshd"; 17 type = types.str; 18 description = "Name of the PAM service."; 19 }; 20 21 unixAuth = mkOption { 22 default = true; 23 type = types.bool; 24 description = '' 25 Whether users can log in with passwords defined in 26 <filename>/etc/shadow</filename>. 27 ''; 28 }; 29 30 rootOK = mkOption { 31 default = false; 32 type = types.bool; 33 description = '' 34 If set, root doesn't need to authenticate (e.g. for the 35 <command>useradd</command> service). 36 ''; 37 }; 38 39 u2fAuth = mkOption { 40 default = config.security.pam.enableU2F; 41 type = types.bool; 42 description = '' 43 If set, users listed in 44 <filename>~/.yubico/u2f_keys</filename> are able to log in 45 with the associated U2F key. 46 ''; 47 }; 48 49 usbAuth = mkOption { 50 default = config.security.pam.usb.enable; 51 type = types.bool; 52 description = '' 53 If set, users listed in 54 <filename>/etc/pamusb.conf</filename> are able to log in 55 with the associated USB key. 56 ''; 57 }; 58 59 otpwAuth = mkOption { 60 default = config.security.pam.enableOTPW; 61 type = types.bool; 62 description = '' 63 If set, the OTPW system will be used (if 64 <filename>~/.otpw</filename> exists). 65 ''; 66 }; 67 68 fprintAuth = mkOption { 69 default = config.services.fprintd.enable; 70 type = types.bool; 71 description = '' 72 If set, fingerprint reader will be used (if exists and 73 your fingerprints are enrolled). 74 ''; 75 }; 76 77 oathAuth = mkOption { 78 default = config.security.pam.oath.enable; 79 type = types.bool; 80 description = '' 81 If set, the OATH Toolkit will be used. 82 ''; 83 }; 84 85 sshAgentAuth = mkOption { 86 default = false; 87 type = types.bool; 88 description = '' 89 If set, the calling user's SSH agent is used to authenticate 90 against the keys in the calling user's 91 <filename>~/.ssh/authorized_keys</filename>. This is useful 92 for <command>sudo</command> on password-less remote systems. 93 ''; 94 }; 95 96 startSession = mkOption { 97 default = false; 98 type = types.bool; 99 description = '' 100 If set, the service will register a new session with 101 systemd's login manager. For local sessions, this will give 102 the user access to audio devices, CD-ROM drives. In the 103 default PolicyKit configuration, it also allows the user to 104 reboot the system. 105 ''; 106 }; 107 108 setLoginUid = mkOption { 109 type = types.bool; 110 description = '' 111 Set the login uid of the process 112 (<filename>/proc/self/loginuid</filename>) for auditing 113 purposes. The login uid is only set by entry points like 114 <command>login</command> and <command>sshd</command>, not by 115 commands like <command>sudo</command>. 116 ''; 117 }; 118 119 forwardXAuth = mkOption { 120 default = false; 121 type = types.bool; 122 description = '' 123 Whether X authentication keys should be passed from the 124 calling user to the target user (e.g. for 125 <command>su</command>) 126 ''; 127 }; 128 129 pamMount = mkOption { 130 default = config.security.pam.mount.enable; 131 type = types.bool; 132 description = '' 133 Enable PAM mount (pam_mount) system to mount fileystems on user login. 134 ''; 135 }; 136 137 allowNullPassword = mkOption { 138 default = false; 139 type = types.bool; 140 description = '' 141 Whether to allow logging into accounts that have no password 142 set (i.e., have an empty password field in 143 <filename>/etc/passwd</filename> or 144 <filename>/etc/group</filename>). This does not enable 145 logging into disabled accounts (i.e., that have the password 146 field set to <literal>!</literal>). Note that regardless of 147 what the pam_unix documentation says, accounts with hashed 148 empty passwords are always allowed to log in. 149 ''; 150 }; 151 152 requireWheel = mkOption { 153 default = false; 154 type = types.bool; 155 description = '' 156 Whether to permit root access only to members of group wheel. 157 ''; 158 }; 159 160 limits = mkOption { 161 description = '' 162 Attribute set describing resource limits. Defaults to the 163 value of <option>security.pam.loginLimits</option>. 164 ''; 165 }; 166 167 showMotd = mkOption { 168 default = false; 169 type = types.bool; 170 description = "Whether to show the message of the day."; 171 }; 172 173 makeHomeDir = mkOption { 174 default = false; 175 type = types.bool; 176 description = '' 177 Whether to try to create home directories for users 178 with <literal>$HOME</literal>s pointing to nonexistent 179 locations on session login. 180 ''; 181 }; 182 183 updateWtmp = mkOption { 184 default = false; 185 type = types.bool; 186 description = "Whether to update <filename>/var/log/wtmp</filename>."; 187 }; 188 189 logFailures = mkOption { 190 default = false; 191 type = types.bool; 192 description = "Whether to log authentication failures in <filename>/var/log/faillog</filename>."; 193 }; 194 195 enableAppArmor = mkOption { 196 default = false; 197 type = types.bool; 198 description = '' 199 Enable support for attaching AppArmor profiles at the 200 user/group level, e.g., as part of a role based access 201 control scheme. 202 ''; 203 }; 204 205 text = mkOption { 206 type = types.nullOr types.lines; 207 description = "Contents of the PAM service file."; 208 }; 209 210 }; 211 212 config = { 213 name = mkDefault name; 214 setLoginUid = mkDefault cfg.startSession; 215 limits = mkDefault config.security.pam.loginLimits; 216 217 # !!! TODO: move the LDAP stuff to the LDAP module, and the 218 # Samba stuff to the Samba module. This requires that the PAM 219 # module provides the right hooks. 220 text = mkDefault 221 ('' 222 # Account management. 223 account sufficient pam_unix.so 224 ${optionalString config.users.ldap.enable 225 "account sufficient ${pam_ldap}/lib/security/pam_ldap.so"} 226 ${optionalString config.krb5.enable 227 "account sufficient ${pam_krb5}/lib/security/pam_krb5.so"} 228 229 # Authentication management. 230 ${optionalString cfg.rootOK 231 "auth sufficient pam_rootok.so"} 232 ${optionalString cfg.requireWheel 233 "auth required pam_wheel.so use_uid"} 234 ${optionalString cfg.logFailures 235 "auth required pam_tally.so"} 236 ${optionalString (config.security.pam.enableSSHAgentAuth && cfg.sshAgentAuth) 237 "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"} 238 ${optionalString cfg.fprintAuth 239 "auth sufficient ${pkgs.fprintd}/lib/security/pam_fprintd.so"} 240 ${optionalString cfg.u2fAuth 241 "auth sufficient ${pkgs.pam_u2f}/lib/security/pam_u2f.so"} 242 ${optionalString cfg.usbAuth 243 "auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so"} 244 '' + 245 # Modules in this block require having the password set in PAM_AUTHTOK. 246 # pam_unix is marked as 'sufficient' on NixOS which means nothing will run 247 # after it succeeds. Certain modules need to run after pam_unix 248 # prompts the user for password so we run it once with 'required' at an 249 # earlier point and it will run again with 'sufficient' further down. 250 # We use try_first_pass the second time to avoid prompting password twice 251 (optionalString (cfg.unixAuth && (config.security.pam.enableEcryptfs || cfg.pamMount)) '' 252 auth required pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth 253 ${optionalString config.security.pam.enableEcryptfs 254 "auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap"} 255 ${optionalString cfg.pamMount 256 "auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so"} 257 '') + '' 258 ${optionalString cfg.unixAuth 259 "auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth try_first_pass"} 260 ${optionalString cfg.otpwAuth 261 "auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so"} 262 ${let oath = config.security.pam.oath; in optionalString cfg.oathAuth 263 "auth sufficient ${pkgs.oathToolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}"} 264 ${optionalString config.users.ldap.enable 265 "auth sufficient ${pam_ldap}/lib/security/pam_ldap.so use_first_pass"} 266 ${optionalString config.krb5.enable '' 267 auth [default=ignore success=1 service_err=reset] ${pam_krb5}/lib/security/pam_krb5.so use_first_pass 268 auth [default=die success=done] ${pam_ccreds}/lib/security/pam_ccreds.so action=validate use_first_pass 269 auth sufficient ${pam_ccreds}/lib/security/pam_ccreds.so action=store use_first_pass 270 ''} 271 auth required pam_deny.so 272 273 # Password management. 274 password requisite pam_unix.so nullok sha512 275 ${optionalString config.security.pam.enableEcryptfs 276 "password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"} 277 ${optionalString cfg.pamMount 278 "password optional ${pkgs.pam_mount}/lib/security/pam_mount.so"} 279 ${optionalString config.users.ldap.enable 280 "password sufficient ${pam_ldap}/lib/security/pam_ldap.so"} 281 ${optionalString config.krb5.enable 282 "password sufficient ${pam_krb5}/lib/security/pam_krb5.so use_first_pass"} 283 ${optionalString config.services.samba.syncPasswordsByPam 284 "password optional ${pkgs.samba}/lib/security/pam_smbpass.so nullok use_authtok try_first_pass"} 285 286 # Session management. 287 session required pam_env.so envfile=${config.system.build.pamEnvironment} 288 session required pam_unix.so 289 ${optionalString cfg.setLoginUid 290 "session ${ 291 if config.boot.isContainer then "optional" else "required" 292 } pam_loginuid.so"} 293 ${optionalString cfg.makeHomeDir 294 "session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=/etc/skel umask=0022"} 295 ${optionalString cfg.updateWtmp 296 "session required ${pkgs.pam}/lib/security/pam_lastlog.so silent"} 297 ${optionalString config.security.pam.enableEcryptfs 298 "session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"} 299 ${optionalString config.users.ldap.enable 300 "session optional ${pam_ldap}/lib/security/pam_ldap.so"} 301 ${optionalString config.krb5.enable 302 "session optional ${pam_krb5}/lib/security/pam_krb5.so"} 303 ${optionalString cfg.otpwAuth 304 "session optional ${pkgs.otpw}/lib/security/pam_otpw.so"} 305 ${optionalString cfg.startSession 306 "session optional ${pkgs.systemd}/lib/security/pam_systemd.so"} 307 ${optionalString cfg.forwardXAuth 308 "session optional pam_xauth.so xauthpath=${pkgs.xorg.xauth}/bin/xauth systemuser=99"} 309 ${optionalString (cfg.limits != []) 310 "session required ${pkgs.pam}/lib/security/pam_limits.so conf=${makeLimitsConf cfg.limits}"} 311 ${optionalString (cfg.showMotd && config.users.motd != null) 312 "session optional ${pkgs.pam}/lib/security/pam_motd.so motd=${motd}"} 313 ${optionalString cfg.pamMount 314 "session optional ${pkgs.pam_mount}/lib/security/pam_mount.so"} 315 ${optionalString (cfg.enableAppArmor && config.security.apparmor.enable) 316 "session optional ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so order=user,group,default debug"} 317 ''); 318 }; 319 320 }; 321 322 323 inherit (pkgs) pam_krb5 pam_ccreds; 324 325 pam_ldap = if config.users.ldap.daemon.enable then pkgs.nss_pam_ldapd else pkgs.pam_ldap; 326 327 # Create a limits.conf(5) file. 328 makeLimitsConf = limits: 329 pkgs.writeText "limits.conf" 330 (concatMapStrings ({ domain, type, item, value }: 331 "${domain} ${type} ${item} ${toString value}\n") 332 limits); 333 334 motd = pkgs.writeText "motd" config.users.motd; 335 336 makePAMService = pamService: 337 { source = pkgs.writeText "${pamService.name}.pam" pamService.text; 338 target = "pam.d/${pamService.name}"; 339 }; 340 341in 342 343{ 344 345 ###### interface 346 347 options = { 348 349 security.pam.loginLimits = mkOption { 350 default = []; 351 example = 352 [ { domain = "ftp"; 353 type = "hard"; 354 item = "nproc"; 355 value = "0"; 356 } 357 { domain = "@student"; 358 type = "-"; 359 item = "maxlogins"; 360 value = "4"; 361 } 362 ]; 363 364 description = 365 '' Define resource limits that should apply to users or groups. 366 Each item in the list should be an attribute set with a 367 <varname>domain</varname>, <varname>type</varname>, 368 <varname>item</varname>, and <varname>value</varname> 369 attribute. The syntax and semantics of these attributes 370 must be that described in the limits.conf(5) man page. 371 ''; 372 }; 373 374 security.pam.services = mkOption { 375 default = []; 376 type = types.loaOf types.optionSet; 377 options = [ pamOpts ]; 378 description = 379 '' 380 This option defines the PAM services. A service typically 381 corresponds to a program that uses PAM, 382 e.g. <command>login</command> or <command>passwd</command>. 383 Each attribute of this set defines a PAM service, with the attribute name 384 defining the name of the service. 385 ''; 386 }; 387 388 security.pam.enableSSHAgentAuth = mkOption { 389 default = false; 390 description = 391 '' 392 Enable sudo logins if the user's SSH agent provides a key 393 present in <filename>~/.ssh/authorized_keys</filename>. 394 This allows machines to exclusively use SSH keys instead of 395 passwords. 396 ''; 397 }; 398 399 security.pam.enableOTPW = mkOption { 400 default = false; 401 description = '' 402 Enable the OTPW (one-time password) PAM module. 403 ''; 404 }; 405 406 security.pam.enableU2F = mkOption { 407 default = false; 408 description = '' 409 Enable the U2F PAM module. 410 ''; 411 }; 412 413 security.pam.enableEcryptfs = mkOption { 414 default = false; 415 description = '' 416 Enable eCryptfs PAM module (mounting ecryptfs home directory on login). 417 ''; 418 }; 419 420 users.motd = mkOption { 421 default = null; 422 example = "Today is Sweetmorn, the 4th day of The Aftermath in the YOLD 3178."; 423 type = types.nullOr types.lines; 424 description = "Message of the day shown to users when they log in."; 425 }; 426 427 }; 428 429 430 ###### implementation 431 432 config = { 433 434 environment.systemPackages = 435 # Include the PAM modules in the system path mostly for the manpages. 436 [ pkgs.pam ] 437 ++ optional config.users.ldap.enable pam_ldap 438 ++ optionals config.krb5.enable [pam_krb5 pam_ccreds] 439 ++ optionals config.security.pam.enableOTPW [ pkgs.otpw ] 440 ++ optionals config.security.pam.oath.enable [ pkgs.oathToolkit ] 441 ++ optionals config.security.pam.enableU2F [ pkgs.pam_u2f ] 442 ++ optionals config.security.pam.enableEcryptfs [ pkgs.ecryptfs ]; 443 444 security.setuidPrograms = 445 optionals config.security.pam.enableEcryptfs [ "mount.ecryptfs_private" "umount.ecryptfs_private" ]; 446 447 environment.etc = 448 mapAttrsToList (n: v: makePAMService v) config.security.pam.services; 449 450 security.setuidOwners = [ { 451 program = "unix_chkpwd"; 452 source = "${pkgs.pam}/sbin/unix_chkpwd.orig"; 453 owner = "root"; 454 setuid = true; 455 } ]; 456 457 security.pam.services = 458 { other.text = 459 '' 460 auth required pam_warn.so 461 auth required pam_deny.so 462 account required pam_warn.so 463 account required pam_deny.so 464 password required pam_warn.so 465 password required pam_deny.so 466 session required pam_warn.so 467 session required pam_deny.so 468 ''; 469 470 # Most of these should be moved to specific modules. 471 cups = {}; 472 ftp = {}; 473 i3lock = {}; 474 screen = {}; 475 vlock = {}; 476 xlock = {}; 477 xscreensaver = {}; 478 }; 479 480 }; 481 482}