aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Wood <david@davidtw.co>2019-08-20 12:20:39 +0100
committerRobert Helgesson <robert@rycee.net>2019-10-02 20:42:29 +0200
commite8dbc3561373b68d12decb3c0d7c1ba245f138f7 (patch)
tree7e8046111a3fc1126770f01c21275ff4f5ae9f42
parent3d546e0d01996268e717b13e727bd53f6b14fb1a (diff)
ssh: sockets forwards; remote and dynamic forwards
This commit adds support for forwarding paths rather than just addresses/ports. It also adds options for specifying remote and dynamic forwards.
-rw-r--r--modules/programs/ssh.nix115
-rw-r--r--tests/modules/programs/ssh/default-config.nix7
-rw-r--r--tests/modules/programs/ssh/default.nix13
-rw-r--r--tests/modules/programs/ssh/forwards-dynamic-bind-path-with-port-asserts.nix32
-rw-r--r--tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf19
-rw-r--r--tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts.nix45
-rw-r--r--tests/modules/programs/ssh/forwards-local-bind-path-with-port-asserts.nix36
-rw-r--r--tests/modules/programs/ssh/forwards-local-host-path-with-port-asserts.nix36
-rw-r--r--tests/modules/programs/ssh/forwards-paths-with-ports-error.json1
-rw-r--r--tests/modules/programs/ssh/forwards-remote-bind-path-with-port-asserts.nix36
-rw-r--r--tests/modules/programs/ssh/forwards-remote-host-path-with-port-asserts.nix36
-rw-r--r--tests/modules/programs/ssh/match-blocks-attrs-expected.conf3
-rw-r--r--tests/modules/programs/ssh/match-blocks-attrs.nix23
-rw-r--r--tests/modules/programs/ssh/no-assertions.json1
14 files changed, 377 insertions, 26 deletions
diff --git a/modules/programs/ssh.nix b/modules/programs/ssh.nix
index fdf02a2c14e..ab61c0dcbc4 100644
--- a/modules/programs/ssh.nix
+++ b/modules/programs/ssh.nix
@@ -6,26 +6,39 @@ let
cfg = config.programs.ssh;
+ isPath = x: builtins.substring 0 1 (toString x) == "/";
+
+ addressPort = entry:
+ if isPath entry.address
+ then " ${entry.address}"
+ else " [${entry.address}]:${toString entry.port}";
+
yn = flag: if flag then "yes" else "no";
unwords = builtins.concatStringsSep " ";
- localForwardModule = types.submodule ({ ... }: {
- options = {
- bind = {
- address = mkOption {
- type = types.str;
- default = "localhost";
- example = "example.org";
- description = "The address where to bind the port.";
- };
+ bindOptions = {
+ address = mkOption {
+ type = types.str;
+ default = "localhost";
+ example = "example.org";
+ description = "The address where to bind the port.";
+ };
- port = mkOption {
- type = types.port;
- example = 8080;
- description = "Specifies port number to bind on bind address.";
- };
- };
+ port = mkOption {
+ type = types.port;
+ example = 8080;
+ description = "Specifies port number to bind on bind address.";
+ };
+ };
+
+ dynamicForwardModule = types.submodule {
+ options = bindOptions;
+ };
+
+ forwardModule = types.submodule {
+ options = {
+ bind = bindOptions;
host = {
address = mkOption {
@@ -41,7 +54,7 @@ let
};
};
};
- });
+ };
matchBlockModule = types.submodule ({ name, ... }: {
options = {
@@ -186,7 +199,7 @@ let
};
localForwards = mkOption {
- type = types.listOf localForwardModule;
+ type = types.listOf forwardModule;
default = [];
example = literalExample ''
[
@@ -202,7 +215,43 @@ let
<citerefentry>
<refentrytitle>ssh_config</refentrytitle>
<manvolnum>5</manvolnum>
- </citerefentry> for LocalForward.
+ </citerefentry> for <literal>LocalForward</literal>.
+ '';
+ };
+
+ remoteForwards = mkOption {
+ type = types.listOf forwardModule;
+ default = [];
+ example = literalExample ''
+ [
+ {
+ bind.port = 8080;
+ host.address = "10.0.0.13";
+ host.port = 80;
+ }
+ ];
+ '';
+ description = ''
+ Specify remote port forwardings. See
+ <citerefentry>
+ <refentrytitle>ssh_config</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> for <literal>RemoteForward</literal>.
+ '';
+ };
+
+ dynamicForwards = mkOption {
+ type = types.listOf dynamicForwardModule;
+ default = [];
+ example = literalExample ''
+ [ { port = 8080; } ];
+ '';
+ description = ''
+ Specify dynamic port forwardings. See
+ <citerefentry>
+ <refentrytitle>ssh_config</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> for <literal>DynamicForward</literal>.
'';
};
@@ -235,14 +284,9 @@ let
++ optional (cf.proxyCommand != null) " ProxyCommand ${cf.proxyCommand}"
++ optional (cf.proxyJump != null) " ProxyJump ${cf.proxyJump}"
++ map (file: " IdentityFile ${file}") cf.identityFile
- ++ map (f:
- let
- addressPort = entry: " [${entry.address}]:${toString entry.port}";
- in
- " LocalForward"
- + addressPort f.bind
- + addressPort f.host
- ) cf.localForwards
+ ++ map (f: " LocalForward" + addressPort f.bind + addressPort f.host) cf.localForwards
+ ++ map (f: " RemoteForward" + addressPort f.bind + addressPort f.host) cf.remoteForwards
+ ++ map (f: " DynamicForward" + addressPort f) cf.dynamicForwards
++ mapAttrsToList (n: v: " ${n} ${v}") cf.extraOptions
);
@@ -370,6 +414,25 @@ in
};
config = mkIf cfg.enable {
+ assertions = [
+ {
+ assertion =
+ let
+ # `builtins.any`/`lib.lists.any` does not return `true` if there are no elements.
+ any' = pred: items: if items == [] then true else any pred items;
+ # Check that if `entry.address` is defined, and is a path, that `entry.port` has not
+ # been defined.
+ noPathWithPort = entry: entry ? address && isPath entry.address -> !(entry ? port);
+ checkDynamic = block: any' noPathWithPort block.dynamicForwards;
+ checkBindAndHost = fwd: noPathWithPort fwd.bind && noPathWithPort fwd.host;
+ checkLocal = block: any' checkBindAndHost block.localForwards;
+ checkRemote = block: any' checkBindAndHost block.remoteForwards;
+ checkMatchBlock = block: all (fn: fn block) [ checkLocal checkRemote checkDynamic ];
+ in any' checkMatchBlock (builtins.attrValues cfg.matchBlocks);
+ message = "Forwarded paths cannot have ports.";
+ }
+ ];
+
home.file.".ssh/config".text = ''
${concatStringsSep "\n" (
mapAttrsToList (n: v: "${n} ${v}") cfg.extraOptionOverrides)}
diff --git a/tests/modules/programs/ssh/default-config.nix b/tests/modules/programs/ssh/default-config.nix
index e43ee3dc769..266bc9d1f5d 100644
--- a/tests/modules/programs/ssh/default-config.nix
+++ b/tests/modules/programs/ssh/default-config.nix
@@ -8,9 +8,16 @@ with lib;
enable = true;
};
+ 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 ${./default-config-expected.conf}
+ assertFileContent home-files/assertions ${./no-assertions.json}
'';
};
}
diff --git a/tests/modules/programs/ssh/default.nix b/tests/modules/programs/ssh/default.nix
index d385e4ee921..507eef0bdb8 100644
--- a/tests/modules/programs/ssh/default.nix
+++ b/tests/modules/programs/ssh/default.nix
@@ -1,4 +1,17 @@
{
ssh-defaults = ./default-config.nix;
ssh-match-blocks = ./match-blocks-attrs.nix;
+
+ ssh-forwards-dynamic-valid-bind-no-asserts =
+ ./forwards-dynamic-valid-bind-no-asserts.nix;
+ ssh-forwards-dynamic-bind-path-with-port-asserts =
+ ./forwards-dynamic-bind-path-with-port-asserts.nix;
+ ssh-forwards-local-bind-path-with-port-asserts =
+ ./forwards-local-bind-path-with-port-asserts.nix;
+ ssh-forwards-local-host-path-with-port-asserts =
+ ./forwards-local-host-path-with-port-asserts.nix;
+ ssh-forwards-remote-bind-path-with-port-asserts =
+ ./forwards-remote-bind-path-with-port-asserts.nix;
+ ssh-forwards-remote-host-path-with-port-asserts =
+ ./forwards-remote-host-path-with-port-asserts.nix;
}
diff --git a/tests/modules/programs/ssh/forwards-dynamic-bind-path-with-port-asserts.nix b/tests/modules/programs/ssh/forwards-dynamic-bind-path-with-port-asserts.nix
new file mode 100644
index 00000000000..2e9082de378
--- /dev/null
+++ b/tests/modules/programs/ssh/forwards-dynamic-bind-path-with-port-asserts.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.ssh = {
+ enable = true;
+ matchBlocks = {
+ dynamicBindPathWithPort = {
+ dynamicForwards = [
+ {
+ # Error:
+ address = "/run/user/1000/gnupg/S.gpg-agent.extra";
+ port = 3000;
+ }
+ ];
+ };
+ };
+ };
+
+ home.file.result.text =
+ builtins.toJSON
+ (map (a: a.message)
+ (filter (a: !a.assertion)
+ config.assertions));
+
+ nmt.script = ''
+ assertFileContent home-files/result ${./forwards-paths-with-ports-error.json}
+ '';
+ };
+}
diff --git a/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf b/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf
new file mode 100644
index 00000000000..5213d282c28
--- /dev/null
+++ b/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf
@@ -0,0 +1,19 @@
+
+
+Host dynamicBindAddressWithPort
+ DynamicForward [127.0.0.1]:3000
+
+Host dynamicBindPathNoPort
+ DynamicForward /run/user/1000/gnupg/S.gpg-agent.extra
+
+Host *
+ ForwardAgent no
+ Compression no
+ ServerAliveInterval 0
+ HashKnownHosts no
+ UserKnownHostsFile ~/.ssh/known_hosts
+ ControlMaster no
+ ControlPath ~/.ssh/master-%r@%n:%p
+ ControlPersist no
+
+
diff --git a/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts.nix b/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts.nix
new file mode 100644
index 00000000000..15ab59e82ca
--- /dev/null
+++ b/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.ssh = {
+ enable = true;
+ matchBlocks = {
+ dynamicBindPathNoPort = {
+ dynamicForwards = [
+ {
+ # OK:
+ address = "/run/user/1000/gnupg/S.gpg-agent.extra";
+ }
+ ];
+ };
+
+ dynamicBindAddressWithPort = {
+ dynamicForwards = [
+ {
+ # OK:
+ address = "127.0.0.1";
+ port = 3000;
+ }
+ ];
+ };
+ };
+ };
+
+ home.file.result.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 \
+ ${./forwards-dynamic-valid-bind-no-asserts-expected.conf}
+ assertFileContent home-files/result ${./no-assertions.json}
+ '';
+ };
+}
diff --git a/tests/modules/programs/ssh/forwards-local-bind-path-with-port-asserts.nix b/tests/modules/programs/ssh/forwards-local-bind-path-with-port-asserts.nix
new file mode 100644
index 00000000000..c05cba82791
--- /dev/null
+++ b/tests/modules/programs/ssh/forwards-local-bind-path-with-port-asserts.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.ssh = {
+ enable = true;
+ matchBlocks = {
+ localBindPathWithPort = {
+ localForwards = [
+ {
+ # OK:
+ host.address = "127.0.0.1";
+ host.port = 3000;
+
+ # Error:
+ bind.address = "/run/user/1000/gnupg/S.gpg-agent.extra";
+ bind.port = 3000;
+ }
+ ];
+ };
+ };
+ };
+
+ home.file.result.text =
+ builtins.toJSON
+ (map (a: a.message)
+ (filter (a: !a.assertion)
+ config.assertions));
+
+ nmt.script = ''
+ assertFileContent home-files/result ${./forwards-paths-with-ports-error.json}
+ '';
+ };
+}
diff --git a/tests/modules/programs/ssh/forwards-local-host-path-with-port-asserts.nix b/tests/modules/programs/ssh/forwards-local-host-path-with-port-asserts.nix
new file mode 100644
index 00000000000..8cecc5e5121
--- /dev/null
+++ b/tests/modules/programs/ssh/forwards-local-host-path-with-port-asserts.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.ssh = {
+ enable = true;
+ matchBlocks = {
+ localHostPathWithPort = {
+ localForwards = [
+ {
+ # OK:
+ bind.address = "127.0.0.1";
+ bind.port = 3000;
+
+ # Error:
+ host.address = "/run/user/1000/gnupg/S.gpg-agent.extra";
+ host.port = 3000;
+ }
+ ];
+ };
+ };
+ };
+
+ home.file.result.text =
+ builtins.toJSON
+ (map (a: a.message)
+ (filter (a: !a.assertion)
+ config.assertions));
+
+ nmt.script = ''
+ assertFileContent home-files/result ${./forwards-paths-with-ports-error.json}
+ '';
+ };
+}
diff --git a/tests/modules/programs/ssh/forwards-paths-with-ports-error.json b/tests/modules/programs/ssh/forwards-paths-with-ports-error.json
new file mode 100644
index 00000000000..e7e3a374ecc
--- /dev/null
+++ b/tests/modules/programs/ssh/forwards-paths-with-ports-error.json
@@ -0,0 +1 @@
+["Forwarded paths cannot have ports."] \ No newline at end of file
diff --git a/tests/modules/programs/ssh/forwards-remote-bind-path-with-port-asserts.nix b/tests/modules/programs/ssh/forwards-remote-bind-path-with-port-asserts.nix
new file mode 100644
index 00000000000..a0473147bd3
--- /dev/null
+++ b/tests/modules/programs/ssh/forwards-remote-bind-path-with-port-asserts.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.ssh = {
+ enable = true;
+ matchBlocks = {
+ remoteBindPathWithPort = {
+ remoteForwards = [
+ {
+ # OK:
+ host.address = "127.0.0.1";
+ host.port = 3000;
+
+ # Error:
+ bind.address = "/run/user/1000/gnupg/S.gpg-agent.extra";
+ bind.port = 3000;
+ }
+ ];
+ };
+ };
+ };
+
+ home.file.result.text =
+ builtins.toJSON
+ (map (a: a.message)
+ (filter (a: !a.assertion)
+ config.assertions));
+
+ nmt.script = ''
+ assertFileContent home-files/result ${./forwards-paths-with-ports-error.json}
+ '';
+ };
+}
diff --git a/tests/modules/programs/ssh/forwards-remote-host-path-with-port-asserts.nix b/tests/modules/programs/ssh/forwards-remote-host-path-with-port-asserts.nix
new file mode 100644
index 00000000000..770b8ab2870
--- /dev/null
+++ b/tests/modules/programs/ssh/forwards-remote-host-path-with-port-asserts.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.ssh = {
+ enable = true;
+ matchBlocks = {
+ remoteHostPathWithPort = {
+ remoteForwards = [
+ {
+ # OK:
+ bind.address = "127.0.0.1";
+ bind.port = 3000;
+
+ # Error:
+ host.address = "/run/user/1000/gnupg/S.gpg-agent.extra";
+ host.port = 3000;
+ }
+ ];
+ };
+ };
+ };
+
+ home.file.result.text =
+ builtins.toJSON
+ (map (a: a.message)
+ (filter (a: !a.assertion)
+ config.assertions));
+
+ nmt.script = ''
+ assertFileContent home-files/result ${./forwards-paths-with-ports-error.json}
+ '';
+ };
+}
diff --git a/tests/modules/programs/ssh/match-blocks-attrs-expected.conf b/tests/modules/programs/ssh/match-blocks-attrs-expected.conf
index 1bff480fdce..f0d768375f0 100644
--- a/tests/modules/programs/ssh/match-blocks-attrs-expected.conf
+++ b/tests/modules/programs/ssh/match-blocks-attrs-expected.conf
@@ -12,6 +12,9 @@ Host xyz
ServerAliveInterval 60
IdentityFile file
LocalForward [localhost]:8080 [10.0.0.1]:80
+ RemoteForward [localhost]:8081 [10.0.0.2]:80
+ RemoteForward /run/user/1000/gnupg/S.gpg-agent.extra /run/user/1000/gnupg/S.gpg-agent
+ DynamicForward [localhost]:2839
Host *
ForwardAgent no
diff --git a/tests/modules/programs/ssh/match-blocks-attrs.nix b/tests/modules/programs/ssh/match-blocks-attrs.nix
index 3e09cd2d5f2..94263ef9d27 100644
--- a/tests/modules/programs/ssh/match-blocks-attrs.nix
+++ b/tests/modules/programs/ssh/match-blocks-attrs.nix
@@ -22,6 +22,22 @@ with lib;
host.port = 80;
}
];
+ remoteForwards = [
+ {
+ bind.port = 8081;
+ host.address = "10.0.0.2";
+ host.port = 80;
+ }
+ {
+ bind.address = "/run/user/1000/gnupg/S.gpg-agent.extra";
+ host.address = "/run/user/1000/gnupg/S.gpg-agent";
+ }
+ ];
+ dynamicForwards = [
+ {
+ port = 2839;
+ }
+ ];
};
"* !github.com" = {
@@ -31,11 +47,18 @@ with lib;
};
};
+ 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-attrs-expected.conf}
+ assertFileContent home-files/assertions ${./no-assertions.json}
'';
};
}
diff --git a/tests/modules/programs/ssh/no-assertions.json b/tests/modules/programs/ssh/no-assertions.json
new file mode 100644
index 00000000000..0637a088a01
--- /dev/null
+++ b/tests/modules/programs/ssh/no-assertions.json
@@ -0,0 +1 @@
+[] \ No newline at end of file