diff options
Diffstat (limited to 'nixpkgs/nixos/modules/config/users-groups.nix')
-rw-r--r-- | nixpkgs/nixos/modules/config/users-groups.nix | 117 |
1 files changed, 79 insertions, 38 deletions
diff --git a/nixpkgs/nixos/modules/config/users-groups.nix b/nixpkgs/nixos/modules/config/users-groups.nix index 141e43fec39..0ab303d0ae4 100644 --- a/nixpkgs/nixos/modules/config/users-groups.nix +++ b/nixpkgs/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 <option>hashedPassword</option>, <option>password</option> and <option>passwordFile</option> @@ -25,8 +35,19 @@ let ''; hashedPasswordDescription = '' - To generate hashed password install <literal>mkpasswd</literal> + To generate a hashed password install the <literal>mkpasswd</literal> package and run <literal>mkpasswd -m sha-512</literal>. + + If set to an empty string (<literal>""</literal>), 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 <command>su</command> or + <command>sudo</command>). 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 <option>users.users.<name?>.password</option>. + + If set to <literal>null</literal> (default) this user will not + be able to log in using a password (i.e. via <command>login</command> + command). ''; userOpts = { name, config, ... }: { @@ -354,18 +375,6 @@ let }; }; - mkSubuidEntry = user: concatStrings ( - map (range: "${user.name}:${toString range.startUid}:${toString range.count}\n") - user.subUidRanges); - - subuidFile = concatStrings (map mkSubuidEntry (attrValues cfg.users)); - - mkSubgidEntry = user: concatStrings ( - map (range: "${user.name}:${toString range.startGid}:${toString range.count}\n") - user.subGidRanges); - - subgidFile = concatStrings (map mkSubgidEntry (attrValues cfg.users)); - idsAreUnique = set: idAttr: !(fold (name: args@{ dup, acc }: let id = builtins.toString (builtins.getAttr idAttr (builtins.getAttr name set)); @@ -385,6 +394,7 @@ let { inherit (u) name uid group description home createHome isSystemUser password passwordFile hashedPassword + isNormalUser subUidRanges subGidRanges initialPassword initialHashedPassword; shell = utils.toShellPath u.shell; }) cfg.users; @@ -406,6 +416,12 @@ in { imports = [ (mkAliasOptionModule [ "users" "extraUsers" ] [ "users" "users" ]) (mkAliasOptionModule [ "users" "extraGroups" ] [ "users" "groups" ]) + (mkChangedOptionModule + [ "security" "initialRootPassword" ] + [ "users" "users" "root" "initialHashedPassword" ] + (cfg: if cfg.security.initialRootPassword == "!" + then null + else cfg.security.initialRootPassword)) ]; ###### interface @@ -447,7 +463,7 @@ in { users.users = mkOption { default = {}; - type = with types; loaOf (submodule userOpts); + type = with types; attrsOf (submodule userOpts); example = { alice = { uid = 1234; @@ -471,20 +487,12 @@ in { { students.gid = 1001; hackers = { }; }; - type = with types; loaOf (submodule groupOpts); + type = with types; attrsOf (submodule groupOpts); description = '' Additional groups to be created automatically by the system. ''; }; - # FIXME: obsolete - will remove. - security.initialRootPassword = mkOption { - type = types.str; - default = "!"; - example = ""; - visible = false; - }; - }; @@ -499,7 +507,6 @@ in { home = "/root"; shell = mkDefault cfg.defaultUserShell; group = "root"; - initialHashedPassword = mkDefault config.security.initialRootPassword; }; nobody = { uid = ids.uids.nobody; @@ -549,16 +556,7 @@ in { # Install all the user shells environment.systemPackages = systemShells; - environment.etc = { - subuid = { - text = subuidFile; - mode = "0644"; - }; - subgid = { - text = subgidFile; - mode = "0644"; - }; - } // (mapAttrs' (name: { packages, ... }: { + environment.etc = (mapAttrs' (name: { packages, ... }: { name = "profiles/per-user/${name}"; value.source = pkgs.buildEnv { name = "user-environment"; @@ -583,22 +581,65 @@ in { # password or an SSH authorized key. Privileged accounts are # root and users in the wheel group. assertion = !cfg.mutableUsers -> - any id (mapAttrsToList (name: cfg: + any id ((mapAttrsToList (name: cfg: (name == "root" || 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 != [] || cfg.openssh.authorizedKeys.keyFiles != []) - ) cfg.users); + ) cfg.users) ++ [ + config.security.googleOsLogin.enable + ]); message = '' 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) ( + flip mapAttrsToList cfg.users (name: user: + # This regex matches a subset of the Modular Crypto Format (MCF)[1] + # informal standard. Since this depends largely on the OS or the + # specific implementation of crypt(3) we only support the (sane) + # schemes implemented by glibc and BSDs. In particular the original + # DES hash is excluded since, having no structure, it would validate + # common mistakes like typing the plaintext password. + # + # [1]: https://en.wikipedia.org/wiki/Crypt_(C) + let + sep = "\\$"; + base64 = "[a-zA-Z0-9./]+"; + id = "[a-z0-9-]+"; + value = "[a-zA-Z0-9/+.-]+"; + options = "${id}(=${value})?(,${id}=${value})*"; + scheme = "${id}(${sep}${options})?"; + content = "${base64}${sep}${base64}"; + mcf = "^${sep}${scheme}${sep}${content}$"; + in + if (allowsLogin user.hashedPassword + && user.hashedPassword != "" # login without password + && builtins.match mcf user.hashedPassword == null) + 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`.'' + else null + )); }; |