aboutsummaryrefslogtreecommitdiff
path: root/modules/programs/ssh.nix
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 /modules/programs/ssh.nix
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.
Diffstat (limited to '')
-rw-r--r--modules/programs/ssh.nix115
1 files changed, 89 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)}