diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md index e7eeca2af3e91..74d29ab1cf9ca 100644 --- a/nixos/doc/manual/release-notes/rl-2311.section.md +++ b/nixos/doc/manual/release-notes/rl-2311.section.md @@ -396,6 +396,9 @@ - The `fonts.fonts` and `fonts.enableDefaultFonts` options have been renamed to `fonts.packages` and `fonts.enableDefaultPackages` respectively. +- The `services.sslh` module has been updated to follow [RFC 0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md). As such, several options have been moved to the freeform attribute set [services.sslh.settings](#opt-services.sslh.settings), which allows to change any of the settings in {manpage}`sslh(8)`. + In addition, the newly added option [services.sslh.method](#opt-services.sslh.method) allows to switch between the {manpage}`fork(2)`, {manpage}`select(2)` and `libev`-based connection handling method; see the [sslh docs](https://github.com/yrutschle/sslh/blob/master/doc/INSTALL.md#binaries) for a comparison. + - `pkgs.openvpn3` now optionally supports systemd-resolved. `programs.openvpn3` will automatically enable systemd-resolved support if `config.services.resolved.enable` is enabled. - `services.fail2ban.jails` can now be configured with attribute sets defining settings and filters instead of lines. The stringed options `daemonConfig` and `extraSettings` have respectively been replaced by `daemonSettings` and `jails.DEFAULT.settings` which use attribute sets. diff --git a/nixos/modules/services/networking/sslh.nix b/nixos/modules/services/networking/sslh.nix index daf2f2f3668ee..dd29db510020a 100644 --- a/nixos/modules/services/networking/sslh.nix +++ b/nixos/modules/services/networking/sslh.nix @@ -5,81 +5,131 @@ with lib; let cfg = config.services.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: - ( - ${ - 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"; } - ); - ''; + + configFormat = pkgs.formats.libconfig {}; + configFile = configFormat.generate "sslh.conf" cfg.settings; in + { imports = [ (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-connections" ] + (config: if config.services.sslh.verbose then 1 else 0)) ]; - options = { - services.sslh = { - enable = mkEnableOption (lib.mdDoc "sslh"); + meta.buildDocsInSandbox = false; - verbose = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc "Verbose logs."; - }; + options.services.sslh = { + enable = mkEnableOption (lib.mdDoc "sslh, protocol demultiplexer"); - timeout = mkOption { - type = types.int; - default = 2; - description = lib.mdDoc "Timeout in seconds."; - }; + method = mkOption { + type = types.enum [ "fork" "select" "ev" ]; + default = "fork"; + description = lib.mdDoc '' + The method to use for handling connections: - transparent = mkOption { - type = types.bool; - default = false; - 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"; - }; + - `fork` forks a new process for each incoming connection. It is + well-tested and very reliable, but incurs the overhead of many + processes. - listenAddresses = mkOption { - type = types.coercedTo types.str singleton (types.listOf types.str); - default = [ "0.0.0.0" "[::]" ]; - description = lib.mdDoc "Listening addresses or hostnames."; - }; + - `select` uses only one thread, which monitors all connections at once. + It has lower overhead per connection, but if it stops, you'll lose all + connections. - port = mkOption { - type = types.port; - default = 443; - description = lib.mdDoc "Listening port."; - }; + - `ev` is implemented using libev, it's similar to `select` but + scales better to a large number of connections. + ''; + }; + + listenAddresses = mkOption { + type = with types; coercedTo str singleton (listOf str); + default = [ "0.0.0.0" "[::]" ]; + description = lib.mdDoc "Listening addresses or hostnames."; + }; + + port = mkOption { + type = types.port; + default = 443; + description = lib.mdDoc "Listening port."; + }; + + settings = mkOption { + type = types.submodule { + freeformType = configFormat.type; + + 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.verbose-connections = mkOption { + type = types.ints.between 0 4; + default = 0; + description = lib.mdDoc '' + Where to log connections information. Possible values are: + + 0. don't log anything + 1. write log to stdout + 2. write log to syslog + 3. write log to both stdout and syslog + 4. write to a log file ({option}`sslh.settings.logfile`) + ''; + }; + + 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)`), - appendConfig = mkOption { - type = types.str; - default = defaultAppendConfig; - description = lib.mdDoc "Verbatim configuration file."; + - `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 +146,29 @@ in PermissionsStartOnly = true; Restart = "always"; RestartSec = "1s"; - ExecStart = "${pkgs.sslh}/bin/sslh -F${configFile}"; + ExecStart = "${pkgs.sslh}/bin/sslh-${cfg.method} -F${configFile}"; 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; PrivateDevices = true; ProtectSystem = "full"; 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 # 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 boot.kernel.sysctl."net.ipv4.conf.default.route_localnet" = 1; boot.kernel.sysctl."net.ipv4.conf.all.route_localnet" = 1; diff --git a/nixos/tests/sslh.nix b/nixos/tests/sslh.nix index 17094606e8e6b..30ffd389d4422 100644 --- a/nixos/tests/sslh.nix +++ b/nixos/tests/sslh.nix @@ -10,21 +10,13 @@ import ./make-test-python.nix { 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 = { enable = true; - transparent = true; - appendConfig = '' - protocols: - ( - { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; }, - { name: "http"; host: "localhost"; port: "80"; probe: "builtin"; }, - ); - ''; + settings.transparent = true; + settings.protocols = [ + { name = "ssh"; service = "ssh"; host = "localhost"; port = "22"; probe = "builtin"; } + { name = "http"; host = "localhost"; port = "80"; probe = "builtin"; } + ]; }; services.openssh.enable = true; users.users.root.openssh.authorizedKeys.keyFiles = [ ./initrd-network-ssh/id_ed25519.pub ]; diff --git a/pkgs/servers/sslh/default.nix b/pkgs/servers/sslh/default.nix index 6add47454c2f6..97ec06306fc4a 100644 --- a/pkgs/servers/sslh/default.nix +++ b/pkgs/servers/sslh/default.nix @@ -1,22 +1,30 @@ -{ lib, stdenv, fetchFromGitHub, libcap, libconfig, perl, tcp_wrappers, pcre2, nixosTests }: +{ lib, stdenv, fetchFromGitHub, fetchpatch, libcap, libev, libconfig, perl, tcp_wrappers, pcre2, nixosTests }: stdenv.mkDerivation rec { pname = "sslh"; - version = "1.22c"; + version = "2.0.0"; src = fetchFromGitHub { owner = "yrutschle"; repo = pname; rev = "v${version}"; - sha256 = "sha256-A+nUWiOPoz/T5afZUzt5In01e049TgHisTF8P5Vj180="; + hash = "sha256-KfNQWSmAf86AFoInKlNZoiSuSwVLaJVnfo7SjZVY/VU="; }; postPatch = "patchShebangs *.sh"; - buildInputs = [ libcap libconfig perl tcp_wrappers pcre2 ]; + buildInputs = [ libcap libev libconfig perl tcp_wrappers pcre2 ]; makeFlags = [ "USELIBCAP=1" "USELIBWRAP=1" ]; + postInstall = '' + # install all flavours + install -p sslh-fork "$out/sbin/sslh-fork" + install -p sslh-select "$out/sbin/sslh-select" + install -p sslh-ev "$out/sbin/sslh-ev" + ln -sf sslh-fork "$out/sbin/sslh" + ''; + installFlags = [ "PREFIX=$(out)" ]; hardeningDisable = [ "format" ];