Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: mutable user eval test #31034

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
75 changes: 75 additions & 0 deletions nixos/module-tests/mutable-users.nix
@@ -0,0 +1,75 @@
let
ensurePass = name: config:
builtins.seq
(builtins.unsafeDiscardOutputDependency
(import ../lib/eval-config.nix {
modules = [
config
({
fileSystems."/".device = "/dev/bogus";
boot.loader.grub.device = "/dev/bogus";
})
];
}).config.system.build.toplevel.drvPath
) "ok" ;


ensureFail = name: config:
if (builtins.tryEval (ensurePass name config)).success == false
then "ok"
else throw "unexpected success in ${name}";
in {
user-with-password = ensurePass "user-with-password" {
# services.openssh.enable = true;
users.mutableUsers = false;
users.users.foo = {
password = "foob";
extraGroups = [ "wheel" ];
};
users.users.root.hashedPassword = null;
};

root-only-no-password = ensureFail "root-only-no-password" {
users.mutableUsers = false;
users.users.root.hashedPassword = null;
};

root-only-with-bang-password = ensureFail "root-only-with-bang-password" {
users.mutableUsers = false;
users.users.root.hashedPassword = "!";
};

root-only-with-real-password = ensurePass "root-only-with-real-password" {
users.mutableUsers = false;
users.users.root.hashedPassword = "w00t";
};

root-only-with-real-password-ssh-no-login = ensureFail "root-only-with-real-password-ssh-no-login" {
users.mutableUsers = false;
users.users.root.hashedPassword = "w00t";
services.openssh.enable = true;
services.openssh.permitRootLogin = "no";
};

root-only-with-real-password-ssh-without-pass-login = ensureFail "root-only-with-real-password-ssh-without-pass-login" {
users.mutableUsers = false;
users.users.root.hashedPassword = "w00t";
services.openssh.enable = true;
services.openssh.permitRootLogin = "without-password";
};

root-only-with-real-password-ssh-prohibit-password-login = ensureFail "root-only-with-real-password-ssh-prohibit-password-login" {
users.mutableUsers = false;
users.users.root.hashedPassword = "w00t";
services.openssh.enable = true;
services.openssh.permitRootLogin = "prohibit-password";
};

root-only-with-real-password-ssh-no-login-ssh-disabled = ensurePass "root-only-with-real-password-ssh-no-login-ssh-disabled" {
users.mutableUsers = false;
users.users.root.hashedPassword = "w00t";
services.openssh.enable = false;
services.openssh.permitRootLogin = "no";
};

}
81 changes: 70 additions & 11 deletions nixos/modules/config/users-groups.nix
Expand Up @@ -557,18 +557,77 @@ 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:
(name == "root"
|| cfg.group == "wheel"
|| elem "wheel" cfg.extraGroups)
&&
((cfg.hashedPassword != null && cfg.hashedPassword != "!")
|| cfg.password != null
|| cfg.passwordFile != null
|| cfg.openssh.authorizedKeys.keys != []
|| cfg.openssh.authorizedKeys.keyFiles != [])
) cfg.users);
(let
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, this assertion can not be accurate if security.sudo.configFile is overwritten or security.sudo.extraConfig is set. I see two options:

  • print a warning if that's the case and do not check anything;
  • still check things, but add an explicit "breakglass" option to disable this assertion.

userHasKeys = cfg:
(
cfg.openssh.authorizedKeys.keys != []
|| cfg.openssh.authorizedKeys.keyFiles != []
);

userHasPassword = cfg:
(
(cfg.hashedPassword != null && cfg.hashedPassword != "!")
|| cfg.password != null
|| cfg.passwordFile != null
);

userHasSSHCreds = name: cfg:
let
ssh = config.services.openssh;
passwordWorks = if ssh.passwordAuthentication
then userHasPassword cfg
else false;

keyWorks = userHasKeys cfg;
in if name == "root" then
(
# Is root allowed explicitly to use a password?
if ssh.permitRootLogin == "yes"
then passwordWorks || keyWorks
else
# Is root allowed explicitly denied from using a password?
if (ssh.permitRootLogin == "without-password"
|| ssh.permitRootLogin == "prohibit-password")
then keyWorks
else
# Forced commands don't count as being allowed in
# and no means no
if (ssh.permitRootLogin == "forced-command"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be "forced-commands-only"

|| ssh.permitRootLogin == "no")
then false
else
builtins.trace ("Cannot handle openssh.permitRootLogin"
+ " = ${ssh.permitRootLogin} in determining if it"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add quotes around "${ssh.permitRootLogin}"

+ " can be used for SSH login. Assuming no, for"
+ " safety.") false
)
else passwordWorks || keyWorks;

userIsWheel = cfg:
(
cfg.group == "wheel"
|| elem "wheel" cfg.extraGroups
);

userCanBecomeRoot = name: cfg:
if name == "root"
then true
else if config.security.sudo.enable == false
then false
else if config.security.sudo.wheelNeedsPassword
then (userIsWheel cfg && userHasPassword cfg)
else userIsWheel cfg;

in any id (mapAttrsToList (name: cfg:
userCanBecomeRoot name cfg
&& (
if config.services.openssh.enable
then userHasSSHCreds name cfg
else userHasPassword cfg
)
) cfg.users));
message = ''
No account can log in at the console or over SSH. Please set
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line seem to be unfinished

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.'';
}
Expand Down
4 changes: 3 additions & 1 deletion nixos/modules/services/networking/ssh/sshd.nix
Expand Up @@ -21,7 +21,7 @@ let
daemon reads in addition to the the user's authorized_keys file.
You can combine the <literal>keys</literal> and
<literal>keyFiles</literal> options.
Warning: If you are using <literal>NixOps</literal> then don't use this
Warning: If you are using <literal>NixOps</literal> then don't use this
option since it will replace the key required for deployment via ssh.
'';
};
Expand Down Expand Up @@ -114,6 +114,8 @@ in

permitRootLogin = mkOption {
default = "prohibit-password";
# When changing the allowed types, you must also update
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/the allowed types/the allowed values/

# userHasSSHCreds in nixos/modules/config/users-groups.nix!
type = types.enum ["yes" "without-password" "prohibit-password" "forced-commands-only" "no"];
description = ''
Whether the root user can login using ssh.
Expand Down