Merge pull request #227442 from christoph-heiss/openssh/allowusers
openssh: add {Allow,Deny}{Users,Groups} settings
This commit is contained in:
commit
3b848391b6
2 changed files with 103 additions and 14 deletions
|
@ -12,22 +12,44 @@ let
|
|||
then cfgc.package
|
||||
else pkgs.buildPackages.openssh;
|
||||
|
||||
# reports boolean as yes / no
|
||||
mkValueStringSshd = with lib; v:
|
||||
if isInt v then toString v
|
||||
else if isString v then v
|
||||
else if true == v then "yes"
|
||||
else if false == v then "no"
|
||||
else if isList v then concatStringsSep "," v
|
||||
else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
|
||||
|
||||
# dont use the "=" operator
|
||||
settingsFormat = (pkgs.formats.keyValue {
|
||||
mkKeyValue = lib.generators.mkKeyValueDefault {
|
||||
mkValueString = mkValueStringSshd;
|
||||
} " ";});
|
||||
settingsFormat =
|
||||
let
|
||||
# reports boolean as yes / no
|
||||
mkValueString = with lib; v:
|
||||
if isInt v then toString v
|
||||
else if isString v then v
|
||||
else if true == v then "yes"
|
||||
else if false == v then "no"
|
||||
else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
|
||||
|
||||
configFile = settingsFormat.generate "sshd.conf-settings" cfg.settings;
|
||||
base = pkgs.formats.keyValue {
|
||||
mkKeyValue = lib.generators.mkKeyValueDefault { inherit mkValueString; } " ";
|
||||
};
|
||||
# OpenSSH is very inconsistent with options that can take multiple values.
|
||||
# For some of them, they can simply appear multiple times and are appended, for others the
|
||||
# values must be separated by whitespace or even commas.
|
||||
# Consult either sshd_config(5) or, as last resort, the OpehSSH source for parsing
|
||||
# the options at servconf.c:process_server_config_line_depth() to determine the right "mode"
|
||||
# for each. But fortunaly this fact is documented for most of them in the manpage.
|
||||
commaSeparated = [ "Ciphers" "KexAlgorithms" "Macs" ];
|
||||
spaceSeparated = [ "AuthorizedKeysFile" "AllowGroups" "AllowUsers" "DenyGroups" "DenyUsers" ];
|
||||
in {
|
||||
inherit (base) type;
|
||||
generate = name: value:
|
||||
let transformedValue = mapAttrs (key: val:
|
||||
if isList val then
|
||||
if elem key commaSeparated then concatStringsSep "," val
|
||||
else if elem key spaceSeparated then concatStringsSep " " val
|
||||
else throw "list value for unknown key ${key}: ${(lib.generators.toPretty {}) val}"
|
||||
else
|
||||
val
|
||||
) value;
|
||||
in
|
||||
base.generate name transformedValue;
|
||||
};
|
||||
|
||||
configFile = settingsFormat.generate "sshd.conf-settings" (filterAttrs (n: v: v != null) cfg.settings);
|
||||
sshconf = pkgs.runCommand "sshd.conf-final" { } ''
|
||||
cat ${configFile} - >$out <<EOL
|
||||
${cfg.extraConfig}
|
||||
|
@ -431,6 +453,42 @@ in
|
|||
<https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
|
||||
'';
|
||||
};
|
||||
AllowUsers = mkOption {
|
||||
type = with types; nullOr (listOf str);
|
||||
default = null;
|
||||
description = lib.mdDoc ''
|
||||
If specified, login is allowed only for the listed users.
|
||||
See {manpage}`sshd_config(5)` for details.
|
||||
'';
|
||||
};
|
||||
DenyUsers = mkOption {
|
||||
type = with types; nullOr (listOf str);
|
||||
default = null;
|
||||
description = lib.mdDoc ''
|
||||
If specified, login is denied for all listed users. Takes
|
||||
precedence over [](#opt-services.openssh.settings.AllowUsers).
|
||||
See {manpage}`sshd_config(5)` for details.
|
||||
'';
|
||||
};
|
||||
AllowGroups = mkOption {
|
||||
type = with types; nullOr (listOf str);
|
||||
default = null;
|
||||
description = lib.mdDoc ''
|
||||
If specified, login is allowed only for users part of the
|
||||
listed groups.
|
||||
See {manpage}`sshd_config(5)` for details.
|
||||
'';
|
||||
};
|
||||
DenyGroups = mkOption {
|
||||
type = with types; nullOr (listOf str);
|
||||
default = null;
|
||||
description = lib.mdDoc ''
|
||||
If specified, login is denied for all users part of the listed
|
||||
groups. Takes precedence over
|
||||
[](#opt-services.openssh.settings.AllowGroups). See
|
||||
{manpage}`sshd_config(5)` for details.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
|
@ -82,6 +82,19 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
server_allowedusers =
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
services.openssh = { enable = true; settings.AllowUsers = [ "alice" "bob" ]; };
|
||||
users.groups = { alice = { }; bob = { }; carol = { }; };
|
||||
users.users = {
|
||||
alice = { isNormalUser = true; group = "alice"; openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; };
|
||||
bob = { isNormalUser = true; group = "bob"; openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; };
|
||||
carol = { isNormalUser = true; group = "carol"; openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; };
|
||||
};
|
||||
};
|
||||
|
||||
client =
|
||||
{ ... }: { };
|
||||
|
||||
|
@ -147,5 +160,23 @@ in {
|
|||
|
||||
with subtest("match-rules"):
|
||||
server_match_rule.succeed("ss -nlt | grep '127.0.0.1:22'")
|
||||
|
||||
with subtest("allowed-users"):
|
||||
client.succeed(
|
||||
"cat ${snakeOilPrivateKey} > privkey.snakeoil"
|
||||
)
|
||||
client.succeed("chmod 600 privkey.snakeoil")
|
||||
client.succeed(
|
||||
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil alice@server_allowedusers true",
|
||||
timeout=30
|
||||
)
|
||||
client.succeed(
|
||||
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil bob@server_allowedusers true",
|
||||
timeout=30
|
||||
)
|
||||
client.fail(
|
||||
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil carol@server_allowedusers true",
|
||||
timeout=30
|
||||
)
|
||||
'';
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue