lol

Merge pull request #208363 from GeorgesAlkhouri/refactor/nixos-modules-shadow

nixos/shadow: refactor login.defs config options

authored by

Aaron Andersen and committed by
GitHub
7362e078 1f0dc6a7

+198 -90
+198 -90
nixos/modules/programs/shadow.nix
··· 1 1 # Configuration for the pwdutils suite of tools: passwd, useradd, etc. 2 - 3 2 { config, lib, utils, pkgs, ... }: 4 - 5 3 with lib; 6 - 7 4 let 5 + cfg = config.security.loginDefs; 6 + in 7 + { 8 + options = with types; { 9 + security.loginDefs = { 10 + package = mkPackageOptionMD pkgs "shadow" { }; 8 11 9 - /* 10 - There are three different sources for user/group id ranges, each of which gets 11 - used by different programs: 12 - - The login.defs file, used by the useradd, groupadd and newusers commands 13 - - The update-users-groups.pl file, used by NixOS in the activation phase to 14 - decide on which ids to use for declaratively defined users without a static 15 - id 16 - - Systemd compile time options -Dsystem-uid-max= and -Dsystem-gid-max=, used 17 - by systemd for features like ConditionUser=@system and systemd-sysusers 18 - */ 19 - loginDefs = 20 - '' 21 - DEFAULT_HOME yes 12 + chfnRestrict = mkOption { 13 + description = mdDoc '' 14 + Use chfn SUID to allow non-root users to change their account GECOS information. 15 + ''; 16 + type = nullOr str; 17 + default = null; 18 + }; 22 19 23 - SYS_UID_MIN 400 24 - SYS_UID_MAX 999 25 - UID_MIN 1000 26 - UID_MAX 29999 20 + settings = mkOption { 21 + description = mdDoc '' 22 + Config options for the /etc/login.defs file, that defines 23 + the site-specific configuration for the shadow password suite. 24 + See login.defs(5) man page for available options. 25 + ''; 26 + type = submodule { 27 + freeformType = (pkgs.formats.keyValue { }).type; 28 + /* There are three different sources for user/group id ranges, each of which gets 29 + used by different programs: 30 + - The login.defs file, used by the useradd, groupadd and newusers commands 31 + - The update-users-groups.pl file, used by NixOS in the activation phase to 32 + decide on which ids to use for declaratively defined users without a static 33 + id 34 + - Systemd compile time options -Dsystem-uid-max= and -Dsystem-gid-max=, used 35 + by systemd for features like ConditionUser=@system and systemd-sysusers 36 + */ 37 + options = { 38 + DEFAULT_HOME = mkOption { 39 + description = mdDoc "Indicate if login is allowed if we can't cd to the home directory."; 40 + default = "yes"; 41 + type = enum [ "yes" "no" ]; 42 + }; 27 43 28 - SYS_GID_MIN 400 29 - SYS_GID_MAX 999 30 - GID_MIN 1000 31 - GID_MAX 29999 44 + ENCRYPT_METHOD = mkOption { 45 + description = mdDoc "This defines the system default encryption algorithm for encrypting passwords."; 46 + # The default crypt() method, keep in sync with the PAM default 47 + default = "YESCRYPT"; 48 + type = enum [ "YESCRYPT" "SHA512" "SHA256" "MD5" "DES"]; 49 + }; 32 50 33 - TTYGROUP tty 34 - TTYPERM 0620 51 + SYS_UID_MIN = mkOption { 52 + description = mdDoc "Range of user IDs used for the creation of system users by useradd or newusers."; 53 + default = 400; 54 + type = int; 55 + }; 35 56 36 - # Ensure privacy for newly created home directories. 37 - UMASK 077 57 + SYS_UID_MAX = mkOption { 58 + description = mdDoc "Range of user IDs used for the creation of system users by useradd or newusers."; 59 + default = 999; 60 + type = int; 61 + }; 38 62 39 - # Uncomment this and install chfn SUID to allow non-root 40 - # users to change their account GECOS information. 41 - # This should be made configurable. 42 - #CHFN_RESTRICT frwh 63 + UID_MIN = mkOption { 64 + description = mdDoc "Range of user IDs used for the creation of regular users by useradd or newusers."; 65 + default = 1000; 66 + type = int; 67 + }; 43 68 44 - # The default crypt() method, keep in sync with the PAM default 45 - ENCRYPT_METHOD YESCRYPT 46 - ''; 69 + UID_MAX = mkOption { 70 + description = mdDoc "Range of user IDs used for the creation of regular users by useradd or newusers."; 71 + default = 29999; 72 + type = int; 73 + }; 47 74 48 - mkSetuidRoot = source: 49 - { setuid = true; 50 - owner = "root"; 51 - group = "root"; 52 - inherit source; 53 - }; 75 + SYS_GID_MIN = mkOption { 76 + description = mdDoc "Range of group IDs used for the creation of system groups by useradd, groupadd, or newusers"; 77 + default = 400; 78 + type = int; 79 + }; 54 80 55 - in 81 + SYS_GID_MAX = mkOption { 82 + description = mdDoc "Range of group IDs used for the creation of system groups by useradd, groupadd, or newusers"; 83 + default = 999; 84 + type = int; 85 + }; 56 86 57 - { 87 + GID_MIN = mkOption { 88 + description = mdDoc "Range of group IDs used for the creation of regular groups by useradd, groupadd, or newusers."; 89 + default = 1000; 90 + type = int; 91 + }; 58 92 59 - ###### interface 93 + GID_MAX = mkOption { 94 + description = mdDoc "Range of group IDs used for the creation of regular groups by useradd, groupadd, or newusers."; 95 + default = 29999; 96 + type = int; 97 + }; 60 98 61 - options = { 99 + TTYGROUP = mkOption { 100 + description = mdDoc '' 101 + The terminal permissions: the login tty will be owned by the TTYGROUP group, 102 + and the permissions will be set to TTYPERM''; 103 + default = "tty"; 104 + type = str; 105 + }; 62 106 63 - users.defaultUserShell = lib.mkOption { 64 - description = lib.mdDoc '' 107 + TTYPERM = mkOption { 108 + description = mdDoc '' 109 + The terminal permissions: the login tty will be owned by the TTYGROUP group, 110 + and the permissions will be set to TTYPERM''; 111 + default = "0620"; 112 + type = str; 113 + }; 114 + 115 + # Ensure privacy for newly created home directories. 116 + UMASK = mkOption { 117 + description = mdDoc "The file mode creation mask is initialized to this value."; 118 + default = "077"; 119 + type = str; 120 + }; 121 + }; 122 + }; 123 + default = { }; 124 + }; 125 + }; 126 + 127 + users.defaultUserShell = mkOption { 128 + description = mdDoc '' 65 129 This option defines the default shell assigned to user 66 130 accounts. This can be either a full system path or a shell package. 67 131 ··· 69 133 used outside the store (in particular in /etc/passwd). 70 134 ''; 71 135 example = literalExpression "pkgs.zsh"; 72 - type = types.either types.path types.shellPackage; 136 + type = either path shellPackage; 73 137 }; 74 - 75 138 }; 76 - 77 139 78 140 ###### implementation 79 141 80 142 config = { 143 + assertions = [ 144 + { 145 + assertion = cfg.settings.SYS_UID_MIN <= cfg.settings.SYS_UID_MAX; 146 + message = "SYS_UID_MIN must be less than or equal to SYS_UID_MAX"; 147 + } 148 + { 149 + assertion = cfg.settings.UID_MIN <= cfg.settings.UID_MAX; 150 + message = "UID_MIN must be less than or equal to UID_MAX"; 151 + } 152 + { 153 + assertion = cfg.settings.SYS_GID_MIN <= cfg.settings.SYS_GID_MAX; 154 + message = "SYS_GID_MIN must be less than or equal to SYS_GID_MAX"; 155 + } 156 + { 157 + assertion = cfg.settings.GID_MIN <= cfg.settings.GID_MAX; 158 + message = "GID_MIN must be less than or equal to GID_MAX"; 159 + } 160 + ]; 81 161 82 - environment.systemPackages = 83 - lib.optional config.users.mutableUsers pkgs.shadow ++ 84 - lib.optional (types.shellPackage.check config.users.defaultUserShell) 85 - config.users.defaultUserShell; 162 + security.loginDefs.settings.CHFN_RESTRICT = 163 + mkIf (cfg.chfnRestrict != null) cfg.chfnRestrict; 164 + 165 + environment.systemPackages = optional config.users.mutableUsers cfg.package 166 + ++ optional (types.shellPackage.check config.users.defaultUserShell) config.users.defaultUserShell 167 + ++ optional (cfg.chfnRestrict != null) pkgs.util-linux; 86 168 87 169 environment.etc = 88 - { # /etc/login.defs: global configuration for pwdutils. You 89 - # cannot login without it! 90 - "login.defs".source = pkgs.writeText "login.defs" loginDefs; 170 + # Create custom toKeyValue generator 171 + # see https://man7.org/linux/man-pages/man5/login.defs.5.html for config specification 172 + let 173 + toKeyValue = generators.toKeyValue { 174 + mkKeyValue = generators.mkKeyValueDefault { } " "; 175 + }; 176 + in 177 + { 178 + # /etc/login.defs: global configuration for pwdutils. 179 + # You cannot login without it! 180 + "login.defs".source = pkgs.writeText "login.defs" (toKeyValue cfg.settings); 91 181 92 182 # /etc/default/useradd: configuration for useradd. 93 - "default/useradd".source = pkgs.writeText "useradd" 94 - '' 95 - GROUP=100 96 - HOME=/home 97 - SHELL=${utils.toShellPath config.users.defaultUserShell} 98 - ''; 183 + "default/useradd".source = pkgs.writeText "useradd" '' 184 + GROUP=100 185 + HOME=/home 186 + SHELL=${utils.toShellPath config.users.defaultUserShell} 187 + ''; 99 188 }; 100 189 101 - security.pam.services = 102 - { chsh = { rootOK = true; }; 103 - chfn = { rootOK = true; }; 104 - su = { rootOK = true; forwardXAuth = true; logFailures = true; }; 105 - passwd = {}; 106 - # Note: useradd, groupadd etc. aren't setuid root, so it 107 - # doesn't really matter what the PAM config says as long as it 108 - # lets root in. 109 - useradd = { rootOK = true; }; 110 - usermod = { rootOK = true; }; 111 - userdel = { rootOK = true; }; 112 - groupadd = { rootOK = true; }; 113 - groupmod = { rootOK = true; }; 114 - groupmems = { rootOK = true; }; 115 - groupdel = { rootOK = true; }; 116 - login = { startSession = true; allowNullPassword = true; showMotd = true; updateWtmp = true; }; 117 - chpasswd = { rootOK = true; }; 190 + security.pam.services = { 191 + chsh = { rootOK = true; }; 192 + chfn = { rootOK = true; }; 193 + su = { 194 + rootOK = true; 195 + forwardXAuth = true; 196 + logFailures = true; 118 197 }; 119 - 120 - security.wrappers = { 121 - su = mkSetuidRoot "${pkgs.shadow.su}/bin/su"; 122 - sg = mkSetuidRoot "${pkgs.shadow.out}/bin/sg"; 123 - newgrp = mkSetuidRoot "${pkgs.shadow.out}/bin/newgrp"; 124 - newuidmap = mkSetuidRoot "${pkgs.shadow.out}/bin/newuidmap"; 125 - newgidmap = mkSetuidRoot "${pkgs.shadow.out}/bin/newgidmap"; 126 - } // lib.optionalAttrs config.users.mutableUsers { 127 - chsh = mkSetuidRoot "${pkgs.shadow.out}/bin/chsh"; 128 - passwd = mkSetuidRoot "${pkgs.shadow.out}/bin/passwd"; 198 + passwd = { }; 199 + # Note: useradd, groupadd etc. aren't setuid root, so it 200 + # doesn't really matter what the PAM config says as long as it 201 + # lets root in. 202 + useradd.rootOK = true; 203 + usermod.rootOK = true; 204 + userdel.rootOK = true; 205 + groupadd.rootOK = true; 206 + groupmod.rootOK = true; 207 + groupmems.rootOK = true; 208 + groupdel.rootOK = true; 209 + login = { 210 + startSession = true; 211 + allowNullPassword = true; 212 + showMotd = true; 213 + updateWtmp = true; 214 + }; 215 + chpasswd = { rootOK = true; }; 129 216 }; 217 + 218 + security.wrappers = 219 + let 220 + mkSetuidRoot = source: { 221 + setuid = true; 222 + owner = "root"; 223 + group = "root"; 224 + inherit source; 225 + }; 226 + in 227 + { 228 + su = mkSetuidRoot "${cfg.package.su}/bin/su"; 229 + sg = mkSetuidRoot "${cfg.package.out}/bin/sg"; 230 + newgrp = mkSetuidRoot "${cfg.package.out}/bin/newgrp"; 231 + newuidmap = mkSetuidRoot "${cfg.package.out}/bin/newuidmap"; 232 + newgidmap = mkSetuidRoot "${cfg.package.out}/bin/newgidmap"; 233 + } 234 + // optionalAttrs config.users.mutableUsers { 235 + chsh = mkSetuidRoot "${cfg.package.out}/bin/chsh"; 236 + passwd = mkSetuidRoot "${cfg.package.out}/bin/passwd"; 237 + }; 130 238 }; 131 239 }