mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-07-13 21:50:33 +03:00
503 lines
15 KiB
Nix
503 lines
15 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
let
|
|
cfg = config.services.wstunnel;
|
|
|
|
argsFormat = {
|
|
type =
|
|
let
|
|
inherit (lib.types)
|
|
attrsOf
|
|
listOf
|
|
oneOf
|
|
bool
|
|
int
|
|
str
|
|
;
|
|
in
|
|
attrsOf (oneOf [
|
|
bool
|
|
int
|
|
str
|
|
(listOf str)
|
|
]);
|
|
generate = lib.cli.toGNUCommandLineShell { };
|
|
};
|
|
|
|
hostPortToString = { host, port, ... }: "${host}:${toString port}";
|
|
|
|
commonOptions = {
|
|
enable = lib.mkEnableOption "this `wstunnel` instance" // {
|
|
default = true;
|
|
};
|
|
|
|
package = lib.mkPackageOption pkgs "wstunnel" { };
|
|
|
|
autoStart = lib.mkEnableOption "starting this wstunnel instance automatically" // {
|
|
default = true;
|
|
};
|
|
|
|
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 =
|
|
let
|
|
outerConfig = config;
|
|
in
|
|
{ config, ... }:
|
|
let
|
|
certConfig = outerConfig.security.acme.certs.${config.useACMEHost};
|
|
in
|
|
{
|
|
imports =
|
|
[
|
|
../../misc/assertions.nix
|
|
|
|
(lib.mkRenamedOptionModule
|
|
[
|
|
"enableHTTPS"
|
|
]
|
|
[
|
|
"listen"
|
|
"enableHTTPS"
|
|
]
|
|
)
|
|
]
|
|
++ lib.map
|
|
(
|
|
option:
|
|
lib.mkRemovedOptionModule [ option ] ''
|
|
The wstunnel module now uses RFC-42-style settings, please modify your config accordingly
|
|
''
|
|
)
|
|
[
|
|
"extraArgs"
|
|
"websocketPingInterval"
|
|
"loggingLevel"
|
|
|
|
"restrictTo"
|
|
"tlsCertificate"
|
|
"tlsKey"
|
|
];
|
|
|
|
options = commonOptions // {
|
|
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 {
|
|
options = {
|
|
host = lib.mkOption {
|
|
description = "The hostname.";
|
|
type = lib.types.str;
|
|
};
|
|
port = lib.mkOption {
|
|
description = "The port.";
|
|
type = lib.types.port;
|
|
};
|
|
enableHTTPS = lib.mkOption {
|
|
description = "Use HTTPS for the tunnel server.";
|
|
type = lib.types.bool;
|
|
default = true;
|
|
};
|
|
};
|
|
};
|
|
default =
|
|
{ config, ... }:
|
|
{
|
|
host = "0.0.0.0";
|
|
port = if config.enableHTTPS then 443 else 80;
|
|
};
|
|
defaultText = lib.literalExpression ''
|
|
{ config, ... }:
|
|
{
|
|
host = "0.0.0.0";
|
|
port = if config.enableHTTPS then 443 else 80;
|
|
}
|
|
'';
|
|
};
|
|
|
|
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";
|
|
};
|
|
|
|
settings = lib.mkOption {
|
|
type = lib.types.submodule {
|
|
freeformType = argsFormat.type;
|
|
|
|
options = {
|
|
restrict-to = lib.mkOption {
|
|
type = lib.types.listOf (
|
|
lib.types.submodule {
|
|
options = {
|
|
host = lib.mkOption {
|
|
description = "The hostname.";
|
|
type = lib.types.str;
|
|
};
|
|
port = lib.mkOption {
|
|
description = "The port.";
|
|
type = lib.types.port;
|
|
};
|
|
};
|
|
}
|
|
);
|
|
default = [ ];
|
|
example = [
|
|
{
|
|
host = "127.0.0.1";
|
|
port = 51820;
|
|
}
|
|
];
|
|
description = ''
|
|
Restrictions on the connections that the server will accept.
|
|
For more flexibility, and the possibility to also allow reverse tunnels,
|
|
look into the `restrict-config` option that takes a path to a yaml file.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
default = { };
|
|
description = ''
|
|
Command line arguments to pass to `wstunnel`.
|
|
Attributes of the form `argName = true;` will be translated to `--argName`,
|
|
and `argName = \"value\"` to `--argName value`.
|
|
'';
|
|
example = {
|
|
"someNewOption" = true;
|
|
"someNewOptionWithValue" = "someValue";
|
|
};
|
|
};
|
|
};
|
|
|
|
config = {
|
|
settings = lib.mkIf (config.useACMEHost != null) {
|
|
tls-certificate = "${certConfig.directory}/fullchain.pem";
|
|
tls-private-key = "${certConfig.directory}/key.pem";
|
|
};
|
|
};
|
|
};
|
|
|
|
clientSubmodule =
|
|
{ config, ... }:
|
|
{
|
|
imports =
|
|
[
|
|
../../misc/assertions.nix
|
|
]
|
|
++ lib.map
|
|
(
|
|
option:
|
|
lib.mkRemovedOptionModule [ option ] ''
|
|
The wstunnel module now uses RFC-42-style settings, please modify your config accordingly
|
|
''
|
|
)
|
|
[
|
|
"extraArgs"
|
|
"websocketPingInterval"
|
|
"loggingLevel"
|
|
|
|
"localToRemote"
|
|
"remoteToLocal"
|
|
"httpProxy"
|
|
"soMark"
|
|
"upgradePathPrefix"
|
|
"tlsSNI"
|
|
"tlsVerifyCertificate"
|
|
"upgradeCredentials"
|
|
"customHeaders"
|
|
];
|
|
|
|
options = commonOptions // {
|
|
connectTo = lib.mkOption {
|
|
description = "Server address and port to connect to.";
|
|
type = lib.types.str;
|
|
example = "https://wstunnel.server.com:8443";
|
|
};
|
|
|
|
addNetBind = lib.mkEnableOption "Whether add CAP_NET_BIND_SERVICE to the tunnel service, this should be enabled if you want to bind port < 1024";
|
|
|
|
settings = lib.mkOption {
|
|
type = lib.types.submodule {
|
|
freeformType = argsFormat.type;
|
|
|
|
options = {
|
|
http-headers = lib.mkOption {
|
|
type = lib.types.coercedTo (lib.types.attrsOf lib.types.str) (lib.mapAttrsToList (
|
|
n: v: "${n}:${v}"
|
|
)) (lib.types.listOf lib.types.str);
|
|
default = { };
|
|
example = {
|
|
"X-Some-Header" = "some-value";
|
|
};
|
|
description = ''
|
|
Custom headers to send in the upgrade request
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
default = { };
|
|
description = ''
|
|
Command line arguments to pass to `wstunnel`.
|
|
Attributes of the form `argName = true;` will be translated to `--argName`,
|
|
and `argName = \"value\"` to `--argName value`.
|
|
'';
|
|
example = {
|
|
"someNewOption" = true;
|
|
"someNewOptionWithValue" = "someValue";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
generateServerUnit = name: serverCfg: {
|
|
name = "wstunnel-server-${name}";
|
|
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 = {
|
|
Type = "exec";
|
|
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";
|
|
|
|
ExecStart =
|
|
let
|
|
convertedSettings = serverCfg.settings // {
|
|
restrict-to = lib.map hostPortToString serverCfg.settings.restrict-to;
|
|
};
|
|
in
|
|
''
|
|
${lib.getExe serverCfg.package} \
|
|
server \
|
|
${argsFormat.generate convertedSettings} \
|
|
${lib.escapeShellArg "${
|
|
if serverCfg.listen.enableHTTPS then "wss" else "ws"
|
|
}://${hostPortToString serverCfg.listen}"}
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
|
|
generateClientUnit = name: clientCfg: {
|
|
name = "wstunnel-client-${name}";
|
|
value = {
|
|
description = "wstunnel client - ${name}";
|
|
requires = [
|
|
"network.target"
|
|
"network-online.target"
|
|
];
|
|
after = [
|
|
"network.target"
|
|
"network-online.target"
|
|
];
|
|
wantedBy = lib.optional clientCfg.autoStart "multi-user.target";
|
|
|
|
serviceConfig = {
|
|
Type = "exec";
|
|
EnvironmentFile = lib.optional (clientCfg.environmentFile != null) clientCfg.environmentFile;
|
|
DynamicUser = true;
|
|
PrivateTmp = true;
|
|
AmbientCapabilities =
|
|
(lib.optionals clientCfg.addNetBind [ "CAP_NET_BIND_SERVICE" ])
|
|
++ (lib.optionals ((clientCfg.settings.socket-so-mark or null) != null) [ "CAP_NET_ADMIN" ]);
|
|
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";
|
|
|
|
ExecStart = ''
|
|
${lib.getExe clientCfg.package} \
|
|
client \
|
|
${argsFormat.generate clientCfg.settings} \
|
|
${lib.escapeShellArg clientCfg.connectTo}
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
in
|
|
{
|
|
options.services.wstunnel = {
|
|
enable = lib.mkEnableOption "wstunnel";
|
|
|
|
servers = lib.mkOption {
|
|
description = "`wstunnel` servers to set up.";
|
|
type = lib.types.attrsOf (lib.types.submodule serverSubmodule);
|
|
default = { };
|
|
example = {
|
|
"wg-tunnel" = {
|
|
listen = {
|
|
host = "0.0.0.0";
|
|
port = 8080;
|
|
enableHTTPS = true;
|
|
};
|
|
settings = {
|
|
tls-certificate = "/var/lib/secrets/fullchain.pem";
|
|
tls-private-key = "/var/lib/secrets/key.pem";
|
|
restrict-to = [
|
|
{
|
|
host = "127.0.0.1";
|
|
port = 51820;
|
|
}
|
|
];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
clients = lib.mkOption {
|
|
description = "`wstunnel` clients to set up.";
|
|
type = lib.types.attrsOf (lib.types.submodule clientSubmodule);
|
|
default = { };
|
|
example = {
|
|
"wg-tunnel" = {
|
|
connectTo = "wss://wstunnel.server.com:8443";
|
|
localToRemote = [
|
|
"tcp://1212:google.com:443"
|
|
"tcp://2:n.lan:4?proxy_protocol"
|
|
];
|
|
remoteToLocal = [
|
|
"socks5://[::1]:1212"
|
|
"unix://wstunnel.sock:g.com:443"
|
|
];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
systemd.services =
|
|
(lib.mapAttrs' generateServerUnit (lib.filterAttrs (_: v: v.enable) cfg.servers))
|
|
// (lib.mapAttrs' generateClientUnit (lib.filterAttrs (_: v: v.enable) cfg.clients));
|
|
|
|
assertions =
|
|
(lib.mapAttrsToList (name: serverCfg: {
|
|
assertion =
|
|
serverCfg.listen.enableHTTPS
|
|
->
|
|
(serverCfg.useACMEHost != null)
|
|
|| (
|
|
(serverCfg.settings.tls-certificate or null) != null
|
|
&& (serverCfg.settings.tls-private-key or null) != null
|
|
);
|
|
message = ''
|
|
If services.wstunnel.servers."${name}".listen.enableHTTPS is set to true, either services.wstunnel.servers."${name}".useACMEHost or both services.wstunnel.servers."${name}".settings.tls-private-key and services.wstunnel.servers."${name}".settings.tls-certificate need to be set.
|
|
'';
|
|
}) cfg.servers)
|
|
++ (lib.foldlAttrs (
|
|
assertions: _: server:
|
|
assertions ++ server.assertions
|
|
) [ ] cfg.servers)
|
|
|
|
++ (lib.mapAttrsToList (
|
|
name: clientCfg:
|
|
let
|
|
isListAttrDefined = settings: attr: (settings.${attr} or [ ]) != [ ];
|
|
in
|
|
{
|
|
assertion =
|
|
isListAttrDefined clientCfg.settings "local-to-remote"
|
|
|| isListAttrDefined clientCfg.settings "remote-to-local";
|
|
message = ''
|
|
Either one of services.wstunnel.clients."${name}".settings.local-to-remote or services.wstunnel.clients."${name}".settings.remote-to-local must be set.
|
|
'';
|
|
}
|
|
) cfg.clients)
|
|
++ (lib.foldlAttrs (
|
|
assertions: _: client:
|
|
assertions ++ client.assertions
|
|
) [ ] cfg.clients);
|
|
|
|
warnings =
|
|
(lib.foldlAttrs (
|
|
warnings: _: server:
|
|
warnings ++ server.warnings
|
|
) [ ] cfg.servers)
|
|
++ (lib.foldlAttrs (
|
|
warnings: _: client:
|
|
warnings ++ client.warnings
|
|
) [ ] cfg.clients);
|
|
};
|
|
|
|
meta.maintainers = with lib.maintainers; [
|
|
pentane
|
|
raylas
|
|
rvdp
|
|
neverbehave
|
|
];
|
|
}
|