nixos/sslh: refactor for RFC42

This commit is contained in:
rnhmjoj 2023-07-25 08:19:43 +02:00
parent edb4422d73
commit 3728338d40
No known key found for this signature in database
GPG Key ID: BFBAF4C975F76450
2 changed files with 120 additions and 77 deletions

View File

@ -5,81 +5,123 @@ with lib;
let let
cfg = config.services.sslh; cfg = config.services.sslh;
user = "sslh"; user = "sslh";
configFile = pkgs.writeText "sslh.conf" ''
verbose: ${boolToString cfg.verbose};
foreground: true;
inetd: false;
numeric: false;
transparent: ${boolToString cfg.transparent};
timeout: "${toString cfg.timeout}";
listen: configFormat = pkgs.formats.libconfig {};
( configFile = configFormat.generate "sslh.conf" cfg.settings;
${
concatMapStringsSep ",\n"
(addr: ''{ host: "${addr}"; port: "${toString cfg.port}"; }'')
cfg.listenAddresses
}
);
${cfg.appendConfig}
'';
defaultAppendConfig = ''
protocols:
(
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
{ name: "openvpn"; host: "localhost"; port: "1194"; probe: "builtin"; },
{ name: "xmpp"; host: "localhost"; port: "5222"; probe: "builtin"; },
{ name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
{ name: "tls"; host: "localhost"; port: "443"; probe: "builtin"; },
{ name: "anyprot"; host: "localhost"; port: "443"; probe: "builtin"; }
);
'';
in in
{ {
imports = [ imports = [
(mkRenamedOptionModule [ "services" "sslh" "listenAddress" ] [ "services" "sslh" "listenAddresses" ]) (mkRenamedOptionModule [ "services" "sslh" "listenAddress" ] [ "services" "sslh" "listenAddresses" ])
(mkRenamedOptionModule [ "services" "sslh" "timeout" ] [ "services" "sslh" "settings" "timeout" ])
(mkRenamedOptionModule [ "services" "sslh" "transparent" ] [ "services" "sslh" "settings" "transparent" ])
(mkRemovedOptionModule [ "services" "sslh" "appendConfig" ] "Use services.sslh.settings instead")
(mkChangedOptionModule [ "services" "sslh" "verbose" ] [ "services" "sslh" "settings" "verbose" ]
(verbose: if verbose then 1 else 0))
]; ];
options = { meta.buildDocsInSandbox = false;
services.sslh = {
enable = mkEnableOption (lib.mdDoc "sslh");
verbose = mkOption { options.services.sslh = {
type = types.bool; enable = mkEnableOption (lib.mdDoc "sslh, protocol demultiplexer");
default = false;
description = lib.mdDoc "Verbose logs.";
};
timeout = mkOption { method = mkOption {
type = types.int; type = types.enum [ "fork" "select" ];
default = 2; default = "fork";
description = lib.mdDoc "Timeout in seconds."; description = lib.mdDoc ''
}; The method to use for handling connections:
transparent = mkOption { - `fork` forks a new process for each incoming connection. It is
type = types.bool; well-tested and very reliable, but incurs the overhead of many
default = false; processes.
description = lib.mdDoc "Will the services behind sslh (Apache, sshd and so on) see the external IP and ports as if the external world connected directly to them";
};
listenAddresses = mkOption { - `select` uses only one thread, which monitors all connections at once.
type = types.coercedTo types.str singleton (types.listOf types.str); It has lower overhead per connection, but if it stops, you'll lose all
default = [ "0.0.0.0" "[::]" ]; connections.
description = lib.mdDoc "Listening addresses or hostnames."; '';
}; };
port = mkOption { listenAddresses = mkOption {
type = types.port; type = with types; coercedTo str singleton (listOf str);
default = 443; default = [ "0.0.0.0" "[::]" ];
description = lib.mdDoc "Listening port."; description = lib.mdDoc "Listening addresses or hostnames.";
}; };
appendConfig = mkOption { port = mkOption {
type = types.str; type = types.port;
default = defaultAppendConfig; default = 443;
description = lib.mdDoc "Verbatim configuration file."; description = lib.mdDoc "Listening port.";
};
settings = mkOption {
type = types.submodule {
freeformType = configFormat.type;
options.verbose = mkOption {
type = types.int;
default = 0;
example = 3;
description = lib.mdDoc ''
Logging verbosity: higher values for more information.
'';
};
options.timeout = mkOption {
type = types.ints.unsigned;
default = 2;
description = lib.mdDoc "Timeout in seconds.";
};
options.transparent = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Whether the services behind sslh (Apache, sshd and so on) will see the
external IP and ports as if the external world connected directly to
them.
'';
};
options.numeric = mkOption {
type = types.bool;
default = true;
description = lib.mdDoc ''
Whether to disable reverse DNS lookups, thus keeping IP
address literals in the log.
'';
};
options.protocols = mkOption {
type = types.listOf configFormat.type;
default = [
{ name = "ssh"; host = "localhost"; port = "22"; service= "ssh"; }
{ name = "openvpn"; host = "localhost"; port = "1194"; }
{ name = "xmpp"; host = "localhost"; port = "5222"; }
{ name = "http"; host = "localhost"; port = "80"; }
{ name = "tls"; host = "localhost"; port = "443"; }
{ name = "anyprot"; host = "localhost"; port = "443"; }
];
description = lib.mdDoc ''
List of protocols sslh will probe for and redirect.
Each protocol entry consists of:
- `name`: name of the probe.
- `service`: libwrap service name (see {manpage}`hosts_access(5)`),
- `host`, `port`: where to connect when this probe succeeds,
- `log_level`: to log incoming connections,
- `transparent`: proxy this protocol transparently,
- etc.
See the documentation for all options, including probe-specific ones.
'';
};
}; };
description = lib.mdDoc "sslh configuration. See {manpage}`sslh(8)` for available settings.";
}; };
}; };
@ -96,20 +138,29 @@ in
PermissionsStartOnly = true; PermissionsStartOnly = true;
Restart = "always"; Restart = "always";
RestartSec = "1s"; RestartSec = "1s";
ExecStart = "${pkgs.sslh}/bin/sslh -F${configFile}"; ExecStart = "${pkgs.sslh}/bin/sslh-${cfg.method} -F${configFile}";
KillMode = "process"; KillMode = "process";
AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID"; AmbientCapabilities = ["CAP_NET_BIND_SERVICE" "CAP_NET_ADMIN" "CAP_SETGID" "CAP_SETUID"];
PrivateTmp = true; PrivateTmp = true;
PrivateDevices = true; PrivateDevices = true;
ProtectSystem = "full"; ProtectSystem = "full";
ProtectHome = true; ProtectHome = true;
}; };
}; };
services.sslh.settings = {
# Settings defined here are not supposed to be changed: doing so will
# break the module, as such you need `lib.mkForce` to override them.
foreground = true;
inetd = false;
listen = map (addr: { host = addr; port = toString cfg.port; }) cfg.listenAddresses;
};
}) })
# code from https://github.com/yrutschle/sslh#transparent-proxy-support # code from https://github.com/yrutschle/sslh#transparent-proxy-support
# the only difference is using iptables mark 0x2 instead of 0x1 to avoid conflicts with nixos/nat module # the only difference is using iptables mark 0x2 instead of 0x1 to avoid conflicts with nixos/nat module
(mkIf (cfg.enable && cfg.transparent) { (mkIf (cfg.enable && cfg.settings.transparent) {
# Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination # Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
boot.kernel.sysctl."net.ipv4.conf.default.route_localnet" = 1; boot.kernel.sysctl."net.ipv4.conf.default.route_localnet" = 1;
boot.kernel.sysctl."net.ipv4.conf.all.route_localnet" = 1; boot.kernel.sysctl."net.ipv4.conf.all.route_localnet" = 1;

View File

@ -10,21 +10,13 @@ import ./make-test-python.nix {
prefixLength = 64; prefixLength = 64;
} }
]; ];
# sslh is really slow when reverse dns does not work
networking.hosts = {
"fe00:aa:bb:cc::2" = [ "server" ];
"fe00:aa:bb:cc::1" = [ "client" ];
};
services.sslh = { services.sslh = {
enable = true; enable = true;
transparent = true; settings.transparent = true;
appendConfig = '' settings.protocols = [
protocols: { name = "ssh"; service = "ssh"; host = "localhost"; port = "22"; probe = "builtin"; }
( { name = "http"; host = "localhost"; port = "80"; probe = "builtin"; }
{ name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; }, ];
{ name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
);
'';
}; };
services.openssh.enable = true; services.openssh.enable = true;
users.users.root.openssh.authorizedKeys.keyFiles = [ ./initrd-network-ssh/id_ed25519.pub ]; users.users.root.openssh.authorizedKeys.keyFiles = [ ./initrd-network-ssh/id_ed25519.pub ];