at 18.09-beta 6.6 kB view raw
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 { host: "${cfg.listenAddress}"; port: "${toString cfg.port}"; } 19 ); 20 21 ${cfg.appendConfig} 22 ''; 23 defaultAppendConfig = '' 24 protocols: 25 ( 26 { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; }, 27 { name: "openvpn"; host: "localhost"; port: "1194"; probe: "builtin"; }, 28 { name: "xmpp"; host: "localhost"; port: "5222"; probe: "builtin"; }, 29 { name: "http"; host: "localhost"; port: "80"; probe: "builtin"; }, 30 { name: "ssl"; host: "localhost"; port: "443"; probe: "builtin"; }, 31 { name: "anyprot"; host: "localhost"; port: "443"; probe: "builtin"; } 32 ); 33 ''; 34in 35{ 36 options = { 37 services.sslh = { 38 enable = mkEnableOption "sslh"; 39 40 verbose = mkOption { 41 type = types.bool; 42 default = false; 43 description = "Verbose logs."; 44 }; 45 46 timeout = mkOption { 47 type = types.int; 48 default = 2; 49 description = "Timeout in seconds."; 50 }; 51 52 transparent = mkOption { 53 type = types.bool; 54 default = false; 55 description = "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"; 56 }; 57 58 listenAddress = mkOption { 59 type = types.str; 60 default = "0.0.0.0"; 61 description = "Listening address or hostname."; 62 }; 63 64 port = mkOption { 65 type = types.int; 66 default = 443; 67 description = "Listening port."; 68 }; 69 70 appendConfig = mkOption { 71 type = types.str; 72 default = defaultAppendConfig; 73 description = "Verbatim configuration file."; 74 }; 75 }; 76 }; 77 78 config = mkMerge [ 79 (mkIf cfg.enable { 80 users.users.${user} = { 81 description = "sslh daemon user"; 82 isSystemUser = true; 83 }; 84 85 systemd.services.sslh = { 86 description = "Applicative Protocol Multiplexer (e.g. share SSH and HTTPS on the same port)"; 87 after = [ "network.target" ]; 88 wantedBy = [ "multi-user.target" ]; 89 90 serviceConfig = { 91 User = user; 92 Group = "nogroup"; 93 PermissionsStartOnly = true; 94 Restart = "always"; 95 RestartSec = "1s"; 96 ExecStart = "${pkgs.sslh}/bin/sslh -F${configFile}"; 97 KillMode = "process"; 98 AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID"; 99 PrivateTmp = true; 100 PrivateDevices = true; 101 ProtectSystem = "full"; 102 ProtectHome = true; 103 }; 104 }; 105 }) 106 107 # code from https://github.com/yrutschle/sslh#transparent-proxy-support 108 # the only difference is using iptables mark 0x2 instead of 0x1 to avoid conflicts with nixos/nat module 109 (mkIf (cfg.enable && cfg.transparent) { 110 # Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination 111 boot.kernel.sysctl."net.ipv4.conf.default.route_localnet" = 1; 112 boot.kernel.sysctl."net.ipv4.conf.all.route_localnet" = 1; 113 114 systemd.services.sslh = let 115 iptablesCommands = [ 116 # DROP martian packets as they would have been if route_localnet was zero 117 # Note: packets not leaving the server aren't affected by this, thus sslh will still work 118 { table = "raw"; command = "PREROUTING ! -i lo -d 127.0.0.0/8 -j DROP"; } 119 { table = "mangle"; command = "POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP"; } 120 # Mark all connections made by ssl for special treatment (here sslh is run as user ${user}) 121 { table = "nat"; command = "OUTPUT -m owner --uid-owner ${user} -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x02/0x0f"; } 122 # Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark) 123 { table = "mangle"; command = "OUTPUT ! -o lo -p tcp -m connmark --mark 0x02/0x0f -j CONNMARK --restore-mark --mask 0x0f"; } 124 ]; 125 ip6tablesCommands = [ 126 { table = "raw"; command = "PREROUTING ! -i lo -d ::1/128 -j DROP"; } 127 { table = "mangle"; command = "POSTROUTING ! -o lo -s ::1/128 -j DROP"; } 128 { table = "nat"; command = "OUTPUT -m owner --uid-owner ${user} -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x02/0x0f"; } 129 { table = "mangle"; command = "OUTPUT ! -o lo -p tcp -m connmark --mark 0x02/0x0f -j CONNMARK --restore-mark --mask 0x0f"; } 130 ]; 131 in { 132 path = [ pkgs.iptables pkgs.iproute pkgs.procps ]; 133 134 preStart = '' 135 # Cleanup old iptables entries which might be still there 136 ${concatMapStringsSep "\n" ({table, command}: "while iptables -w -t ${table} -D ${command} 2>/dev/null; do echo; done") iptablesCommands} 137 ${concatMapStringsSep "\n" ({table, command}: "iptables -w -t ${table} -A ${command}" ) iptablesCommands} 138 139 # Configure routing for those marked packets 140 ip rule add fwmark 0x2 lookup 100 141 ip route add local 0.0.0.0/0 dev lo table 100 142 143 '' + optionalString config.networking.enableIPv6 '' 144 ${concatMapStringsSep "\n" ({table, command}: "while ip6tables -w -t ${table} -D ${command} 2>/dev/null; do echo; done") ip6tablesCommands} 145 ${concatMapStringsSep "\n" ({table, command}: "ip6tables -w -t ${table} -A ${command}" ) ip6tablesCommands} 146 147 ip -6 rule add fwmark 0x2 lookup 100 148 ip -6 route add local ::/0 dev lo table 100 149 ''; 150 151 postStop = '' 152 ${concatMapStringsSep "\n" ({table, command}: "iptables -w -t ${table} -D ${command}") iptablesCommands} 153 154 ip rule del fwmark 0x2 lookup 100 155 ip route del local 0.0.0.0/0 dev lo table 100 156 '' + optionalString config.networking.enableIPv6 '' 157 ${concatMapStringsSep "\n" ({table, command}: "ip6tables -w -t ${table} -D ${command}") ip6tablesCommands} 158 159 ip -6 rule del fwmark 0x2 lookup 100 160 ip -6 route del local ::/0 dev lo table 100 161 ''; 162 }; 163 }) 164 ]; 165}