Niko's Project Corner

Publishing internal services behind a NAT

Description Secure, scalable and easy-to-configure HTTP(S) services.
Languages Bash
Tags Ng­inx
Duration Summer 2014
Modified 1st September 2014

Even in desk­top ap­pli­ca­tions it is be­com­ing more and more com­mon to provide a HTTP based APIs or full user in­ter­faces. For ex­am­ple Bit­Tor­rent's μTor­rent and Bit­Tor­rent Sync don't have any built-in UI, and in­stead users just head with their pre­ferred in­ter­net browser to http://lo­cal­host:8080 or http://lo­cal­host:8888. How­ever they typ­ically lack HTTPS en­cryp­tion and each port needs to be con­fig­ured to the NAT router in­di­vid­ually. This so­lu­tion uses a Ng­inx in­stance on a vir­tual ma­chine to provide a HTTPS re­verse proxy to all these ser­vices in a sin­gle port un­der dif­fer­ent sub-do­mains.

My ba­sic re­quire­ment was to make in­ter­nal HTTP ser­vices ac­ces­si­ble from ex­ter­nal net­works by an easy-to-con­fig­ure and rel­atively se­cure so­lu­tion. I also wanted to min­imize con­fig­ura­tion on the router side, be­cause it doesn't scale nicely to dozens of ports and a few ma­chi­nes. It is also un­able to add HTTPS wrap­ping on HTTP ser­vices. Also the true server shouldn't be di­rectly vis­ible to the out­side world, to mit­igate the risk of Heart­bleed-like bugs and have the con­fig­ura­tion hard­ened for this pur­pose. Within the in­ter­nal net­work it is fine to use pass­word-only SSH au­then­ti­ca­tion, but lo­gins orig­inat­ing from the ex­ter­nal net­work should be al­lowed only by pub­lic/pri­vate key au­then­ti­ca­tion.

This so­lu­tion re­quires only two ports to be con­fig­ured to the NAT router: 443 for HTTPS and some ar­bi­trary port for SSH. All traf­fic is routed to a vir­tual ma­chine (4 GB disk and 512 MB RAM is suf­fi­cient), which only needs sshd and Ng­inx ser­vices run­ning. Cur­rently the Ng­inx is serv­ing a self-signed cer­tifi­cate, which is se­cure as long as you check that the SHA-256 check­sum matches with the cert you have gen­er­ated.

The over­all net­work and ser­vice struc­ture is shown in Fig­ure 1. The vir­tual ma­chine is con­fig­ured to get its own IP from the router's DHCP ser­vice. HTTPS is ter­mi­nated at the vir­tual ma­chine's Ng­inx, and based on the re­quest's sub-do­main it is proxy-passed to the rel­evant ser­vice. Ser­vices have their own in­ter­nal do­mains which are con­fig­ured in the hosts file (a more ad­vanced al­ter­na­tive would be to run an in­ter­nal DNS ser­vice). The ac­tual ser­vice can lo­cate in any other ma­chine within the net­work as well, if there are mul­ti­ple servers at work. An ad­di­tional ben­efit is the pos­si­bil­ity of adding a com­mon HTTP Ba­sic au­then­ti­ca­tion on vir­tual ma­chine's Ng­inx. So far I have been very happy with this set-up. I can also run op­tional ser­vices such as the Squid HTTP proxy.

Figure 1: The net­work topol­ogy and some com­mon ser­vices' port num­bers. Ser­vices with HTTP ac­cess are shown in green and are served by Ng­inx re­verse proxy, and other ser­vices are made vis­ible by SSH tun­nel­ing and port-for­ward­ing. The vir­tual ma­chine is the only ma­chine vis­ible to the out­side world, and it ac­cepts SSH lo­gins only by a pub­lic/pri­vate key.

Related blog posts: