Post

My Router - Part 8

Accessing Services from Internet over HTTPS Port

My Router - Part 8

I’d like to multiplex TCP port 443 so that I can passwordlessly SSH into my network without needing a VPN.

I’ve explored different multiplexers, such SSLH, HAProxy, all of didn’t work for some reason, probably due to incorrectly configured firewall rules that I couldn’t figure out how to configure properly.

With the help of Google, I finally figured out the NGINX stream configuration to pass the real client IP address to the HTTPS server, and strip the proxy header out of the stream for SSH and OpenVPN connections, all without the firewall rules that SSLH and HAProxy required for transparent mode that broke things in the router…


Setting up SSH Passwordless access

Let’s modify our DropBear configuration in order to restrict our first DropBear instance to the lan network, and create another DropBear instance where our passwordless SSH server will reside, accessible via the guest network:

1
2
3
4
5
6
7
8
9
10
11
12
uci rename dropbear.@dropbear[0]="lan"
uci set dropbear.lan=dropbear
uci set dropbear.lan.PasswordAuth='on'
uci set dropbear.lan.RootPasswordAuth='on'
uci set dropbear.lan.Interface='lan'

uci set dropbear.guest=dropbear
uci set dropbear.guest.RootPasswordAuth='off'
uci set dropbear.guest.PasswordAuth='off'
uci set dropbear.guest.Interface='guest'
uci commit
service dropbear restart

Some Prep Work

First, we need to install the NGINX stream module:

1
apk add nginx-mod-stream

We need to change the ports that NGINX listens on in the /etc/config/nginx file:

1
sed -i "s|443 ssl|8443 ssl proxy_protocol|g" /etc/config/nginx

We need to add a new configuration file under /etc/nginx/conf.d:

1
2
3
4
5
6
FILE=/etc/nginx/conf.d/cfg:proxy_protocol.conf
cat << EOF > ${FILE}
set_real_ip_from  127.0.0.1;
real_ip_header    proxy_protocol;
EOF
echo ${FILE} >> /etc/sysupgrade.conf

The first 2 lines in /etc/nginx/conf.d/cfg:proxy_protocol.conf allows NGINX to pull the client IP address from the calling stream block, while the last line allows me to use really long domain names. I don’t feel like buying a domain name at this point…. :p


Our Awesome Multiplexer!

Let’s add the stream block to /etc/nginx/uci.conf.template to handle the multiplexing, then restart NGINX to make the magic work:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
mkdir -p /etc/nginx/stream.d
cat << EOF >> /etc/nginx/uci.conf.template
stream {
	ssl_preread on;
	map_hash_bucket_size 128;

	upstream ssh {
		server 127.0.0.1:8022;
	}
	upstream sni_default {
		server 127.0.0.1:8443;
	}
	upstream sni_blank {
		server 127.0.0.1:8443; # <= Change to block handling OpenVPN
	}

	map \$ssl_preread_protocol \$upstream_backend {
		""        ssh;
		default   \$host;
	}
	map \$ssl_preread_server_name \$host {
		""        sni_blank;
		default   sni_default;
	}

	server {
		listen 443;
		proxy_protocol on;
		proxy_pass \$upstream_backend;
	}
	server {  # Block handling hand-off to SSH
		listen 127.0.0.1:8022 proxy_protocol;
		proxy_pass 192.168.3.1:22;
		proxy_protocol off;  # << DO NOT REMOVE!  Breaks SSH if you do! >>
	}
	include stream.d/*.conf;
}
EOF
service nginx restart

HTTPS from WAN Firewall Rule

We also need to add a firewall rule to allow access to TCP port 443 from the internet, then restart the firewall:

1
2
3
4
5
6
7
8
uci set firewall.https_from_wan=rule
uci set firewall.https_from_wan.src='wan'
uci set firewall.https_from_wan.name='Allow-HTTPS-from-WAN'
uci set firewall.https_from_wan.proto='tcp'
uci set firewall.https_from_wan.dest_port='443'
uci set firewall.https_from_wan.target='ACCEPT'
uci commit
service firewall restart

What The Stream Block Does

Upstream definitions

  • upstream ssh points to our passwordless SSH server at 127.0.0.1:8022.
  • upstream sni_default points to our alternative port HTTPS stack on port 8443.
  • upstream sni_blank also points to our alternative port HTTPS stack on port 8443.

Map blocks

  • First map contains protocol detection. No protocol detected gets directed to upstream ssh. Everything else gets sent to the second map.

  • Second map contains routing for proxy server destination based on SNI (Server Name Indication). No SNI means it gets directed to upstream sni_blank. Everything else gets pointed to upstream sni_default.

Server blocks

  • First block listens to port 443, passing control to the specified proxy server. Note that proxy_protocol on is set, preserving client IP address for the receiving server.

  • Second block listens to 127.0.0.1 port 8022 with proxy_protocol off, which DOES NOT pass the client IP address to the proxy server, enabling SSH to work properly. It proxies to 192.168.3.1:22, which is the passwordless SSH server we set up earlier in this post.


Special Notes

  • OpenVPN should be able to be added by adding a new server block with a new port number (aka 127.0.0.1:8194), like so:
    1
    2
    3
    4
    5
    
      server {
          listen 127.0.0.1:8194 proxy_protocol;
          proxy_pass 127.0.0.1:1194;
          proxy_protocol off;  # << DO NOT CHANGE!  Breaks OpenVPN if you do! >>
      }
    

    Also, upstream sni_blank must be changed to point to the IP/port of your new server block, like so:

    1
    2
    3
    
      upstream sni_blank {
          server 127.0.0.1:8194;	# OpenVPN server
      }
    
  • Hostnames are not recommended to be used within the stream block. Some of the articles that I’ve read seem to indicate that NGNIX doesn’t resolve hostnames inside the stream block. I personally haven’t tried using hostnames because the servers that I have are assigned IP addresses within OpenWrt.

  • Each of the proxy servers upstreams should have a static IP address assigned, either by OpenWrt or within the server OS itself. Assigning static IP addresses in OpenWrt was covered in My Router - Part 1: Static IP Addresses. It is beyond the scope of this post to assist with assigning static IP address within non-OpenWrt OSes.

Summary

We’ve successfully set up our HTTPS port (port 443) multiplexer, enabling SSH and potentially OpenVPN to communcate on TCP port 443 alongside normal HTTPS traffic.

Because we are preserving the client IP address on HTTPS communcation, all HTTPS communcation (if not modified) with the router domain name generates a 403 error, disguised config-wise as a 404 error, when accessed from outside our network, thus (hopefully) protecting our router setup from outside interference. We are trying to hide the fact that something exists at this domain name, after all….

SSH from the internet is protected by cryptographic public-private key pairs and does not accept passwords, while leaving the in-network SSH able to log in using passwords.

Additional Information

This post is licensed under CC BY 4.0 by the author.

Trending Tags