From 8d7a9b322770cbbed116da8d349bab32acf6bf7b Mon Sep 17 00:00:00 2001 From: r-vdp Date: Thu, 13 Jun 2024 11:18:57 +0300 Subject: [PATCH 1/4] wstunnel: 9.6.2 -> 9.7.0 --- pkgs/by-name/ws/wstunnel/package.nix | 38 ++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/pkgs/by-name/ws/wstunnel/package.nix b/pkgs/by-name/ws/wstunnel/package.nix index 9e172a08f375..189abbb6b180 100644 --- a/pkgs/by-name/ws/wstunnel/package.nix +++ b/pkgs/by-name/ws/wstunnel/package.nix @@ -3,30 +3,46 @@ , lib }: -rustPlatform.buildRustPackage rec { +let + version = "9.7.0"; +in + +rustPlatform.buildRustPackage { pname = "wstunnel"; - version = "9.6.2"; + inherit version; src = fetchFromGitHub { owner = "erebe"; - repo = "wstunnel"; + repo = "wstunnel"; rev = "v${version}"; - hash = "sha256-0r+8C8Gf3/s3opzplzc22d9VVp39FtBq1bYkxlmtqjg="; + hash = "sha256-8bLccR6ZmldmrvjlZKFHEa4PoLzyUcLkyQbwSrJjoyY="; }; - cargoHash = "sha256-hHVxa7Ihmuuf26ZSzGmrHA2RczhzXtse3h1M4cNCvhw="; + cargoHash = "sha256-IAq7Fyr6Ne1Bq18WfqBoppel9FOWSs8PkiXKMwcJ26c="; checkFlags = [ - # make use of network connection + # Tries to launch a test container "--skip=tcp::tests::test_proxy_connection" ]; + doInstallCheck = true; + installCheckPhase = '' + runHook preInstallCheck + actual="$($out/bin/wstunnel --version)" + expected="${pname} ${version}" + echo "Check that 'wstunnel --version' returns: $expected" + if [[ "$actual" != "$expected" ]]; then + echo "'wstunnel --version' returned: $actual" + exit 1 + fi + runHook postInstallCheck + ''; + meta = { - description = "Tunneling program over websocket protocol"; + description = "Tunnel all your traffic over Websocket or HTTP2 - Bypass firewalls/DPI"; + homepage = "https://github.com/erebe/wstunnel"; + license = lib.licenses.bsd3; + maintainers = with lib.maintainers; [ rvdp neverbehave ]; mainProgram = "wstunnel"; - homepage = "https://github.com/erebe/wstunnel"; - license = with lib.licenses; [ bsd3 ]; - maintainers = with lib.maintainers; [ neverbehave ]; - platforms = lib.platforms.linux; }; } From 53e7bea45c8387212509202704ce38890395adca Mon Sep 17 00:00:00 2001 From: r-vdp Date: Thu, 13 Jun 2024 14:52:15 +0300 Subject: [PATCH 2/4] nixos/wstunnel: update the wstunnel module to work with the new rust implementation Co-authored-by: h7x4 --- .../modules/services/networking/wstunnel.nix | 425 +++++++++++------- pkgs/by-name/ws/wstunnel/package.nix | 20 +- 2 files changed, 261 insertions(+), 184 deletions(-) diff --git a/nixos/modules/services/networking/wstunnel.nix b/nixos/modules/services/networking/wstunnel.nix index 1b169567624c..cd489031c073 100644 --- a/nixos/modules/services/networking/wstunnel.nix +++ b/nixos/modules/services/networking/wstunnel.nix @@ -1,83 +1,94 @@ -{ config, lib, options, pkgs, utils, ... }: -with lib; +{ config +, lib +, pkgs +, ... +}: + let cfg = config.services.wstunnel; - attrsToArgs = attrs: utils.escapeSystemdExecArgs ( - mapAttrsToList - (name: value: if value == true then "--${name}" else "--${name}=${value}") - attrs - ); - hostPortToString = { host, port }: "${host}:${builtins.toString port}"; + hostPortToString = { host, port }: "${host}:${toString port}"; hostPortSubmodule = { options = { - host = mkOption { + host = lib.mkOption { description = "The hostname."; - type = types.str; + type = lib.types.str; }; - port = mkOption { + port = lib.mkOption { description = "The port."; - type = types.port; + type = lib.types.port; }; }; }; commonOptions = { - enable = mkOption { - description = "Whether to enable this `wstunnel` instance."; - type = types.bool; + enable = lib.mkEnableOption "this `wstunnel` instance." // { default = true; }; - package = mkPackageOption pkgs "wstunnel" {}; + package = lib.mkPackageOption pkgs "wstunnel" { }; - autoStart = mkOption { - description = "Whether this tunnel server should be started automatically."; - type = types.bool; - default = true; - }; + autoStart = + lib.mkEnableOption "starting this wstunnel instance automatically." // { + default = true; + }; - extraArgs = mkOption { - description = "Extra command line arguments to pass to `wstunnel`. Attributes of the form `argName = true;` will be translated to `--argName`, and `argName = \"value\"` to `--argName=value`."; - type = with types; attrsOf (either str bool); - default = {}; + extraArgs = lib.mkOption { + description = '' + Extra command line arguments to pass to `wstunnel`. + Attributes of the form `argName = true;` will be translated to `--argName`, + and `argName = \"value\"` to `--argName value`. + ''; + type = with lib.types; attrsOf (either str bool); + default = { }; example = { "someNewOption" = true; "someNewOptionWithValue" = "someValue"; }; }; - loggingLevel = mkOption { + loggingLevel = lib.mkOption { description = '' Passed to --log-lvl Control the log verbosity. i.e: TRACE, DEBUG, INFO, WARN, ERROR, OFF For more details, checkout [EnvFilter](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#example-syntax) ''; - type = types.nullOr types.str; + type = lib.types.nullOr lib.types.str; example = "INFO"; default = null; }; - environmentFile = mkOption { - description = "Environment file to be passed to the systemd service. Useful for passing secrets to the service to prevent them from being world-readable in the Nix store. Note however that the secrets are passed to `wstunnel` through the command line, which makes them locally readable for all users of the system at runtime."; - type = types.nullOr types.path; + environmentFile = lib.mkOption { + description = '' + Environment file to be passed to the systemd service. + Useful for passing secrets to the service to prevent them from being + world-readable in the Nix store. + Note however that the secrets are passed to `wstunnel` through + the command line, which makes them locally readable for all users of + the system at runtime. + ''; + type = lib.types.nullOr lib.types.path; default = null; example = "/var/lib/secrets/wstunnelSecrets"; }; }; - serverSubmodule = { config, ...}: { + serverSubmodule = { config, ... }: { options = commonOptions // { - listen = mkOption { - description = "Address and port to listen on. Setting the port to a value below 1024 will also give the process the required `CAP_NET_BIND_SERVICE` capability."; - type = types.submodule hostPortSubmodule; + listen = lib.mkOption { + description = '' + Address and port to listen on. + Setting the port to a value below 1024 will also give the process + the required `CAP_NET_BIND_SERVICE` capability. + ''; + type = lib.types.submodule hostPortSubmodule; default = { host = "0.0.0.0"; port = if config.enableHTTPS then 443 else 80; }; - defaultText = literalExpression '' + defaultText = lib.literalExpression '' { host = "0.0.0.0"; port = if enableHTTPS then 443 else 80; @@ -85,39 +96,50 @@ let ''; }; - restrictTo = mkOption { - description = "Accepted traffic will be forwarded only to this service. Set to `null` to allow forwarding to arbitrary addresses."; - type = types.listOf (types.submodule hostPortSubmodule); - default = []; + restrictTo = lib.mkOption { + description = '' + Accepted traffic will be forwarded only to this service. + ''; + type = lib.types.listOf (lib.types.submodule hostPortSubmodule); + default = [ ]; example = [{ host = "127.0.0.1"; port = 51820; }]; }; - enableHTTPS = mkOption { + enableHTTPS = lib.mkOption { description = "Use HTTPS for the tunnel server."; - type = types.bool; + type = lib.types.bool; default = true; }; - tlsCertificate = mkOption { - description = "TLS certificate to use instead of the hardcoded one in case of HTTPS connections. Use together with `tlsKey`."; - type = types.nullOr types.path; + tlsCertificate = lib.mkOption { + description = '' + TLS certificate to use instead of the hardcoded one in case of HTTPS connections. + Use together with `tlsKey`. + ''; + type = lib.types.nullOr lib.types.path; default = null; example = "/var/lib/secrets/cert.pem"; }; - tlsKey = mkOption { - description = "TLS key to use instead of the hardcoded on in case of HTTPS connections. Use together with `tlsCertificate`."; - type = types.nullOr types.path; + tlsKey = lib.mkOption { + description = '' + TLS key to use instead of the hardcoded on in case of HTTPS connections. + Use together with `tlsCertificate`. + ''; + type = lib.types.nullOr lib.types.path; default = null; example = "/var/lib/secrets/key.pem"; }; - useACMEHost = mkOption { - description = "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`."; - type = types.nullOr types.str; + useACMEHost = lib.mkOption { + description = '' + 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`. + ''; + type = lib.types.nullOr lib.types.str; default = null; example = "example.com"; }; @@ -126,95 +148,113 @@ let clientSubmodule = { config, ... }: { options = commonOptions // { - connectTo = mkOption { + connectTo = lib.mkOption { description = "Server address and port to connect to."; - type = types.str; + type = lib.types.str; example = "https://wstunnel.server.com:8443"; }; - localToRemote = mkOption { + localToRemote = lib.mkOption { description = ''Listen on local and forwards traffic from remote.''; - type = types.listOf (types.str); - default = []; + type = lib.types.listOf (lib.types.str); + default = [ ]; example = [ "tcp://1212:google.com:443" "unix:///tmp/wstunnel.sock:g.com:443" ]; }; - remoteToLocal = mkOption { + remoteToLocal = lib.mkOption { description = "Listen on remote and forwards traffic from local. Only tcp is supported"; - type = types.listOf (types.str); - default = []; + type = lib.types.listOf lib.types.str; + default = [ ]; example = [ "tcp://1212:google.com:443" "unix://wstunnel.sock:g.com:443" ]; }; - addNetBind = mkEnableOption "Whether add CAP_NET_BIND_SERVICE to the tunnel service, this should be enabled if you want to bind port < 1024"; + 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 = mkOption { + httpProxy = lib.mkOption { description = '' Proxy to use to connect to the wstunnel server (`USER:PASS@HOST:PORT`). ::: {.warning} - Passwords specified here will be world-readable in the Nix store! To pass a password to the service, point the `environmentFile` option to a file containing `PROXY_PASSWORD=` and set this option to `:$PROXY_PASSWORD@:`. Note however that this will also locally leak the passwords at runtime via e.g. /proc//cmdline. - + Passwords specified here will be world-readable in the Nix store! + To pass a password to the service, point the `environmentFile` option + to a file containing `PROXY_PASSWORD=` and set + this option to `:$PROXY_PASSWORD@:`. + Note however that this will also locally leak the passwords at + runtime via e.g. /proc//cmdline. ::: ''; - type = types.nullOr types.str; + type = lib.types.nullOr lib.types.str; default = null; }; - soMark = mkOption { - description = "Mark network packets with the SO_MARK sockoption with the specified value. Setting this option will also enable the required `CAP_NET_ADMIN` capability for the systemd service."; - type = types.nullOr types.int; + soMark = lib.mkOption { + description = '' + Mark network packets with the SO_MARK sockoption with the specified value. + Setting this option will also enable the required `CAP_NET_ADMIN` capability + for the systemd service. + ''; + type = lib.types.nullOr lib.types.ints.unsigned; default = null; }; - upgradePathPrefix = mkOption { - description = "Use a specific HTTP path prefix that will show up in the upgrade request to the `wstunnel` server. Useful when running `wstunnel` behind a reverse proxy."; - type = types.nullOr types.str; + upgradePathPrefix = lib.mkOption { + description = '' + Use a specific HTTP path prefix that will show up in the upgrade + request to the `wstunnel` server. + Useful when running `wstunnel` behind a reverse proxy. + ''; + type = lib.types.nullOr lib.types.str; default = null; example = "wstunnel"; }; - tlsSNI = mkOption { + tlsSNI = lib.mkOption { description = "Use this as the SNI while connecting via TLS. Useful for circumventing hostname-based firewalls."; - type = types.nullOr types.str; + type = lib.types.nullOr lib.types.str; default = null; }; - tlsVerifyCertificate = mkOption { + 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 = types.bool; + type = lib.types.bool; default = true; }; # The original argument name `websocketPingFrequency` is a misnomer, as the frequency is the inverse of the interval. - websocketPingInterval = mkOption { + websocketPingInterval = lib.mkOption { description = "Frequency at which the client will send websocket ping to the server."; - type = types.nullOr types.ints.unsigned; + type = lib.types.nullOr lib.types.ints.unsigned; default = null; }; - upgradeCredentials = mkOption { + upgradeCredentials = lib.mkOption { description = '' - Use these credentials to authenticate during the HTTP upgrade request (Basic authorization type, `USER:[PASS]`). + Use these credentials to authenticate during the HTTP upgrade request + (Basic authorization type, `USER:[PASS]`). ::: {.warning} - Passwords specified here will be world-readable in the Nix store! To pass a password to the service, point the `environmentFile` option to a file containing `HTTP_PASSWORD=` and set this option to `:$HTTP_PASSWORD`. Note however that this will also locally leak the passwords at runtime via e.g. /proc//cmdline. + Passwords specified here will be world-readable in the Nix store! + To pass a password to the service, point the `environmentFile` option + to a file containing `HTTP_PASSWORD=` and set this + option to `:$HTTP_PASSWORD`. + Note however that this will also locally leak the passwords at runtime + via e.g. /proc//cmdline. ::: ''; - type = types.nullOr types.str; + type = lib.types.nullOr lib.types.str; default = null; }; - customHeaders = mkOption { + customHeaders = lib.mkOption { description = "Custom HTTP headers to send during the upgrade request."; - type = types.attrsOf types.str; - default = {}; + type = lib.types.attrsOf lib.types.str; + default = { }; example = { "X-Some-Header" = "some-value"; }; @@ -224,49 +264,63 @@ let generateServerUnit = name: serverCfg: { name = "wstunnel-server-${name}"; - value = { - description = "wstunnel server - ${name}"; - requires = [ "network.target" "network-online.target" ]; - after = [ "network.target" "network-online.target" ]; - wantedBy = optional serverCfg.autoStart "multi-user.target"; + value = + let + certConfig = config.security.acme.certs.${serverCfg.useACMEHost}; + in + { + description = "wstunnel server - ${name}"; + requires = [ "network.target" "network-online.target" ]; + after = [ "network.target" "network-online.target" ]; + wantedBy = lib.optional serverCfg.autoStart "multi-user.target"; - serviceConfig = let - certConfig = config.security.acme.certs."${serverCfg.useACMEHost}"; - in { - Type = "simple"; - ExecStart = with serverCfg; let - resolvedTlsCertificate = if useACMEHost != null - then "${certConfig.directory}/fullchain.pem" - else tlsCertificate; - resolvedTlsKey = if useACMEHost != null - then "${certConfig.directory}/key.pem" - else tlsKey; - in '' - ${package}/bin/wstunnel \ + environment.RUST_LOG = serverCfg.loggingLevel; + + serviceConfig = { + Type = "simple"; + EnvironmentFile = + lib.optional (serverCfg.environmentFile != null) serverCfg.environmentFile; + DynamicUser = true; + SupplementaryGroups = + lib.optional (serverCfg.useACMEHost != null) certConfig.group; + PrivateTmp = true; + AmbientCapabilities = + lib.optionals (serverCfg.listen.port < 1024) [ "CAP_NET_BIND_SERVICE" ]; + NoNewPrivileges = true; + RestrictNamespaces = "uts ipc pid user cgroup"; + ProtectSystem = "strict"; + ProtectHome = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + PrivateDevices = true; + RestrictSUIDSGID = true; + + Restart = "on-failure"; + RestartSec = 2; + RestartSteps = 20; + RestartMaxDelaySec = "5min"; + }; + + script = with serverCfg; '' + ${lib.getExe package} \ server \ - ${concatStringsSep " " (builtins.map (hostPair: "--restrict-to ${utils.escapeSystemdExecArg (hostPortToString hostPair)}") restrictTo)} \ - ${optionalString (resolvedTlsCertificate != null) "--tls-certificate ${utils.escapeSystemdExecArg resolvedTlsCertificate}"} \ - ${optionalString (resolvedTlsKey != null) "--tls-private-key ${utils.escapeSystemdExecArg resolvedTlsKey}"} \ - ${optionalString (loggingLevel != null) "--log-lvl ${loggingLevel}"} \ - ${attrsToArgs extraArgs} \ - ${utils.escapeSystemdExecArg "${if enableHTTPS then "wss" else "ws"}://${hostPortToString listen}"} + ${lib.cli.toGNUCommandLineShell { } ( + lib.recursiveUpdate + { + restrict-to = map hostPortToString restrictTo; + tls-certificate = if useACMEHost != null + then "${certConfig.directory}/fullchain.pem" + else "${tlsCertificate}"; + tls-private-key = if useACMEHost != null + then "${certConfig.directory}/key.pem" + else "${tlsKey}"; + } + extraArgs + )} \ + ${lib.escapeShellArg "${if enableHTTPS then "wss" else "ws"}://${hostPortToString listen}"} ''; - EnvironmentFile = optional (serverCfg.environmentFile != null) serverCfg.environmentFile; - DynamicUser = true; - SupplementaryGroups = optional (serverCfg.useACMEHost != null) certConfig.group; - PrivateTmp = true; - AmbientCapabilities = optionals (serverCfg.listen.port < 1024) [ "CAP_NET_BIND_SERVICE" ]; - NoNewPrivileges = true; - RestrictNamespaces = "uts ipc pid user cgroup"; - ProtectSystem = "strict"; - ProtectHome = true; - ProtectKernelTunables = true; - ProtectKernelModules = true; - ProtectControlGroups = true; - PrivateDevices = true; - RestrictSUIDSGID = true; }; - }; }; generateClientUnit = name: clientCfg: { @@ -275,30 +329,19 @@ let description = "wstunnel client - ${name}"; requires = [ "network.target" "network-online.target" ]; after = [ "network.target" "network-online.target" ]; - wantedBy = optional clientCfg.autoStart "multi-user.target"; + wantedBy = lib.optional clientCfg.autoStart "multi-user.target"; + + environment.RUST_LOG = clientCfg.loggingLevel; serviceConfig = { Type = "simple"; - ExecStart = with clientCfg; '' - ${package}/bin/wstunnel client \ - ${concatStringsSep " " (builtins.map (x: "--local-to-remote ${x}") localToRemote)} \ - ${concatStringsSep " " (builtins.map (x: "--remote-to-local ${x}") remoteToLocal)} \ - ${concatStringsSep " " (mapAttrsToList (n: v: "--http-headers \"${n}: ${v}\"") customHeaders)} \ - ${optionalString (httpProxy != null) "--http-proxy ${httpProxy}"} \ - ${optionalString (soMark != null) "--socket-so-mark=${toString soMark}"} \ - ${optionalString (upgradePathPrefix != null) "--http-upgrade-path-prefix ${upgradePathPrefix}"} \ - ${optionalString (tlsSNI != null) "--tls-sni-override ${tlsSNI}"} \ - ${optionalString tlsVerifyCertificate "--tls-verify-certificate"} \ - ${optionalString (websocketPingInterval != null) "--websocket-ping-frequency-sec ${toString websocketPingInterval}"} \ - ${optionalString (upgradeCredentials != null) "--http-upgrade-credentials ${upgradeCredentials}"} \ - ${optionalString (loggingLevel != null) "--log-lvl ${loggingLevel}"} \ - ${attrsToArgs extraArgs} \ - ${utils.escapeSystemdExecArg connectTo} - ''; - EnvironmentFile = optional (clientCfg.environmentFile != null) clientCfg.environmentFile; + EnvironmentFile = + lib.optional (clientCfg.environmentFile != null) clientCfg.environmentFile; DynamicUser = true; PrivateTmp = true; - AmbientCapabilities = (optionals (clientCfg.soMark != null) [ "CAP_NET_ADMIN" ]) ++ (optionals (clientCfg.addNetBind) [ "CAP_NET_BIND_SERVICE" ]); + AmbientCapabilities = + (lib.optionals clientCfg.addNetBind [ "CAP_NET_BIND_SERVICE" ]) ++ + (lib.optionals (clientCfg.soMark != null) [ "CAP_NET_ADMIN" ]); NoNewPrivileges = true; RestrictNamespaces = "uts ipc pid user cgroup"; ProtectSystem = "strict"; @@ -308,17 +351,45 @@ let ProtectControlGroups = true; PrivateDevices = true; RestrictSUIDSGID = true; + + Restart = "on-failure"; + RestartSec = 2; + RestartSteps = 20; + RestartMaxDelaySec = "5min"; }; + + script = with clientCfg; '' + ${lib.getExe package} \ + client \ + ${lib.cli.toGNUCommandLineShell { } ( + lib.recursiveUpdate + { + local-to-remote = localToRemote; + remote-to-local = remoteToLocal; + http-headers = lib.mapAttrsToList (n: v: "${n}:${v}") customHeaders; + http-proxy = httpProxy; + socket-so-mark = soMark; + http-upgrade-path-prefix = upgradePathPrefix; + tls-sni-override = tlsSNI; + tls-verify-certificate = tlsVerifyCertificate; + websocket-ping-frequency-sec = websocketPingInterval; + http-upgrade-credentials = upgradeCredentials; + } + extraArgs + )} \ + ${lib.escapeShellArg connectTo} + ''; }; }; -in { +in +{ options.services.wstunnel = { - enable = mkEnableOption "wstunnel"; + enable = lib.mkEnableOption "wstunnel"; - servers = mkOption { + servers = lib.mkOption { description = "`wstunnel` servers to set up."; - type = types.attrsOf (types.submodule serverSubmodule); - default = {}; + type = lib.types.attrsOf (lib.types.submodule serverSubmodule); + default = { }; example = { "wg-tunnel" = { listen = { @@ -336,13 +407,13 @@ in { }; }; - clients = mkOption { + clients = lib.mkOption { description = "`wstunnel` clients to set up."; - type = types.attrsOf (types.submodule clientSubmodule); - default = {}; + type = lib.types.attrsOf (lib.types.submodule clientSubmodule); + default = { }; example = { "wg-tunnel" = { - connectTo = "https://wstunnel.server.com:8443"; + connectTo = "wss://wstunnel.server.com:8443"; localToRemote = [ "tcp://1212:google.com:443" "tcp://2:n.lan:4?proxy_protocol" @@ -356,28 +427,42 @@ in { }; }; - config = mkIf cfg.enable { - systemd.services = (mapAttrs' generateServerUnit (filterAttrs (n: v: v.enable) cfg.servers)) // (mapAttrs' generateClientUnit (filterAttrs (n: v: v.enable) cfg.clients)); + config = lib.mkIf cfg.enable { + systemd.services = + (lib.mapAttrs' generateServerUnit (lib.filterAttrs (n: v: v.enable) cfg.servers)) // + (lib.mapAttrs' generateClientUnit (lib.filterAttrs (n: v: v.enable) cfg.clients)); - assertions = (mapAttrsToList (name: serverCfg: { - assertion = !(serverCfg.useACMEHost != null && (serverCfg.tlsCertificate != null || serverCfg.tlsKey != null)); - message = '' - Options services.wstunnel.servers."${name}".useACMEHost and services.wstunnel.servers."${name}".{tlsCertificate, tlsKey} are mutually exclusive. - ''; - }) cfg.servers) ++ - (mapAttrsToList (name: serverCfg: { - assertion = !((serverCfg.tlsCertificate != null || serverCfg.tlsKey != null) && !(serverCfg.tlsCertificate != null && serverCfg.tlsKey != null)); - message = '' - services.wstunnel.servers."${name}".tlsCertificate and services.wstunnel.servers."${name}".tlsKey need to be set together. - ''; - }) cfg.servers) ++ - (mapAttrsToList (name: clientCfg: { - assertion = !(clientCfg.localToRemote == [] && clientCfg.remoteToLocal == []); - message = '' - Either one of services.wstunnel.clients."${name}".localToRemote or services.wstunnel.clients."${name}".remoteToLocal must be set. - ''; - }) cfg.clients); + assertions = + (lib.mapAttrsToList + (name: serverCfg: { + assertion = + !(serverCfg.useACMEHost != null && serverCfg.tlsCertificate != null); + message = '' + Options services.wstunnel.servers."${name}".useACMEHost and services.wstunnel.servers."${name}".{tlsCertificate, tlsKey} are mutually exclusive. + ''; + }) + cfg.servers) ++ + + (lib.mapAttrsToList + (name: serverCfg: { + assertion = + (serverCfg.tlsCertificate == null && serverCfg.tlsKey == null) || + (serverCfg.tlsCertificate != null && serverCfg.tlsKey != null); + message = '' + services.wstunnel.servers."${name}".tlsCertificate and services.wstunnel.servers."${name}".tlsKey need to be set together. + ''; + }) + cfg.servers) ++ + + (lib.mapAttrsToList + (name: clientCfg: { + assertion = !(clientCfg.localToRemote == [ ] && clientCfg.remoteToLocal == [ ]); + message = '' + Either one of services.wstunnel.clients."${name}".localToRemote or services.wstunnel.clients."${name}".remoteToLocal must be set. + ''; + }) + cfg.clients); }; - meta.maintainers = with maintainers; [ alyaeanyx neverbehave ]; + meta.maintainers = with lib.maintainers; [ alyaeanyx rvdp neverbehave ]; } diff --git a/pkgs/by-name/ws/wstunnel/package.nix b/pkgs/by-name/ws/wstunnel/package.nix index 189abbb6b180..20b4b3187e3a 100644 --- a/pkgs/by-name/ws/wstunnel/package.nix +++ b/pkgs/by-name/ws/wstunnel/package.nix @@ -1,6 +1,8 @@ -{ fetchFromGitHub +{ lib +, fetchFromGitHub , rustPlatform -, lib +, testers +, wstunnel }: let @@ -25,22 +27,12 @@ rustPlatform.buildRustPackage { "--skip=tcp::tests::test_proxy_connection" ]; - doInstallCheck = true; - installCheckPhase = '' - runHook preInstallCheck - actual="$($out/bin/wstunnel --version)" - expected="${pname} ${version}" - echo "Check that 'wstunnel --version' returns: $expected" - if [[ "$actual" != "$expected" ]]; then - echo "'wstunnel --version' returned: $actual" - exit 1 - fi - runHook postInstallCheck - ''; + passthru.tests.version = testers.testVersion { package = wstunnel; }; meta = { description = "Tunnel all your traffic over Websocket or HTTP2 - Bypass firewalls/DPI"; homepage = "https://github.com/erebe/wstunnel"; + changelog = "https://github.com/erebe/wstunnel/releases/tag/v${version}"; license = lib.licenses.bsd3; maintainers = with lib.maintainers; [ rvdp neverbehave ]; mainProgram = "wstunnel"; From 4c7c3ceb124ba51891022c62f2fa05cb0862bbce Mon Sep 17 00:00:00 2001 From: h7x4 Date: Sat, 15 Jun 2024 16:32:52 +0200 Subject: [PATCH 3/4] nixosTests.wstunnel: init Co-authored-by: r-vdp --- .../modules/services/networking/wstunnel.nix | 4 +- nixos/tests/all-tests.nix | 1 + nixos/tests/wstunnel.nix | 96 +++++++++++++++++++ pkgs/by-name/ws/wstunnel/package.nix | 6 +- 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 nixos/tests/wstunnel.nix diff --git a/nixos/modules/services/networking/wstunnel.nix b/nixos/modules/services/networking/wstunnel.nix index cd489031c073..bd7536351955 100644 --- a/nixos/modules/services/networking/wstunnel.nix +++ b/nixos/modules/services/networking/wstunnel.nix @@ -277,7 +277,7 @@ let environment.RUST_LOG = serverCfg.loggingLevel; serviceConfig = { - Type = "simple"; + Type = "exec"; EnvironmentFile = lib.optional (serverCfg.environmentFile != null) serverCfg.environmentFile; DynamicUser = true; @@ -334,7 +334,7 @@ let environment.RUST_LOG = clientCfg.loggingLevel; serviceConfig = { - Type = "simple"; + Type = "exec"; EnvironmentFile = lib.optional (clientCfg.environmentFile != null) clientCfg.environmentFile; DynamicUser = true; diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 746b29fd2725..bfeab82e5f1b 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1043,6 +1043,7 @@ in { wordpress = handleTest ./wordpress.nix {}; wrappers = handleTest ./wrappers.nix {}; writefreely = handleTest ./web-apps/writefreely.nix {}; + wstunnel = runTest ./wstunnel.nix; xandikos = handleTest ./xandikos.nix {}; xautolock = handleTest ./xautolock.nix {}; xfce = handleTest ./xfce.nix {}; diff --git a/nixos/tests/wstunnel.nix b/nixos/tests/wstunnel.nix new file mode 100644 index 000000000000..3bbc295568fb --- /dev/null +++ b/nixos/tests/wstunnel.nix @@ -0,0 +1,96 @@ +let + certs = import ./common/acme/server/snakeoil-certs.nix; + domain = certs.domain; +in + +{ + name = "wstunnel"; + + nodes = { + server = { + virtualisation.vlans = [ 1 ]; + + security.pki.certificateFiles = [ certs.ca.cert ]; + + networking = { + useNetworkd = true; + useDHCP = false; + firewall.enable = false; + }; + + systemd.network.networks."01-eth1" = { + name = "eth1"; + networkConfig.Address = "10.0.0.1/24"; + }; + + services.wstunnel = { + enable = true; + servers.my-server = { + listen = { + host = "10.0.0.1"; + port = 443; + }; + tlsCertificate = certs.${domain}.cert; + tlsKey = certs.${domain}.key; + }; + }; + }; + + client = { + virtualisation.vlans = [ 1 ]; + + security.pki.certificateFiles = [ certs.ca.cert ]; + + networking = { + useNetworkd = true; + useDHCP = false; + firewall.enable = false; + extraHosts = '' + 10.0.0.1 ${domain} + ''; + }; + + systemd.network.networks."01-eth1" = { + name = "eth1"; + networkConfig.Address = "10.0.0.2/24"; + }; + + services.wstunnel = { + enable = true; + clients.my-client = { + autoStart = false; + connectTo = "wss://${domain}:443"; + localToRemote = [ + "tcp://8080:localhost:2080" + ]; + remoteToLocal = [ + "tcp://2081:localhost:8081" + ]; + }; + }; + }; + }; + + testScript = /* python */ '' + start_all() + 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.wait_for_unit("wstunnel-client-my-client.service") + + with subtest("connection from client to server"): + server.succeed("nc -l 2080 >/tmp/msg &") + client.sleep(1) + client.succeed('nc -w1 localhost 8080 <<<"Hello from client"') + server.succeed('grep "Hello from client" /tmp/msg') + + with subtest("connection from server to client"): + client.succeed("nc -l 8081 >/tmp/msg &") + server.sleep(1) + server.succeed('nc -w1 localhost 2081 <<<"Hello from server"') + client.succeed('grep "Hello from server" /tmp/msg') + + client.systemctl("stop wstunnel-client-my-client.service") + ''; +} diff --git a/pkgs/by-name/ws/wstunnel/package.nix b/pkgs/by-name/ws/wstunnel/package.nix index 20b4b3187e3a..cfcaa1dc8e47 100644 --- a/pkgs/by-name/ws/wstunnel/package.nix +++ b/pkgs/by-name/ws/wstunnel/package.nix @@ -3,6 +3,7 @@ , rustPlatform , testers , wstunnel +, nixosTests }: let @@ -27,7 +28,10 @@ rustPlatform.buildRustPackage { "--skip=tcp::tests::test_proxy_connection" ]; - passthru.tests.version = testers.testVersion { package = wstunnel; }; + passthru.tests = { + version = testers.testVersion { package = wstunnel; }; + nixosTest = nixosTests.wstunnel; + }; meta = { description = "Tunnel all your traffic over Websocket or HTTP2 - Bypass firewalls/DPI"; From 0faddabc33c18ab499a75bd8187f9255f17a6b8f Mon Sep 17 00:00:00 2001 From: r-vdp Date: Mon, 17 Jun 2024 13:08:53 +0300 Subject: [PATCH 4/4] nixos/wstunnel: Add a mention in the release notes --- nixos/doc/manual/release-notes/rl-2411.section.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md index 889d39974932..ced12d738687 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -21,6 +21,16 @@ - `androidenv.androidPkgs_9_0` has been removed, and replaced with `androidenv.androidPkgs` for a more complete Android SDK including support for Android 9 and later. +- `wstunnel` has had a major version upgrade that entailed rewriting the program in Rust. + The module was updated to accommodate for breaking changes. + Breaking changes to the module API were minimised as much as possible, + but some were nonetheless inevitable due to changes in the upstream CLI. + Certain options were moved from separate CLI arguments into the forward specifications, + and those options were also removed from the module's API, + please consult the wstunnel man page for more detail. + Also be aware that if you have set additional options in `services.wstunnel.{clients,servers}..extraArgs`, + that those might have been removed or modified upstream. + - `nginx` package no longer includes `gd` and `geoip` dependencies. For enabling it, override `nginx` package with the optionals `withImageFilter` and `withGeoIP`. - `openssh` and `openssh_hpn` are now compiled without Kerberos 5 / GSSAPI support in an effort to reduce the attack surface of the components for the majority of users. Users needing this support can