at 24.11-pre 9.4 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.sslh; 7 user = "sslh"; 8 9 configFormat = pkgs.formats.libconfig {}; 10 configFile = configFormat.generate "sslh.conf" cfg.settings; 11in 12 13{ 14 imports = [ 15 (mkRenamedOptionModule [ "services" "sslh" "listenAddress" ] [ "services" "sslh" "listenAddresses" ]) 16 (mkRenamedOptionModule [ "services" "sslh" "timeout" ] [ "services" "sslh" "settings" "timeout" ]) 17 (mkRenamedOptionModule [ "services" "sslh" "transparent" ] [ "services" "sslh" "settings" "transparent" ]) 18 (mkRemovedOptionModule [ "services" "sslh" "appendConfig" ] "Use services.sslh.settings instead") 19 (mkChangedOptionModule [ "services" "sslh" "verbose" ] [ "services" "sslh" "settings" "verbose-connections" ] 20 (config: if config.services.sslh.verbose then 1 else 0)) 21 ]; 22 23 meta.buildDocsInSandbox = false; 24 25 options.services.sslh = { 26 enable = mkEnableOption "sslh, protocol demultiplexer"; 27 28 method = mkOption { 29 type = types.enum [ "fork" "select" "ev" ]; 30 default = "fork"; 31 description = '' 32 The method to use for handling connections: 33 34 - `fork` forks a new process for each incoming connection. It is 35 well-tested and very reliable, but incurs the overhead of many 36 processes. 37 38 - `select` uses only one thread, which monitors all connections at once. 39 It has lower overhead per connection, but if it stops, you'll lose all 40 connections. 41 42 - `ev` is implemented using libev, it's similar to `select` but 43 scales better to a large number of connections. 44 ''; 45 }; 46 47 listenAddresses = mkOption { 48 type = with types; coercedTo str singleton (listOf str); 49 default = [ "0.0.0.0" "[::]" ]; 50 description = "Listening addresses or hostnames."; 51 }; 52 53 port = mkOption { 54 type = types.port; 55 default = 443; 56 description = "Listening port."; 57 }; 58 59 settings = mkOption { 60 type = types.submodule { 61 freeformType = configFormat.type; 62 63 options.timeout = mkOption { 64 type = types.ints.unsigned; 65 default = 2; 66 description = "Timeout in seconds."; 67 }; 68 69 options.transparent = mkOption { 70 type = types.bool; 71 default = false; 72 description = '' 73 Whether the services behind sslh (Apache, sshd and so on) will see the 74 external IP and ports as if the external world connected directly to 75 them. 76 ''; 77 }; 78 79 options.verbose-connections = mkOption { 80 type = types.ints.between 0 4; 81 default = 0; 82 description = '' 83 Where to log connections information. Possible values are: 84 85 0. don't log anything 86 1. write log to stdout 87 2. write log to syslog 88 3. write log to both stdout and syslog 89 4. write to a log file ({option}`sslh.settings.logfile`) 90 ''; 91 }; 92 93 options.numeric = mkOption { 94 type = types.bool; 95 default = true; 96 description = '' 97 Whether to disable reverse DNS lookups, thus keeping IP 98 address literals in the log. 99 ''; 100 }; 101 102 options.protocols = mkOption { 103 type = types.listOf configFormat.type; 104 default = [ 105 { name = "ssh"; host = "localhost"; port = "22"; service= "ssh"; } 106 { name = "openvpn"; host = "localhost"; port = "1194"; } 107 { name = "xmpp"; host = "localhost"; port = "5222"; } 108 { name = "http"; host = "localhost"; port = "80"; } 109 { name = "tls"; host = "localhost"; port = "443"; } 110 { name = "anyprot"; host = "localhost"; port = "443"; } 111 ]; 112 description = '' 113 List of protocols sslh will probe for and redirect. 114 Each protocol entry consists of: 115 116 - `name`: name of the probe. 117 118 - `service`: libwrap service name (see {manpage}`hosts_access(5)`), 119 120 - `host`, `port`: where to connect when this probe succeeds, 121 122 - `log_level`: to log incoming connections, 123 124 - `transparent`: proxy this protocol transparently, 125 126 - etc. 127 128 See the documentation for all options, including probe-specific ones. 129 ''; 130 }; 131 }; 132 description = "sslh configuration. See {manpage}`sslh(8)` for available settings."; 133 }; 134 }; 135 136 config = mkMerge [ 137 (mkIf cfg.enable { 138 systemd.services.sslh = { 139 description = "Applicative Protocol Multiplexer (e.g. share SSH and HTTPS on the same port)"; 140 after = [ "network.target" ]; 141 wantedBy = [ "multi-user.target" ]; 142 143 serviceConfig = { 144 DynamicUser = true; 145 User = "sslh"; 146 PermissionsStartOnly = true; 147 Restart = "always"; 148 RestartSec = "1s"; 149 ExecStart = "${pkgs.sslh}/bin/sslh-${cfg.method} -F${configFile}"; 150 KillMode = "process"; 151 AmbientCapabilities = ["CAP_NET_BIND_SERVICE" "CAP_NET_ADMIN" "CAP_SETGID" "CAP_SETUID"]; 152 PrivateTmp = true; 153 PrivateDevices = true; 154 ProtectSystem = "full"; 155 ProtectHome = true; 156 }; 157 }; 158 159 services.sslh.settings = { 160 # Settings defined here are not supposed to be changed: doing so will 161 # break the module, as such you need `lib.mkForce` to override them. 162 foreground = true; 163 inetd = false; 164 listen = map (addr: { host = addr; port = toString cfg.port; }) cfg.listenAddresses; 165 }; 166 167 }) 168 169 # code from https://github.com/yrutschle/sslh#transparent-proxy-support 170 # the only difference is using iptables mark 0x2 instead of 0x1 to avoid conflicts with nixos/nat module 171 (mkIf (cfg.enable && cfg.settings.transparent) { 172 # Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination 173 boot.kernel.sysctl."net.ipv4.conf.default.route_localnet" = 1; 174 boot.kernel.sysctl."net.ipv4.conf.all.route_localnet" = 1; 175 176 systemd.services.sslh = let 177 iptablesCommands = [ 178 # DROP martian packets as they would have been if route_localnet was zero 179 # Note: packets not leaving the server aren't affected by this, thus sslh will still work 180 { table = "raw"; command = "PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP"; } 181 { table = "mangle"; command = "POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP"; } 182 # Mark all connections made by ssl for special treatment (here sslh is run as user ${user}) 183 { table = "nat"; command = "OUTPUT -m owner --uid-owner ${user} -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x02/0x0f"; } 184 # Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark) 185 { table = "mangle"; command = "OUTPUT ! -o lo -p tcp -m connmark --mark 0x02/0x0f -j CONNMARK --restore-mark --mask 0x0f"; } 186 ]; 187 ip6tablesCommands = [ 188 { table = "raw"; command = "PREROUTING ! -i lo -d ::1/128 -j DROP"; } 189 { table = "mangle"; command = "POSTROUTING ! -o lo -s ::1/128 -j DROP"; } 190 { table = "nat"; command = "OUTPUT -m owner --uid-owner ${user} -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x02/0x0f"; } 191 { table = "mangle"; command = "OUTPUT ! -o lo -p tcp -m connmark --mark 0x02/0x0f -j CONNMARK --restore-mark --mask 0x0f"; } 192 ]; 193 in { 194 path = [ pkgs.iptables pkgs.iproute2 pkgs.procps ]; 195 196 preStart = '' 197 # Cleanup old iptables entries which might be still there 198 ${concatMapStringsSep "\n" ({table, command}: "while iptables -w -t ${table} -D ${command} 2>/dev/null; do echo; done") iptablesCommands} 199 ${concatMapStringsSep "\n" ({table, command}: "iptables -w -t ${table} -A ${command}" ) iptablesCommands} 200 201 # Configure routing for those marked packets 202 ip rule add fwmark 0x2 lookup 100 203 ip route add local 0.0.0.0/0 dev lo table 100 204 205 '' + optionalString config.networking.enableIPv6 '' 206 ${concatMapStringsSep "\n" ({table, command}: "while ip6tables -w -t ${table} -D ${command} 2>/dev/null; do echo; done") ip6tablesCommands} 207 ${concatMapStringsSep "\n" ({table, command}: "ip6tables -w -t ${table} -A ${command}" ) ip6tablesCommands} 208 209 ip -6 rule add fwmark 0x2 lookup 100 210 ip -6 route add local ::/0 dev lo table 100 211 ''; 212 213 postStop = '' 214 ${concatMapStringsSep "\n" ({table, command}: "iptables -w -t ${table} -D ${command}") iptablesCommands} 215 216 ip rule del fwmark 0x2 lookup 100 217 ip route del local 0.0.0.0/0 dev lo table 100 218 '' + optionalString config.networking.enableIPv6 '' 219 ${concatMapStringsSep "\n" ({table, command}: "ip6tables -w -t ${table} -D ${command}") ip6tablesCommands} 220 221 ip -6 rule del fwmark 0x2 lookup 100 222 ip -6 route del local ::/0 dev lo table 100 223 ''; 224 }; 225 }) 226 ]; 227}