From 1bdbebc3f83a7b6a69f84797d5cda9ece8ca3c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Fi=C5=A1er?= <10010448+jficz@users.noreply.github.com> Date: Sun, 27 Nov 2022 16:15:32 +0100 Subject: [PATCH] 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 --- modules/misc/news.nix | 9 ++++ modules/programs/ssh.nix | 48 ++++++++++++++++--- tests/modules/programs/ssh/default.nix | 1 + ...match-blocks-match-and-hosts-expected.conf | 19 ++++++++ .../ssh/match-blocks-match-and-hosts.nix | 32 +++++++++++++ 5 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 tests/modules/programs/ssh/match-blocks-match-and-hosts-expected.conf create mode 100644 tests/modules/programs/ssh/match-blocks-match-and-hosts.nix diff --git a/modules/misc/news.nix b/modules/misc/news.nix index a2f08db2c..cc82598fa 100644 --- a/modules/misc/news.nix +++ b/modules/misc/news.nix @@ -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 + ''; + } ]; }; } diff --git a/modules/programs/ssh.nix b/modules/programs/ssh.nix index 05d16a2c4..f906d1bc7 100644 --- a/modules/programs/ssh.nix +++ b/modules/programs/ssh.nix @@ -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. + Host pattern used by this conditional block. + See + + ssh_config + 5 + + for Host block details. + This option is ignored if + + if defined. + ''; + }; + + match = mkOption { + type = types.nullOr types.str; + default = null; + example = "host canonical\nhost exec \"ping -c1 -q 192.168.17.1\""; + description = '' + Match block conditions used by this block. See + + ssh_config + 5 + + for Match block details. + This option takes precedence over + + 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); }; } diff --git a/tests/modules/programs/ssh/default.nix b/tests/modules/programs/ssh/default.nix index b2f832c91..c5e175995 100644 --- a/tests/modules/programs/ssh/default.nix +++ b/tests/modules/programs/ssh/default.nix @@ -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; diff --git a/tests/modules/programs/ssh/match-blocks-match-and-hosts-expected.conf b/tests/modules/programs/ssh/match-blocks-match-and-hosts-expected.conf new file mode 100644 index 000000000..d50343b99 --- /dev/null +++ b/tests/modules/programs/ssh/match-blocks-match-and-hosts-expected.conf @@ -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 + + diff --git a/tests/modules/programs/ssh/match-blocks-match-and-hosts.nix b/tests/modules/programs/ssh/match-blocks-match-and-hosts.nix new file mode 100644 index 000000000..aa1e40d05 --- /dev/null +++ b/tests/modules/programs/ssh/match-blocks-match-and-hosts.nix @@ -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} + ''; + }; +}