diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix index c2f16e5ed8e..12d9be94663 100644 --- a/nixos/modules/config/users-groups.nix +++ b/nixos/modules/config/users-groups.nix @@ -6,6 +6,16 @@ let ids = config.ids; cfg = config.users; + # Check whether a password hash will allow login. + allowsLogin = hash: + hash == "" # login without password + || !(lib.elem hash + [ null # password login disabled + "!" # password login disabled + "!!" # a variant of "!" + "*" # password unset + ]); + passwordDescription = '' The options , and @@ -25,17 +35,19 @@ let ''; hashedPasswordDescription = '' - To generate hashed password install mkpasswd + To generate a hashed password install the mkpasswd package and run mkpasswd -m sha-512. - For password-less logins without password prompt, use - the empty string "". + If set to an empty string (""), this user will + be able to log in without being asked for a password (but not via remote + services such as SSH, or indirectly via su or + sudo). This should only be used for e.g. bootable + live systems. Note: this is different from setting an empty password, + which ca be achieved using . - For logins with a fixed password (including the empty-string password with - prompt), use one of the un-hashed password options instead, such as - . - - Such unprotected logins should only be used for e.g. bootable live systems. + If set to null (default) this user will not + be able to log in using a password (i.e. via login + command). ''; userOpts = { name, config, ... }: { @@ -415,6 +427,12 @@ in { imports = [ (mkAliasOptionModule [ "users" "extraUsers" ] [ "users" "users" ]) (mkAliasOptionModule [ "users" "extraGroups" ] [ "users" "groups" ]) + (mkChangedOptionModule + [ "security" "initialRootPassword" ] + [ "users" "users" "root" "initialHashedPassword" ] + (cfg: if cfg.security.initialHashedPassword == "!" + then null + else cfg.security.initialHashedPassword)) ]; ###### interface @@ -486,14 +504,6 @@ in { ''; }; - # FIXME: obsolete - will remove. - security.initialRootPassword = mkOption { - type = types.str; - default = "!"; - example = ""; - visible = false; - }; - }; @@ -508,7 +518,6 @@ in { home = "/root"; shell = mkDefault cfg.defaultUserShell; group = "root"; - initialHashedPassword = mkDefault config.security.initialRootPassword; }; nobody = { uid = ids.uids.nobody; @@ -597,7 +606,7 @@ in { || cfg.group == "wheel" || elem "wheel" cfg.extraGroups) && - ((cfg.hashedPassword != null && cfg.hashedPassword != "!") + (allowsLogin cfg.hashedPassword || cfg.password != null || cfg.passwordFile != null || cfg.openssh.authorizedKeys.keys != [] @@ -607,7 +616,17 @@ in { Neither the root account nor any wheel user has a password or SSH authorized key. You must set one to prevent being locked out of your system.''; } - ]; + ] ++ flip mapAttrsToList cfg.users (name: user: + { + assertion = (user.hashedPassword != null) + -> (builtins.match ".*:.*" user.hashedPassword == null); + message = '' + The password hash of user "${name}" contains a ":" character. + This is invalid and would break the login system because the fields + of /etc/shadow (file where hashes are stored) are colon-separated. + Please check the value of option `users.users."${name}".hashedPassword`.''; + } + ); warnings = builtins.filter (x: x != null) ( @@ -630,14 +649,13 @@ in { content = "${base64}${sep}${base64}"; mcf = "^${sep}${scheme}${sep}${content}$"; in - if (user.hashedPassword != null + if (allowsLogin user.hashedPassword + && user.hashedPassword != "" # login without password && builtins.match mcf user.hashedPassword == null) - then - '' + then '' The password hash of user "${name}" may be invalid. You must set a valid hash or the user will be locked out of their account. Please - check the value of option `users.users."${name}".hashedPassword`. - '' + check the value of option `users.users."${name}".hashedPassword`.'' else null ));