1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.sslh;
7 user = "sslh";
8 configFile = pkgs.writeText "sslh.conf" ''
9 verbose: ${boolToString cfg.verbose};
10 foreground: true;
11 inetd: false;
12 numeric: false;
13 transparent: ${boolToString cfg.transparent};
14 timeout: "${toString cfg.timeout}";
15
16 listen:
17 (
18 ${
19 concatMapStringsSep ",\n"
20 (addr: ''{ host: "${addr}"; port: "${toString cfg.port}"; }'')
21 cfg.listenAddresses
22 }
23 );
24
25 ${cfg.appendConfig}
26 '';
27 defaultAppendConfig = ''
28 protocols:
29 (
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"; }
36 );
37 '';
38in
39{
40 imports = [
41 (mkRenamedOptionModule [ "services" "sslh" "listenAddress" ] [ "services" "sslh" "listenAddresses" ])
42 ];
43
44 options = {
45 services.sslh = {
46 enable = mkEnableOption (lib.mdDoc "sslh");
47
48 verbose = mkOption {
49 type = types.bool;
50 default = false;
51 description = lib.mdDoc "Verbose logs.";
52 };
53
54 timeout = mkOption {
55 type = types.int;
56 default = 2;
57 description = lib.mdDoc "Timeout in seconds.";
58 };
59
60 transparent = mkOption {
61 type = types.bool;
62 default = false;
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";
64 };
65
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.";
70 };
71
72 port = mkOption {
73 type = types.port;
74 default = 443;
75 description = lib.mdDoc "Listening port.";
76 };
77
78 appendConfig = mkOption {
79 type = types.str;
80 default = defaultAppendConfig;
81 description = lib.mdDoc "Verbatim configuration file.";
82 };
83 };
84 };
85
86 config = mkMerge [
87 (mkIf cfg.enable {
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" ];
92
93 serviceConfig = {
94 DynamicUser = true;
95 User = "sslh";
96 PermissionsStartOnly = true;
97 Restart = "always";
98 RestartSec = "1s";
99 ExecStart = "${pkgs.sslh}/bin/sslh -F${configFile}";
100 KillMode = "process";
101 AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID";
102 PrivateTmp = true;
103 PrivateDevices = true;
104 ProtectSystem = "full";
105 ProtectHome = true;
106 };
107 };
108 })
109
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;
116
117 systemd.services.sslh = let
118 iptablesCommands = [
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"; }
127 ];
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"; }
133 ];
134 in {
135 path = [ pkgs.iptables pkgs.iproute2 pkgs.procps ];
136
137 preStart = ''
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}
141
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
145
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}
149
150 ip -6 rule add fwmark 0x2 lookup 100
151 ip -6 route add local ::/0 dev lo table 100
152 '';
153
154 postStop = ''
155 ${concatMapStringsSep "\n" ({table, command}: "iptables -w -t ${table} -D ${command}") iptablesCommands}
156
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}
161
162 ip -6 rule del fwmark 0x2 lookup 100
163 ip -6 route del local ::/0 dev lo table 100
164 '';
165 };
166 })
167 ];
168}