Skip to content

Instantly share code, notes, and snippets.

@NiklasGollenstede
Last active September 24, 2020 21:07
Show Gist options
  • Save NiklasGollenstede/6f1cc4c8e0c9421a066501806a84c97a to your computer and use it in GitHub Desktop.
Save NiklasGollenstede/6f1cc4c8e0c9421a066501806a84c97a to your computer and use it in GitHub Desktop.
sslh-select demultiplexing for HTTP(S)/SSH/openVPN

sslh is a protocol (de-)multiplexer that can be used to let encrypted traffic of different kinds pass through restrictive firewalls. This is a guide to make sslh's rather complex setup easy.

Please read the content for more information.

SSLH transparent select service

There are unfortunamely a good number of FiFi and other networks out there that highly restrict the Internet services that may be accessed, often by filtering port numbers and messing with HTTP traffic. Many refrain from doing dep package inspection of the HTTPS port 443, though. The protocol (de-)multiplexer sslh can then be used to access multiple (encrypted) applications at port 443. That has to be set up on the servers end in advance, though, and isn't exactly trivial.

sslh can be run in a variety of setups and can differentiate between many protocols. This guide sets up the more performant select mode, and demultiplexes the probably most common protocols SSH, plain HTTP, TLS(HTTPS), and openVPN. The proxy is "transparent" in that is maintains the original source IP addresses, and is managed via systemd.

This setup includes ideas and snippets from:

NOTE: The iptables rules seem to render the $port{Proto} ports inaccessible for direct external connections! So unless e.g. SSH should always be accessed via $listenPorts, have it listen on an additional Port (in /et/ssh/sshd_config(.d/*)) and set $portSsh below to that port. The same applies to $portHttp and $portOvpn. $portTls usually can't be 443 anyway. (It may be possible to make this work with the same pot numbers on different addresses, but the iptables rules would likely need to be adjusted.)

# . <(cat << "#EOF" # copy from after the first #
interfaceName=eth0
listenPorts=443 # space separated, could also include 80
listenAddress=$(hostname -I | awk '{print $1}') # or a hostname, which can resolve to multiple v4/v6 addresses
listenArgs=$(. <(printf %s 'a=($listenPorts); printf %s "${a[@]/#/--listen $listenAddress:}"'))
portSsh=22022
#portHttp=8080
portTls=4433
portOvpn=1194
addressSsh=$listenAddress
addressHttp=$listenAddress
addressTls=$listenAddress
addressOvpn=$listenAddress
nginxListenHttps="listen $addressTls:$portTls ssl http2; port_in_redirect off;" # optional
#EOF
)

With the above variables set, run this to set up the port multiplexing:

# { (. <(cat << "#EOF" # copy from after the first #
#!/usr/bin/env bash
set -eux

## generate configuration values

cliArgs=(--foreground --numeric --transparent $listenArgs) # TODO: pass "--user sslh" instead User= and AmbientCapabilities= below?
ExecStartPre=(); ExecStopPost=()

if [[ "${portSsh:-}"  ]]; then cliArgs+=(--ssh      $addressSsh:$portSsh ); fi
if [[ "${portHttp:-}" ]]; then cliArgs+=(--http    $addressHttp:$portHttp); fi
if [[ "${portTls:-}"  ]]; then cliArgs+=(--tls      $addressTls:$portTls ); fi
if [[ "${portOvpn:-}" ]]; then cliArgs+=(--openvpn $addressOvpn:$portOvpn); fi

iptables=$(which iptables); ip6tables=$(which ip6tables)
ip_4="$(which ip) -4"; ip_6="$(which ip) -6"

ExecStartPre+=("$iptables  -t mangle -N SSLH" "$ip6tables -t mangle -N SSLH")
for port in $portSsh $portHttp $portTls $portOvpn; do if [[ $port ]]; then
	ExecStartPre+=(
		"$iptables  -t mangle -A OUTPUT -o eth0 -p tcp -m tcp --sport $port -j SSLH"
		"$ip6tables -t mangle -A OUTPUT -o eth0 -p tcp -m tcp --sport $port -j SSLH"
	)
	ExecStopPost+=(
		"$iptables  -t mangle -D OUTPUT -o eth0 -p tcp -m tcp --sport $port -j SSLH"
		"$ip6tables -t mangle -D OUTPUT -o eth0 -p tcp -m tcp --sport $port -j SSLH"
	)
fi; done
ExecStartPre+=(
	"$iptables  -t mangle -A SSLH -j MARK --set-xmark 0x1/0xffffffff"
	"$ip6tables -t mangle -A SSLH -j MARK --set-xmark 0x1/0xffffffff"
	"$iptables  -t mangle -A SSLH -j ACCEPT"
	"$ip6tables -t mangle -A SSLH -j ACCEPT"
)
ExecStopPost+=(
	"$iptables  -t mangle -D SSLH -j MARK --set-xmark 0x1/0xffffffff"
	"$ip6tables -t mangle -D SSLH -j MARK --set-xmark 0x1/0xffffffff"
	"$iptables  -t mangle -D SSLH -j ACCEPT"
	"$ip6tables -t mangle -D SSLH -j ACCEPT"
)
ExecStopPost+=("$iptables  -t mangle -X SSLH" "$ip6tables -t mangle -X SSLH")
ExecStartPre+=(
	"$ip_4 rule  add fwmark 0x1 lookup 100"
	"$ip_6 rule  add fwmark 0x1 lookup 100"
	"$ip_4 route add local 0.0.0.0/0 dev lo table 100"
	"$ip_6 route add local      ::/0 dev lo table 100"
)
ExecStopPost+=(
	"$ip_4 rule  del fwmark 0x1 lookup 100"
	"$ip_6 rule  del fwmark 0x1 lookup 100"
	"$ip_4 route del local 0.0.0.0/0 dev lo table 100"
	"$ip_6 route del local      ::/0 dev lo table 100"
)


# install sslh
echo "sslh sslh/inetd_or_standalone select standalone" | debconf-set-selections
apt install sslh -y # choose 'standalone' if it prompts

# nginx can't listen on port 443 on the external IP, so use this snippet:
if [[ "$nginxListenHttps" ]]; then
	mkdir -p /etc/nginx/snippets/
	printf '%s\n' "$nginxListenHttps" > /etc/nginx/snippets/listen-https.conf
fi

# write configuration
mkdir -p     /etc/systemd/system/sslh.service.d/
cat << EOF > /etc/systemd/system/sslh.service.d/override.conf
[Service]

# Replace the start command and make it use sslh-select
EnvironmentFile=
ExecStart=
ExecStart=$(which sslh-select) ${cliArgs[*]}
Restart=always

# Run sslh as an user and use capabilities to bind ports
User=sslh
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN

# Limit access
PrivateTmp=true
PrivateDevices=true
ProtectSystem=full
ProtectHome=true

# Set routing rules automaticaly on script start
PermissionsStartOnly=true

$( IFS=$'\n'; printf '%s\n' "${ExecStartPre[*]/#/ExecStartPre=}" )

$( IFS=$'\n'; printf '%s\n' "${ExecStopPost[*]/#/ExecStopPost=}" )

EOF

# and now start it
systemctl enable sslh
systemctl start sslh

#EOF
)); }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment