ssh, http and https multiplexing

This is about how to have the ssh and http(s) server share the same port (e.g. 80 or 443 port).
This is really cool :).

# Used sources:
# http://yalis.fr/cms/index.php/post/2014/02/22/Multiplex-SSH-and-HTTPS-on-a-single-port
# http://blog.cppse.nl/apache-proxytunnel-ssh-tunnel
# http://serverfault.com/questions/355271/ssh-over-https-with-proxytunnel-and-nginx
# http://tyy.host-ed.me/pluxml/article4/port-443-for-https-ssh-and-ssh-over-ssl-and-more
# http://ipset.netfilter.org/iptables.man.html
# http://ipset.netfilter.org/iptables-extensions.man.html
# http://man7.org/linux/man-pages/man8/ip-rule.8.html
# http://lartc.org/howto/lartc.netfilter.html
# http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=Unassigned
# https://cloud.githubusercontent.com/assets/2137369/15272097/77d1c09e-1a37-11e6-97ef-d9767035fc3e.png
# http://www.adminsehow.com/2011/09/iptables-packet-traverse-map/

### begin sshttp setup 1
# https://github.com/stealth/sshttp
# Below are the preparations for this setup:
# sshttpd listens on 80 for ssh and http connections. It forwards to ssh:1022 and nginx:880.
# Will work these:
ssh -p 1022 gigi@127.0.0.1      -> access tried from within 192.168.1.31 host
ssh -p 1022 gigi@192.168.1.31   -> access tried from within 192.168.1.31 host
ssh -p 80 gigi@adrhc.go.ro      -> access tried from within 192.168.1.31 host or from internet
http://127.0.0.1/public/        -> access tried from within 192.168.1.31 host
http://127.0.0.1:880/public/    -> access tried from within 192.168.1.31 host
http://192.168.1.31:880/public/ -> access tried from within 192.168.1.31 host
http://192.168.1.31/public/     -> access tried from 192.168.1.31's LAN
http://adrhc.go.ro/public/      -> access tried from within 192.168.1.31 host or from internet
# Won't work this:
ssh -p 1022 gigi@adrhc.go.ro    -> access tried from within 192.168.1.31 host or from internet
http://192.168.1.31/public/     -> access tried from within 192.168.1.31 host
http://adrhc.go.ro:880/public/  -> access tried from within 192.168.1.31 host or from internet

# /etc/modules
modprobe nf_conntrack_ipv4
modprobe nf_conntrack
echo "nf_conntrack" >> /etc/modules
echo "nf_conntrack_ipv4" >> /etc/modules

# in /etc/ssh/sshd_config make sure to have:
# Port 1022
# Banner /etc/sshd-banner.txt 
# Makefile uses the content of /etc/sshd-banner.txt, e.g.:
# SSH_BANNER=-DSSH_BANNER=\"adrhc\'s\ SSH\ server\"
cat /etc/sshd-banner.txt
adrhc's SSH server

# configure nf-setup, e.g. for sshttpd.service below should be:
DEV="eth0"
SSH_PORT=1022
# HTTP_PORT=1443
HTTP_PORT=880
# also you could add this afterwards in order not to run nf-setup if already run:
if [ "`iptables -t mangle -L | grep -v -P "^ufw-" | grep -P "^DIVERT.+tcp spt:$HTTP_PORT"`" != "" ]; then
    echo "sshttp netfilter rules already applied ..."
    exit 0
fi
echo "applying sshttp netfilter rules ..."

# for nginx or apache take care of address binding not to overlap with sshttpd.service, e.g.:
#    server {
#        listen 127.0.0.1:80;
#        listen 127.0.0.1:880;
#        # listen 192.168.1.31:80; -> used/bound by sshttpd.service below
#        listen 192.168.1.31:880;

# install the systemd sshttpd.service defined below:
sudo chown root: /etc/systemd/system/sshttpd.service && sudo chmod 664 /etc/systemd/system/sshttpd.service && sudo systemctl daemon-reload; cp -v $HOME/compile/sshttp/nf-setup $HOME/apps/bin

