Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nixos/sslh: update and refactor for RFC42 #245855

Merged
merged 4 commits into from
Oct 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions nixos/doc/manual/release-notes/rl-2311.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,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.
Expand Down
189 changes: 124 additions & 65 deletions nixos/modules/services/networking/sslh.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
};
};

Expand All @@ -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;
Expand Down
18 changes: 5 additions & 13 deletions nixos/tests/sslh.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 ];
Expand Down
16 changes: 12 additions & 4 deletions pkgs/servers/sslh/default.nix
Original file line number Diff line number Diff line change
@@ -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" ];
Expand Down