1 { config, lib, pkgs, ... }:
6 cfg = config.services.sslh;
8 configFile = pkgs.writeText "sslh.conf" ''
9 verbose: ${boolToString cfg.verbose};
13 transparent: ${boolToString cfg.transparent};
14 timeout: "${toString cfg.timeout}";
19 concatMapStringsSep ",\n"
20 (addr: ''{ host: "${addr}"; port: "${toString cfg.port}"; }'')
27 defaultAppendConfig = ''
30 { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
31 { name: "openvpn"; host: "localhost"; port: "1194"; probe: "builtin"; },
32 { name: "xmpp"; host: "localhost"; port: "5222"; probe: "builtin"; },
33 { name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
34 { name: "tls"; host: "localhost"; port: "443"; probe: "builtin"; },
35 { name: "anyprot"; host: "localhost"; port: "443"; probe: "builtin"; }
41 (mkRenamedOptionModule [ "services" "sslh" "listenAddress" ] [ "services" "sslh" "listenAddresses" ])
46 enable = mkEnableOption (lib.mdDoc "sslh");
51 description = lib.mdDoc "Verbose logs.";
57 description = lib.mdDoc "Timeout in seconds.";
60 transparent = mkOption {
63 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";
66 listenAddresses = mkOption {
67 type = types.coercedTo types.str singleton (types.listOf types.str);
68 default = [ "0.0.0.0" "[::]" ];
69 description = lib.mdDoc "Listening addresses or hostnames.";
75 description = lib.mdDoc "Listening port.";
78 appendConfig = mkOption {
80 default = defaultAppendConfig;
81 description = lib.mdDoc "Verbatim configuration file.";
88 systemd.services.sslh = {
89 description = "Applicative Protocol Multiplexer (e.g. share SSH and HTTPS on the same port)";
90 after = [ "network.target" ];
91 wantedBy = [ "multi-user.target" ];
96 PermissionsStartOnly = true;
99 ExecStart = "${pkgs.sslh}/bin/sslh -F${configFile}";
100 KillMode = "process";
101 AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID";
103 PrivateDevices = true;
104 ProtectSystem = "full";
110 # code from https://github.com/yrutschle/sslh#transparent-proxy-support
111 # the only difference is using iptables mark 0x2 instead of 0x1 to avoid conflicts with nixos/nat module
112 (mkIf (cfg.enable && cfg.transparent) {
113 # Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
114 boot.kernel.sysctl."net.ipv4.conf.default.route_localnet" = 1;
115 boot.kernel.sysctl."net.ipv4.conf.all.route_localnet" = 1;
117 systemd.services.sslh = let
119 # DROP martian packets as they would have been if route_localnet was zero
120 # Note: packets not leaving the server aren't affected by this, thus sslh will still work
121 { table = "raw"; command = "PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP"; }
122 { table = "mangle"; command = "POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP"; }
123 # Mark all connections made by ssl for special treatment (here sslh is run as user ${user})
124 { table = "nat"; command = "OUTPUT -m owner --uid-owner ${user} -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x02/0x0f"; }
125 # Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
126 { table = "mangle"; command = "OUTPUT ! -o lo -p tcp -m connmark --mark 0x02/0x0f -j CONNMARK --restore-mark --mask 0x0f"; }
128 ip6tablesCommands = [
129 { table = "raw"; command = "PREROUTING ! -i lo -d ::1/128 -j DROP"; }
130 { table = "mangle"; command = "POSTROUTING ! -o lo -s ::1/128 -j DROP"; }
131 { table = "nat"; command = "OUTPUT -m owner --uid-owner ${user} -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x02/0x0f"; }
132 { table = "mangle"; command = "OUTPUT ! -o lo -p tcp -m connmark --mark 0x02/0x0f -j CONNMARK --restore-mark --mask 0x0f"; }
135 path = [ pkgs.iptables pkgs.iproute2 pkgs.procps ];
138 # Cleanup old iptables entries which might be still there
139 ${concatMapStringsSep "\n" ({table, command}: "while iptables -w -t ${table} -D ${command} 2>/dev/null; do echo; done") iptablesCommands}
140 ${concatMapStringsSep "\n" ({table, command}: "iptables -w -t ${table} -A ${command}" ) iptablesCommands}
142 # Configure routing for those marked packets
143 ip rule add fwmark 0x2 lookup 100
144 ip route add local 0.0.0.0/0 dev lo table 100
146 '' + optionalString config.networking.enableIPv6 ''
147 ${concatMapStringsSep "\n" ({table, command}: "while ip6tables -w -t ${table} -D ${command} 2>/dev/null; do echo; done") ip6tablesCommands}
148 ${concatMapStringsSep "\n" ({table, command}: "ip6tables -w -t ${table} -A ${command}" ) ip6tablesCommands}
150 ip -6 rule add fwmark 0x2 lookup 100
151 ip -6 route add local ::/0 dev lo table 100
155 ${concatMapStringsSep "\n" ({table, command}: "iptables -w -t ${table} -D ${command}") iptablesCommands}
157 ip rule del fwmark 0x2 lookup 100
158 ip route del local 0.0.0.0/0 dev lo table 100
159 '' + optionalString config.networking.enableIPv6 ''
160 ${concatMapStringsSep "\n" ({table, command}: "ip6tables -w -t ${table} -D ${command}") ip6tablesCommands}
162 ip -6 rule del fwmark 0x2 lookup 100
163 ip -6 route del local ::/0 dev lo table 100