wstunnel: reformat with nixfmt

This commit is contained in:
r-vdp 2024-08-16 20:54:19 +02:00
parent 6cb5757dce
commit 6cac9e409c
No known key found for this signature in database
3 changed files with 298 additions and 285 deletions

View File

@ -1,7 +1,8 @@
{ config {
, lib config,
, pkgs lib,
, ... pkgs,
...
}: }:
let let
@ -29,10 +30,9 @@ let
package = lib.mkPackageOption pkgs "wstunnel" { }; package = lib.mkPackageOption pkgs "wstunnel" { };
autoStart = autoStart = lib.mkEnableOption "starting this wstunnel instance automatically" // {
lib.mkEnableOption "starting this wstunnel instance automatically" // { default = true;
default = true; };
};
extraArgs = lib.mkOption { extraArgs = lib.mkOption {
description = '' description = ''
@ -75,192 +75,198 @@ let
}; };
}; };
serverSubmodule = { config, ... }: { serverSubmodule =
options = commonOptions // { { config, ... }:
listen = lib.mkOption { {
description = '' options = commonOptions // {
Address and port to listen on. listen = lib.mkOption {
Setting the port to a value below 1024 will also give the process description = ''
the required `CAP_NET_BIND_SERVICE` capability. Address and port to listen on.
''; Setting the port to a value below 1024 will also give the process
type = lib.types.submodule hostPortSubmodule; the required `CAP_NET_BIND_SERVICE` capability.
default = { '';
host = "0.0.0.0"; type = lib.types.submodule hostPortSubmodule;
port = if config.enableHTTPS then 443 else 80; default = {
};
defaultText = lib.literalExpression ''
{
host = "0.0.0.0"; host = "0.0.0.0";
port = if enableHTTPS then 443 else 80; port = if config.enableHTTPS then 443 else 80;
} };
''; defaultText = lib.literalExpression ''
}; {
host = "0.0.0.0";
port = if enableHTTPS then 443 else 80;
}
'';
};
restrictTo = lib.mkOption { restrictTo = lib.mkOption {
description = '' description = ''
Accepted traffic will be forwarded only to this service. Accepted traffic will be forwarded only to this service.
''; '';
type = lib.types.listOf (lib.types.submodule hostPortSubmodule); type = lib.types.listOf (lib.types.submodule hostPortSubmodule);
default = [ ]; default = [ ];
example = [{ example = [
host = "127.0.0.1"; {
port = 51820; host = "127.0.0.1";
}]; port = 51820;
}; }
];
};
enableHTTPS = lib.mkOption { enableHTTPS = lib.mkOption {
description = "Use HTTPS for the tunnel server."; description = "Use HTTPS for the tunnel server.";
type = lib.types.bool; type = lib.types.bool;
default = true; default = true;
}; };
tlsCertificate = lib.mkOption { tlsCertificate = lib.mkOption {
description = '' description = ''
TLS certificate to use instead of the hardcoded one in case of HTTPS connections. TLS certificate to use instead of the hardcoded one in case of HTTPS connections.
Use together with `tlsKey`. Use together with `tlsKey`.
''; '';
type = lib.types.nullOr lib.types.path; type = lib.types.nullOr lib.types.path;
default = null; default = null;
example = "/var/lib/secrets/cert.pem"; example = "/var/lib/secrets/cert.pem";
}; };
tlsKey = lib.mkOption { tlsKey = lib.mkOption {
description = '' description = ''
TLS key to use instead of the hardcoded on in case of HTTPS connections. TLS key to use instead of the hardcoded on in case of HTTPS connections.
Use together with `tlsCertificate`. Use together with `tlsCertificate`.
''; '';
type = lib.types.nullOr lib.types.path; type = lib.types.nullOr lib.types.path;
default = null; default = null;
example = "/var/lib/secrets/key.pem"; example = "/var/lib/secrets/key.pem";
}; };
useACMEHost = lib.mkOption { useACMEHost = lib.mkOption {
description = '' description = ''
Use a certificate generated by the NixOS ACME module for the given host. Use a certificate generated by the NixOS ACME module for the given host.
Note that this will not generate a new certificate - you will need to do so with `security.acme.certs`. Note that this will not generate a new certificate - you will need to do so with `security.acme.certs`.
''; '';
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
default = null; default = null;
example = "example.com"; example = "example.com";
}; };
}; };
}; };
clientSubmodule = { config, ... }: { clientSubmodule =
options = commonOptions // { { config, ... }:
connectTo = lib.mkOption { {
description = "Server address and port to connect to."; options = commonOptions // {
type = lib.types.str; connectTo = lib.mkOption {
example = "https://wstunnel.server.com:8443"; description = "Server address and port to connect to.";
}; type = lib.types.str;
example = "https://wstunnel.server.com:8443";
localToRemote = lib.mkOption { };
description = ''Listen on local and forwards traffic from remote.'';
type = lib.types.listOf (lib.types.str); localToRemote = lib.mkOption {
default = [ ]; description = ''Listen on local and forwards traffic from remote.'';
example = [ type = lib.types.listOf (lib.types.str);
"tcp://1212:google.com:443" default = [ ];
"unix:///tmp/wstunnel.sock:g.com:443" example = [
]; "tcp://1212:google.com:443"
}; "unix:///tmp/wstunnel.sock:g.com:443"
];
remoteToLocal = lib.mkOption { };
description = "Listen on remote and forwards traffic from local. Only tcp is supported";
type = lib.types.listOf lib.types.str; remoteToLocal = lib.mkOption {
default = [ ]; description = "Listen on remote and forwards traffic from local. Only tcp is supported";
example = [ type = lib.types.listOf lib.types.str;
"tcp://1212:google.com:443" default = [ ];
"unix://wstunnel.sock:g.com:443" example = [
]; "tcp://1212:google.com:443"
}; "unix://wstunnel.sock:g.com:443"
];
addNetBind = lib.mkEnableOption "Whether add CAP_NET_BIND_SERVICE to the tunnel service, this should be enabled if you want to bind port < 1024"; };
httpProxy = lib.mkOption { addNetBind = lib.mkEnableOption "Whether add CAP_NET_BIND_SERVICE to the tunnel service, this should be enabled if you want to bind port < 1024";
description = ''
Proxy to use to connect to the wstunnel server (`USER:PASS@HOST:PORT`). httpProxy = lib.mkOption {
description = ''
::: {.warning} Proxy to use to connect to the wstunnel server (`USER:PASS@HOST:PORT`).
Passwords specified here will be world-readable in the Nix store!
To pass a password to the service, point the `environmentFile` option ::: {.warning}
to a file containing `PROXY_PASSWORD=<your-password-here>` and set Passwords specified here will be world-readable in the Nix store!
this option to `<user>:$PROXY_PASSWORD@<host>:<port>`. To pass a password to the service, point the `environmentFile` option
Note however that this will also locally leak the passwords at to a file containing `PROXY_PASSWORD=<your-password-here>` and set
runtime via e.g. /proc/<pid>/cmdline. this option to `<user>:$PROXY_PASSWORD@<host>:<port>`.
::: Note however that this will also locally leak the passwords at
''; runtime via e.g. /proc/<pid>/cmdline.
type = lib.types.nullOr lib.types.str; :::
default = null; '';
}; type = lib.types.nullOr lib.types.str;
default = null;
soMark = lib.mkOption { };
description = ''
Mark network packets with the SO_MARK sockoption with the specified value. soMark = lib.mkOption {
Setting this option will also enable the required `CAP_NET_ADMIN` capability description = ''
for the systemd service. Mark network packets with the SO_MARK sockoption with the specified value.
''; Setting this option will also enable the required `CAP_NET_ADMIN` capability
type = lib.types.nullOr lib.types.ints.unsigned; for the systemd service.
default = null; '';
}; type = lib.types.nullOr lib.types.ints.unsigned;
default = null;
upgradePathPrefix = lib.mkOption { };
description = ''
Use a specific HTTP path prefix that will show up in the upgrade upgradePathPrefix = lib.mkOption {
request to the `wstunnel` server. description = ''
Useful when running `wstunnel` behind a reverse proxy. Use a specific HTTP path prefix that will show up in the upgrade
''; request to the `wstunnel` server.
type = lib.types.nullOr lib.types.str; Useful when running `wstunnel` behind a reverse proxy.
default = null; '';
example = "wstunnel"; type = lib.types.nullOr lib.types.str;
}; default = null;
example = "wstunnel";
tlsSNI = lib.mkOption { };
description = "Use this as the SNI while connecting via TLS. Useful for circumventing hostname-based firewalls.";
type = lib.types.nullOr lib.types.str; tlsSNI = lib.mkOption {
default = null; description = "Use this as the SNI while connecting via TLS. Useful for circumventing hostname-based firewalls.";
}; type = lib.types.nullOr lib.types.str;
default = null;
tlsVerifyCertificate = lib.mkOption { };
description = "Whether to verify the TLS certificate of the server. It might be useful to set this to `false` when working with the `tlsSNI` option.";
type = lib.types.bool; tlsVerifyCertificate = lib.mkOption {
default = true; description = "Whether to verify the TLS certificate of the server. It might be useful to set this to `false` when working with the `tlsSNI` option.";
}; type = lib.types.bool;
default = true;
# The original argument name `websocketPingFrequency` is a misnomer, as the frequency is the inverse of the interval. };
websocketPingInterval = lib.mkOption {
description = "Frequency at which the client will send websocket ping to the server."; # The original argument name `websocketPingFrequency` is a misnomer, as the frequency is the inverse of the interval.
type = lib.types.nullOr lib.types.ints.unsigned; websocketPingInterval = lib.mkOption {
default = null; description = "Frequency at which the client will send websocket ping to the server.";
}; type = lib.types.nullOr lib.types.ints.unsigned;
default = null;
upgradeCredentials = lib.mkOption { };
description = ''
Use these credentials to authenticate during the HTTP upgrade request upgradeCredentials = lib.mkOption {
(Basic authorization type, `USER:[PASS]`). description = ''
Use these credentials to authenticate during the HTTP upgrade request
::: {.warning} (Basic authorization type, `USER:[PASS]`).
Passwords specified here will be world-readable in the Nix store!
To pass a password to the service, point the `environmentFile` option ::: {.warning}
to a file containing `HTTP_PASSWORD=<your-password-here>` and set this Passwords specified here will be world-readable in the Nix store!
option to `<user>:$HTTP_PASSWORD`. To pass a password to the service, point the `environmentFile` option
Note however that this will also locally leak the passwords at runtime to a file containing `HTTP_PASSWORD=<your-password-here>` and set this
via e.g. /proc/<pid>/cmdline. option to `<user>:$HTTP_PASSWORD`.
::: Note however that this will also locally leak the passwords at runtime
''; via e.g. /proc/<pid>/cmdline.
type = lib.types.nullOr lib.types.str; :::
default = null; '';
}; type = lib.types.nullOr lib.types.str;
default = null;
customHeaders = lib.mkOption { };
description = "Custom HTTP headers to send during the upgrade request.";
type = lib.types.attrsOf lib.types.str; customHeaders = lib.mkOption {
default = { }; description = "Custom HTTP headers to send during the upgrade request.";
example = { type = lib.types.attrsOf lib.types.str;
"X-Some-Header" = "some-value"; default = { };
example = {
"X-Some-Header" = "some-value";
};
}; };
}; };
}; };
};
generateServerUnit = name: serverCfg: { generateServerUnit = name: serverCfg: {
name = "wstunnel-server-${name}"; name = "wstunnel-server-${name}";
@ -270,22 +276,25 @@ let
in in
{ {
description = "wstunnel server - ${name}"; description = "wstunnel server - ${name}";
requires = [ "network.target" "network-online.target" ]; requires = [
after = [ "network.target" "network-online.target" ]; "network.target"
"network-online.target"
];
after = [
"network.target"
"network-online.target"
];
wantedBy = lib.optional serverCfg.autoStart "multi-user.target"; wantedBy = lib.optional serverCfg.autoStart "multi-user.target";
environment.RUST_LOG = serverCfg.loggingLevel; environment.RUST_LOG = serverCfg.loggingLevel;
serviceConfig = { serviceConfig = {
Type = "exec"; Type = "exec";
EnvironmentFile = EnvironmentFile = lib.optional (serverCfg.environmentFile != null) serverCfg.environmentFile;
lib.optional (serverCfg.environmentFile != null) serverCfg.environmentFile;
DynamicUser = true; DynamicUser = true;
SupplementaryGroups = SupplementaryGroups = lib.optional (serverCfg.useACMEHost != null) certConfig.group;
lib.optional (serverCfg.useACMEHost != null) certConfig.group;
PrivateTmp = true; PrivateTmp = true;
AmbientCapabilities = AmbientCapabilities = lib.optionals (serverCfg.listen.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
lib.optionals (serverCfg.listen.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
NoNewPrivileges = true; NoNewPrivileges = true;
RestrictNamespaces = "uts ipc pid user cgroup"; RestrictNamespaces = "uts ipc pid user cgroup";
ProtectSystem = "strict"; ProtectSystem = "strict";
@ -305,19 +314,16 @@ let
script = with serverCfg; '' script = with serverCfg; ''
${lib.getExe package} \ ${lib.getExe package} \
server \ server \
${lib.cli.toGNUCommandLineShell { } ( ${
lib.recursiveUpdate lib.cli.toGNUCommandLineShell { } (
{ lib.recursiveUpdate {
restrict-to = map hostPortToString restrictTo; restrict-to = map hostPortToString restrictTo;
tls-certificate = if useACMEHost != null tls-certificate =
then "${certConfig.directory}/fullchain.pem" if useACMEHost != null then "${certConfig.directory}/fullchain.pem" else "${tlsCertificate}";
else "${tlsCertificate}"; tls-private-key = if useACMEHost != null then "${certConfig.directory}/key.pem" else "${tlsKey}";
tls-private-key = if useACMEHost != null } extraArgs
then "${certConfig.directory}/key.pem" )
else "${tlsKey}"; } \
}
extraArgs
)} \
${lib.escapeShellArg "${if enableHTTPS then "wss" else "ws"}://${hostPortToString listen}"} ${lib.escapeShellArg "${if enableHTTPS then "wss" else "ws"}://${hostPortToString listen}"}
''; '';
}; };
@ -327,21 +333,26 @@ let
name = "wstunnel-client-${name}"; name = "wstunnel-client-${name}";
value = { value = {
description = "wstunnel client - ${name}"; description = "wstunnel client - ${name}";
requires = [ "network.target" "network-online.target" ]; requires = [
after = [ "network.target" "network-online.target" ]; "network.target"
"network-online.target"
];
after = [
"network.target"
"network-online.target"
];
wantedBy = lib.optional clientCfg.autoStart "multi-user.target"; wantedBy = lib.optional clientCfg.autoStart "multi-user.target";
environment.RUST_LOG = clientCfg.loggingLevel; environment.RUST_LOG = clientCfg.loggingLevel;
serviceConfig = { serviceConfig = {
Type = "exec"; Type = "exec";
EnvironmentFile = EnvironmentFile = lib.optional (clientCfg.environmentFile != null) clientCfg.environmentFile;
lib.optional (clientCfg.environmentFile != null) clientCfg.environmentFile;
DynamicUser = true; DynamicUser = true;
PrivateTmp = true; PrivateTmp = true;
AmbientCapabilities = AmbientCapabilities =
(lib.optionals clientCfg.addNetBind [ "CAP_NET_BIND_SERVICE" ]) ++ (lib.optionals clientCfg.addNetBind [ "CAP_NET_BIND_SERVICE" ])
(lib.optionals (clientCfg.soMark != null) [ "CAP_NET_ADMIN" ]); ++ (lib.optionals (clientCfg.soMark != null) [ "CAP_NET_ADMIN" ]);
NoNewPrivileges = true; NoNewPrivileges = true;
RestrictNamespaces = "uts ipc pid user cgroup"; RestrictNamespaces = "uts ipc pid user cgroup";
ProtectSystem = "strict"; ProtectSystem = "strict";
@ -361,22 +372,22 @@ let
script = with clientCfg; '' script = with clientCfg; ''
${lib.getExe package} \ ${lib.getExe package} \
client \ client \
${lib.cli.toGNUCommandLineShell { } ( ${
lib.recursiveUpdate lib.cli.toGNUCommandLineShell { } (
{ lib.recursiveUpdate {
local-to-remote = localToRemote; local-to-remote = localToRemote;
remote-to-local = remoteToLocal; remote-to-local = remoteToLocal;
http-headers = lib.mapAttrsToList (n: v: "${n}:${v}") customHeaders; http-headers = lib.mapAttrsToList (n: v: "${n}:${v}") customHeaders;
http-proxy = httpProxy; http-proxy = httpProxy;
socket-so-mark = soMark; socket-so-mark = soMark;
http-upgrade-path-prefix = upgradePathPrefix; http-upgrade-path-prefix = upgradePathPrefix;
tls-sni-override = tlsSNI; tls-sni-override = tlsSNI;
tls-verify-certificate = tlsVerifyCertificate; tls-verify-certificate = tlsVerifyCertificate;
websocket-ping-frequency-sec = websocketPingInterval; websocket-ping-frequency-sec = websocketPingInterval;
http-upgrade-credentials = upgradeCredentials; http-upgrade-credentials = upgradeCredentials;
} } extraArgs
extraArgs )
)} \ } \
${lib.escapeShellArg connectTo} ${lib.escapeShellArg connectTo}
''; '';
}; };
@ -399,10 +410,12 @@ in
enableHTTPS = true; enableHTTPS = true;
tlsCertificate = "/var/lib/secrets/fullchain.pem"; tlsCertificate = "/var/lib/secrets/fullchain.pem";
tlsKey = "/var/lib/secrets/key.pem"; tlsKey = "/var/lib/secrets/key.pem";
restrictTo = [{ restrictTo = [
host = "127.0.0.1"; {
port = 51820; host = "127.0.0.1";
}]; port = 51820;
}
];
}; };
}; };
}; };
@ -429,40 +442,39 @@ in
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
systemd.services = systemd.services =
(lib.mapAttrs' generateServerUnit (lib.filterAttrs (n: v: v.enable) cfg.servers)) // (lib.mapAttrs' generateServerUnit (lib.filterAttrs (n: v: v.enable) cfg.servers))
(lib.mapAttrs' generateClientUnit (lib.filterAttrs (n: v: v.enable) cfg.clients)); // (lib.mapAttrs' generateClientUnit (lib.filterAttrs (n: v: v.enable) cfg.clients));
assertions = assertions =
(lib.mapAttrsToList (lib.mapAttrsToList (name: serverCfg: {
(name: serverCfg: { assertion = !(serverCfg.useACMEHost != null && serverCfg.tlsCertificate != null);
assertion = message = ''
!(serverCfg.useACMEHost != null && serverCfg.tlsCertificate != null); Options services.wstunnel.servers."${name}".useACMEHost and services.wstunnel.servers."${name}".{tlsCertificate, tlsKey} are mutually exclusive.
message = '' '';
Options services.wstunnel.servers."${name}".useACMEHost and services.wstunnel.servers."${name}".{tlsCertificate, tlsKey} are mutually exclusive. }) cfg.servers)
''; ++
})
cfg.servers) ++
(lib.mapAttrsToList (lib.mapAttrsToList (name: serverCfg: {
(name: serverCfg: {
assertion = assertion =
(serverCfg.tlsCertificate == null && serverCfg.tlsKey == null) || (serverCfg.tlsCertificate == null && serverCfg.tlsKey == null)
(serverCfg.tlsCertificate != null && serverCfg.tlsKey != null); || (serverCfg.tlsCertificate != null && serverCfg.tlsKey != null);
message = '' message = ''
services.wstunnel.servers."${name}".tlsCertificate and services.wstunnel.servers."${name}".tlsKey need to be set together. services.wstunnel.servers."${name}".tlsCertificate and services.wstunnel.servers."${name}".tlsKey need to be set together.
''; '';
}) }) cfg.servers)
cfg.servers) ++ ++
(lib.mapAttrsToList (lib.mapAttrsToList (name: clientCfg: {
(name: clientCfg: {
assertion = !(clientCfg.localToRemote == [ ] && clientCfg.remoteToLocal == [ ]); assertion = !(clientCfg.localToRemote == [ ] && clientCfg.remoteToLocal == [ ]);
message = '' message = ''
Either one of services.wstunnel.clients."${name}".localToRemote or services.wstunnel.clients."${name}".remoteToLocal must be set. Either one of services.wstunnel.clients."${name}".localToRemote or services.wstunnel.clients."${name}".remoteToLocal must be set.
''; '';
}) }) cfg.clients);
cfg.clients);
}; };
meta.maintainers = with lib.maintainers; [ alyaeanyx rvdp neverbehave ]; meta.maintainers = with lib.maintainers; [
alyaeanyx
rvdp
neverbehave
];
} }