# systemd sshttpd.service:
[Unit]
# see https://github.com/stealth/sshttp
Description=SSH/HTTP(S) multiplexer
# for any address binding conflict that occurs between ufw, ssh, nginx and sshttp I want ufw, ssh and nginx to win against sshttp
After=network.target
# sudo iptables -L | grep -v -P "^ufw-" | grep -P "1022|1443|880|DIVERT|DROP|ssh"
# sudo iptables -t mangle -L | grep -v -P "^ufw-" | grep -P "1022|1443|880|DIVERT|DROP|ssh"
[Service]
Type=forking
RuntimeDirectory=sshttpd
ExecStartPre=-/bin/chown nobody: /run/sshttpd
ExecStartPre=-/home/gigi/apps/bin/nf-setup
Restart=on-failure
RestartSec=3
TimeoutStartSec=5
TimeoutStopSec=5
# using 443 for sshttpd:
# ssh -p 443 gigi@adrhc.go.ro
# wget --no-check-certificate https://adrhc.go.ro/
# ExecStart=/home/gigi/apps/bin/sshttpd -n 4 -S 1022 -H 1443 -L 443 -l 192.168.1.31 -U nobody -R /run/sshttpd
# using 80 for sshttpd:
# ssh -p 80 gigi@adrhc.go.ro
# wget http://adrhc.go.ro/public
ExecStart=/home/gigi/apps/bin/sshttpd -n 4 -S 1022 -H 880 -L 80 -l 192.168.1.31 -U nobody -R /run/sshttpd
[Install]
WantedBy=multi-user.target

### begin sshttp setup 2 (read first sshttp step 1)
# Below are the preparations for this setup:
# sshttpd listens on 444 for ssh and https connections. 
# sshttpd forwards to ssh:1022 or stunnel:1443.
# stunnel:1443 forwards to nginx:127.0.0.1:1080 or ssh:127.0.0.1:22 based on sni.
# the original remote client's ip is accessible (only for https but not ssh) with $realip_remote_addr (http://nginx.org/en/docs/http/ngx_http_realip_module.html#real_ip_header)

# Issue: any redirect (301 or 302) used in the server 127.0.0.1:1080 defined below will set Location header to http instead of https
# - see sshttp setup 3 for a solution 
# - see https://forum.nginx.org/read.php?2,269623,269647#msg-269647 (listen proxy_protocol and rewrite redirect scheme) for a better? solution:
src/http/ngx_http_header_filter_module.c: 
#if (NGX_HTTP_SSL) 
if (c->ssl || port == 443) { 
*b->last++ ='s'; 
} 
#endif 

# Won't work Transmission remote GUI but the web page will still work.
# ERROR (while using Transmission remote GUI):
    2016/09/19 15:03:42 [error] 5562#0: *2431 broken header: ">:azX���g��^}q�/���A��Rp(���n3��0�,�(�$��
    ����kjih9876�����2�.�*�&���=5��/�+�'�#��   ����g@?>3210����EDCB�1�-�)�%���:LM2V
    ����kjih9876�����2�.�*�&���=5��/�+�'�#��   ����g@?>3210����EDCB�1�-�)�%��� 444 -> also make sure ufw allows 444
# ssh -p 443 gigi@adrhc.go.ro
# wget --no-check-certificate https://adrhc.go.ro/
ExecStart=/********/apps/bin/sshttpd -n 4 -S 1022 -H 1443 -L 444 -l 192.168.1.31 -U nobody -R /run/sshttpd

# in nginx add this "magic" server:
server {
    listen 127.0.0.1:1080 default_server proxy_protocol;
    include xhttpd_1080_proxy.conf;
    port_in_redirect off;
    # change also fastcgi_params! (see below)
    ... your stuff ...
}

# xhttpd_1080_proxy.conf:
set_real_ip_from 192.168.1.0/24;
set_real_ip_from 127.0.0.0/8;
# set_real_ip_from ::1/32; -> doesn't work for me
real_ip_header proxy_protocol;
set $real_internet_https "on";
set $real_internet_port "443";

# in fastcgi_params have (besides your stuff):
# This special fastcgi_params must be used only by "magic server" (127.0.0.1:1080)!
fastcgi_param HTTPS $real_internet_https if_not_empty;
fastcgi_param SERVER_PORT $real_internet_port if_not_empty;

# stunnel.conf for server side
# sudo killall stunnel; sleep 1; sudo bin/stunnel etc/stunnel/stunnel.conf
pid = /run/stunnel.pid
debug = 4
output = /********/apps/log/stunnel.log
options = NO_SSLv2
compression = deflate
cert = /********/apps/etc/nginx/certs/adrhc.go.ro-server-pub.pem
key = /********/apps/etc/nginx/certs/adrhc.go.ro-server-priv-no-pwd.pem
[tls]
accept = 192.168.1.31:1443
connect = 127.0.0.1:1080
protocol = proxy
[ssh]
sni = tls:tti.go.ro
connect = 127.0.0.1:22
renegotiation = no
debug = 5
cert = /********/apps/etc/nginx/certs/adrhc.go.ro-server-pub.pem
key = /********/apps/etc/nginx/certs/adrhc.go.ro-server-priv-no-pwd.pem
[www on any]
sni = tls:*
connect = 127.0.0.1:1080
protocol = proxy

