My Router - Part 8
Accessing Services from Internet over HTTPS Port
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 sshpoints to our passwordless SSH server at 127.0.0.1:8022.upstream sni_defaultpoints to our alternative port HTTPS stack on port 8443.upstream sni_blankalso 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 toupstream sni_default.
Server blocks
First block listens to port 443, passing control to the specified proxy server. Note that
proxy_protocol onis 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_blankmust 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
streamblock. Some of the articles that I’ve read seem to indicate that NGNIX doesn’t resolve hostnames inside thestreamblock. 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.