View File

@ -60,37 +60,34 @@ in
clients.my-client = { clients.my-client = {
autoStart = false; autoStart = false;
connectTo = "wss://${domain}:443"; connectTo = "wss://${domain}:443";
localToRemote = [ localToRemote = [ "tcp://8080:localhost:2080" ];
"tcp://8080:localhost:2080" remoteToLocal = [ "tcp://2081:localhost:8081" ];
];
remoteToLocal = [
"tcp://2081:localhost:8081"
];
}; };
}; };
}; };
}; };
testScript = /* python */ '' testScript = # python
start_all() ''
server.wait_for_unit("wstunnel-server-my-server.service") start_all()
client.wait_for_open_port(443, "10.0.0.1") server.wait_for_unit("wstunnel-server-my-server.service")
client.wait_for_open_port(443, "10.0.0.1")
client.systemctl("start wstunnel-client-my-client.service") client.systemctl("start wstunnel-client-my-client.service")
client.wait_for_unit("wstunnel-client-my-client.service") client.wait_for_unit("wstunnel-client-my-client.service")
with subtest("connection from client to server"): with subtest("connection from client to server"):
server.succeed("nc -l 2080 >/tmp/msg &") server.succeed("nc -l 2080 >/tmp/msg &")
client.sleep(1) client.sleep(1)
client.succeed('nc -w1 localhost 8080 <<<"Hello from client"') client.succeed('nc -w1 localhost 8080 <<<"Hello from client"')
server.succeed('grep "Hello from client" /tmp/msg') server.succeed('grep "Hello from client" /tmp/msg')
with subtest("connection from server to client"): with subtest("connection from server to client"):
client.succeed("nc -l 8081 >/tmp/msg &") client.succeed("nc -l 8081 >/tmp/msg &")
server.sleep(1) server.sleep(1)
server.succeed('nc -w1 localhost 2081 <<<"Hello from server"') server.succeed('nc -w1 localhost 2081 <<<"Hello from server"')
client.succeed('grep "Hello from server" /tmp/msg') client.succeed('grep "Hello from server" /tmp/msg')
client.systemctl("stop wstunnel-client-my-client.service") client.systemctl("stop wstunnel-client-my-client.service")
''; '';
} }

View File

@ -1,9 +1,10 @@
{ lib {
, fetchFromGitHub lib,
, rustPlatform fetchFromGitHub,
, testers rustPlatform,
, wstunnel testers,
, nixosTests wstunnel,
nixosTests,
}: }:
let let
@ -38,7 +39,10 @@ rustPlatform.buildRustPackage {
homepage = "https://github.com/erebe/wstunnel"; homepage = "https://github.com/erebe/wstunnel";
changelog = "https://github.com/erebe/wstunnel/releases/tag/v${version}"; changelog = "https://github.com/erebe/wstunnel/releases/tag/v${version}";
license = lib.licenses.bsd3; license = lib.licenses.bsd3;
maintainers = with lib.maintainers; [ rvdp neverbehave ]; maintainers = with lib.maintainers; [
rvdp
neverbehave
];
mainProgram = "wstunnel"; mainProgram = "wstunnel";
}; };
} }