nixpkgs/nixos/modules/services/networking/sslh.nix
Silvan Mosberger 374e6bcc40 treewide: Format all Nix files
Format all Nix files using the officially approved formatter,
making the CI check introduced in the previous commit succeed:

  nix-build ci -A fmt.check

This is the next step of the of the [implementation](https://github.com/NixOS/nixfmt/issues/153)
of the accepted [RFC 166](https://github.com/NixOS/rfcs/pull/166).

This commit will lead to merge conflicts for a number of PRs,
up to an estimated ~1100 (~33%) among the PRs with activity in the past 2
months, but that should be lower than what it would be without the previous
[partial treewide format](https://github.com/NixOS/nixpkgs/pull/322537).

Merge conflicts caused by this commit can now automatically be resolved while rebasing using the
[auto-rebase script](8616af08d9/maintainers/scripts/auto-rebase).

If you run into any problems regarding any of this, please reach out to the
[formatting team](https://nixos.org/community/teams/formatting/) by
pinging @NixOS/nix-formatting.
2025-04-01 20:10:43 +02:00

327 lines
10 KiB
Nix

{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.services.sslh;
user = "sslh";
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)
)
];
meta.buildDocsInSandbox = false;
options.services.sslh = {
enable = mkEnableOption "sslh, protocol demultiplexer";
method = mkOption {
type = types.enum [
"fork"
"select"
"ev"
];
default = "fork";
description = ''
The method to use for handling connections:
- `fork` forks a new process for each incoming connection. It is
well-tested and very reliable, but incurs the overhead of many
processes.
- `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.
- `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 = "Listening addresses or hostnames.";
};
port = mkOption {
type = types.port;
default = 443;
description = "Listening port.";
};
settings = mkOption {
type = types.submodule {
freeformType = configFormat.type;
options.timeout = mkOption {
type = types.ints.unsigned;
default = 2;
description = "Timeout in seconds.";
};
options.transparent = mkOption {
type = types.bool;
default = false;
description = ''
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 = ''
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 = ''
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 = ''
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 = "sslh configuration. See {manpage}`sslh(8)` for available settings.";
};
};
config = mkMerge [
(mkIf cfg.enable {
systemd.services.sslh = {
description = "Applicative Protocol Multiplexer (e.g. share SSH and HTTPS on the same port)";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
DynamicUser = true;
User = "sslh";
PermissionsStartOnly = true;
Restart = "always";
RestartSec = "1s";
ExecStart = "${pkgs.sslh}/bin/sslh-${cfg.method} -F${configFile}";
KillMode = "process";
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.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;
systemd.services.sslh =
let
iptablesCommands = [
# DROP martian packets as they would have been if route_localnet was zero
# Note: packets not leaving the server aren't affected by this, thus sslh will still work
{
table = "raw";
command = "PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP";
}
{
table = "mangle";
command = "POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP";
}
# Mark all connections made by ssl for special treatment (here sslh is run as user ${user})
{
table = "nat";
command = "OUTPUT -m owner --uid-owner ${user} -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x02/0x0f";
}
# Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
{
table = "mangle";
command = "OUTPUT ! -o lo -p tcp -m connmark --mark 0x02/0x0f -j CONNMARK --restore-mark --mask 0x0f";
}
];
ip6tablesCommands = [
{
table = "raw";
command = "PREROUTING ! -i lo -d ::1/128 -j DROP";
}
{
table = "mangle";
command = "POSTROUTING ! -o lo -s ::1/128 -j DROP";
}
{
table = "nat";
command = "OUTPUT -m owner --uid-owner ${user} -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x02/0x0f";
}
{
table = "mangle";
command = "OUTPUT ! -o lo -p tcp -m connmark --mark 0x02/0x0f -j CONNMARK --restore-mark --mask 0x0f";
}
];
in
{
path = [
pkgs.iptables
pkgs.iproute2
pkgs.procps
];
preStart =
''
# Cleanup old iptables entries which might be still there
${concatMapStringsSep "\n" (
{ table, command }: "while iptables -w -t ${table} -D ${command} 2>/dev/null; do echo; done"
) iptablesCommands}
${concatMapStringsSep "\n" (
{ table, command }: "iptables -w -t ${table} -A ${command}"
) iptablesCommands}
# Configure routing for those marked packets
ip rule add fwmark 0x2 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
''
+ optionalString config.networking.enableIPv6 ''
${concatMapStringsSep "\n" (
{ table, command }: "while ip6tables -w -t ${table} -D ${command} 2>/dev/null; do echo; done"
) ip6tablesCommands}
${concatMapStringsSep "\n" (
{ table, command }: "ip6tables -w -t ${table} -A ${command}"
) ip6tablesCommands}
ip -6 rule add fwmark 0x2 lookup 100
ip -6 route add local ::/0 dev lo table 100
'';
postStop =
''
${concatMapStringsSep "\n" (
{ table, command }: "iptables -w -t ${table} -D ${command}"
) iptablesCommands}
ip rule del fwmark 0x2 lookup 100
ip route del local 0.0.0.0/0 dev lo table 100
''
+ optionalString config.networking.enableIPv6 ''
${concatMapStringsSep "\n" (
{ table, command }: "ip6tables -w -t ${table} -D ${command}"
) ip6tablesCommands}
ip -6 rule del fwmark 0x2 lookup 100
ip -6 route del local ::/0 dev lo table 100
'';
};
})
];
}