ssh: add generic Match support for matchBlocks (#2992)

* ssh: add generic Match support for matchBlocks

Introduce conservative support for actual `Match`
blocks in ssh config.

"Conservative" means this PR doesn'tt try to process
the `match` expression and simply uses it as a string
provided by the user.

If set, `match` has precedence over `host` meaning
if both are set, `match` is used and `host` is ignored.

* Add news entry
This commit is contained in:
Jakub Fišer 2022-11-27 16:15:32 +01:00 committed by GitHub
parent 7ae7250df8
commit 1bdbebc3f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 103 additions and 6 deletions

View file

@ -843,6 +843,15 @@ in
export MOZ_ALLOW_DOWNGRADE=1
'';
}
{
time = "2022-11-27T13:14:01+00:00";
message = ''
'programs.ssh.matchBlocks.*' now supports literal 'Match' blocks via
'programs.ssh.matchBlocks.*.match' option as an alternative to plain
'Host' blocks
'';
}
];
};
}

View file

@ -60,10 +60,37 @@ let
matchBlockModule = types.submodule ({ dagName, ... }: {
options = {
host = mkOption {
type = types.str;
type = types.nullOr types.str;
default = null;
example = "*.example.org";
description = ''
The host pattern used by this conditional block.
<literal>Host</literal> pattern used by this conditional block.
See
<citerefentry>
<refentrytitle>ssh_config</refentrytitle>
<manvolnum>5</manvolnum>
</citerefentry>
for <literal>Host</literal> block details.
This option is ignored if
<option>ssh.matchBlocks.*.matcht</option>
if defined.
'';
};
match = mkOption {
type = types.nullOr types.str;
default = null;
example = "host <hostname> canonical\nhost <hostname> exec \"ping -c1 -q 192.168.17.1\"";
description = ''
<literal>Match</literal> block conditions used by this block. See
<citerefentry>
<refentrytitle>ssh_config</refentrytitle>
<manvolnum>5</manvolnum>
</citerefentry>
for <literal>Match</literal> block details.
This option takes precedence over
<option>ssh.matchBlocks.*.host</option>
if defined.
'';
};
@ -276,11 +303,16 @@ let
};
};
config.host = mkDefault dagName;
# config.host = mkDefault dagName;
});
matchBlockStr = cf: concatStringsSep "\n" (
["Host ${cf.host}"]
matchBlockStr = key: cf: concatStringsSep "\n" (
let
hostOrDagName = if cf.host != null then cf.host else key;
matchHead = if cf.match != null
then "Match ${cf.match}"
else "Host ${hostOrDagName}";
in [ "${matchHead}" ]
++ optional (cf.port != null) " Port ${toString cf.port}"
++ optional (cf.forwardAgent != null) " ForwardAgent ${lib.hm.booleans.yesNo cf.forwardAgent}"
++ optional cf.forwardX11 " ForwardX11 yes"
@ -492,7 +524,7 @@ in
++ (optional (cfg.includes != [ ]) ''
Include ${concatStringsSep " " cfg.includes}
'')
++ (map (block: matchBlockStr block.data) matchBlocks)
++ (map (block: matchBlockStr block.name block.data) matchBlocks)
)}
Host *
@ -508,5 +540,9 @@ in
${replaceStrings ["\n"] ["\n "] cfg.extraConfig}
'';
warnings = mapAttrsToList
(n: v: "The SSH config match block `programs.ssh.matchBlocks.${n}` sets both of the host and match options.\nThe match option takes precedence.")
(filterAttrs (n: v: v.data.host != null && v.data.match != null) cfg.matchBlocks);
};
}

View file

@ -2,6 +2,7 @@
ssh-defaults = ./default-config.nix;
ssh-includes = ./includes.nix;
ssh-match-blocks = ./match-blocks-attrs.nix;
ssh-match-blocks-match-and-hosts = ./match-blocks-match-and-hosts.nix;
ssh-forwards-dynamic-valid-bind-no-asserts =
./forwards-dynamic-valid-bind-no-asserts.nix;

View file

@ -0,0 +1,19 @@
Host * !github.com
Port 516
Host abc
Port 2222
Match host xyz canonical
Port 2223
Host *
ForwardAgent no
Compression no
ServerAliveInterval 0
ServerAliveCountMax 3
HashKnownHosts no
UserKnownHostsFile ~/.ssh/known_hosts
ControlMaster no
ControlPath ~/.ssh/master-%r@%n:%p
ControlPersist no

View file

@ -0,0 +1,32 @@
{ config, lib, pkgs, ... }:
with lib;
{
config = {
programs.ssh = {
enable = true;
matchBlocks = {
abc = { port = 2222; };
xyz = {
match = "host xyz canonical";
port = 2223;
};
"* !github.com" = { port = 516; };
};
};
home.file.assertions.text = builtins.toJSON
(map (a: a.message) (filter (a: !a.assertion) config.assertions));
nmt.script = ''
assertFileExists home-files/.ssh/config
assertFileContent \
home-files/.ssh/config \
${./match-blocks-match-and-hosts-expected.conf}
assertFileContent home-files/assertions ${./no-assertions.json}
'';
};
}