# stunnel.conf for client side
# killall stunnel; sleep 1; stunnel ****stunnel.conf && tailf ****stunnel.log
# ssh -p 1194 gigi@localhost
pid = /****************/temp/stunnel.pid
debug = 4
output = /****************/****stunnel.log
options = NO_SSLv2
[tti.go.ro]
# Set sTunnel to be in client mode (defaults to server)
client = yes  
# Port to locally connect to
accept = 127.0.0.1:1194  
# Remote server for sTunnel to connect to
connect = adrhc.go.ro:443
sni = tti.go.ro
verify = 2
CAfile = /****************/****Temp/Zyxel/adrhc.go.ro-server-pub.pem
# checkHost = certificate's CN field (see "Rejected by CERT at" in stunnel.log for learning CN)
checkHost = adrhc.go.ro
# CAfile = /****************/****Temp/Zyxel/adr-pub.pem
# checkHost = adr

### begin sshttp setup 3 (read first sshttp step 2)
# any redirect (301 or 302) used in the server 127.0.0.1:1080 defined above will go to the https server
# Issue: the original remote client's ip is not accessible (https or ssh)

# you'll need the https nginx configuration for your site listening at least on 127.0.0.1:443
# you no longer need the "magic" server defined above
# How this works:
# browser/stunnel-client useing ssl -> sshttpd:443 -> stunnel[tls to http] using ssl -> stunnel[http to https]

# stunnel.conf for server side
# sudo killall stunnel; sleep 1; sudo bin/stunnel etc/stunnel/stunnel.conf
pid = /run/stunnel.pid
debug = 4
output = /********/apps/log/stunnel.log
options = NO_SSLv2
compression = deflate
cert = /********/apps/etc/nginx/certs/adrhc.go.ro-server-pub.pem
key = /********/apps/etc/nginx/certs/adrhc.go.ro-server-priv-no-pwd.pem
[tls]
accept = 192.168.1.31:1443
connect = 127.0.0.1:1081
protocol = proxy
[ssh]
sni = tls:tti.go.ro
connect = 127.0.0.1:22
renegotiation = no
debug = 5
cert = /********/apps/etc/nginx/certs/adrhc.go.ro-server-pub.pem
key = /********/apps/etc/nginx/certs/adrhc.go.ro-server-priv-no-pwd.pem
[tls to http]
sni = tls:*
connect = 127.0.0.1:1081
# connect = 127.0.0.1:1080
# protocol = proxy
[http to https]
accept = 127.0.0.1:1081
connect = 127.0.0.1:443
client = yes

### begin sslh setup
# https://github.com/yrutschle/sslh
# Here I use ssh:1021 instead of ssh:1022.
sudo apt-get install sslh

sudo useradd -d /nonexistent -M -s /bin/false sslh
# according to https://github.com/yrutschle/sslh#capabilities-support I need:
sudo setcap cap_net_bind_service,cap_net_admin+pe /usr/sbin/sslh-select
sudo getcap -rv /usr/sbin/sslh-select

cat /etc/default/sslh
RUN=yes
DAEMON=/usr/sbin/sslh-select
# with --transparent the local ip is not acceptable:
DAEMON_OPTS="--transparent --timeout 1 --numeric --user sslh --listen 192.168.1.31:334 --ssh 192.168.1.31:1021 --http 192.168.1.31:80 --pidfile /var/run/sslh/sslh.pid"
# without --transparent is acceptable also local ip:
# DAEMON_OPTS="--transparent --timeout 1 --numeric --user sslh --listen 192.168.1.31:334 --ssh 192.168.1.31:1021 --http 127.0.0.1:80 --pidfile /var/run/sslh/sslh.pid"

cat /etc/systemd/system/sslh.service.d/custom.conf 
# cp -v $HOME/bin/systemd-services/sslh-setup.sh $HOME/apps/bin
[Service]
ExecStartPre=-/********/apps/bin/sslh-setup.sh
ExecStart=
ExecStart=/usr/sbin/sslh-select --foreground $DAEMON_OPTS
SuccessExitStatus=15

cat sslh-setup.sh
#!/bin/sh
if [ "`sudo iptables -t mangle -L | grep -P "^SSLH\s.+\sspt:1021"`" != "" ]; then
    echo "SSLH netfilter rules already applied ..."
    exit 0
fi
iptables -t mangle -N SSLH
iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 --sport 1021 --jump SSLH
iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 --sport 80 --jump SSLH
iptables -t mangle -A SSLH --jump MARK --set-mark 0x1
iptables -t mangle -A SSLH --jump ACCEPT
ip rule add fwmark 0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100

sudo systemctl daemon-reload
sudo systemctl enable sslh
sudo systemctl start sslh

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.