A proxy is a gateway service for users to access the Internet. This might be implemented to enforce security policy or simply as a performance enhancement since proxies often times are configured to cache fetched pages, increasing responsiveness for subsequent requests for the same content. But what is a reverse proxy? A reverse proxy acts as a gateway to internal servers. It can be used to cache pages for performance reasons, just as with a forward proxy. In my opinion, one of the most interesting reasons to use a reverse proxy is to provide an alternate or supplemental means of authenticated access to internal web-based services behind a firewall. The configuration example provided here uses Apache and the mod_proxy module. Diversification of access methods for road warriors and remote workers helps ensure that services are available under a variety of circumstances, some of which may preclude traditional IPsec VPN access, for example. This can be a simple alternative to SSL-based VPNs, such as OpenVPN or SSL-Explorer.
My examples will use Debian and Apache2, so if you use a different Linux distribution, or even another platform, the installation tools will vary, but the concepts are similar. The following packages are required:
On Debian (and Ubuntu), the two modules above are provided through the default installation of apache; specifically, they are part of the apache2.2-common package. Make sure you have these, regardless of what platform you’re using.
Basic Architecture and DNS
Before we move into the actual configuration, let’s consider how all of this will work. The diagram below shows the basic architecture.
Depending on the requirements, the traffic between the proxy server and the internal server may or may not be encrypted.
With respect to DNS, there are two distinct options:
- Use one name (e.g. proxy.example.com) and differentiate between internal servers/applications using a unique URL prefix, e.g. https://proxy.example.com/app1, https://proxy.example.com/app2, etc.
- Create a one-to-one mapping between internal and external DNS names.
While the first option does not reveal anything to any attackers about internal hostnames, it does require content providers and users to publish and maintain two different sets of URLs/bookmarks and use one for proxied access and the other for direct access, e.g. VPN or onsite. This assumes that the internal servers also answer to HTTPS requests. The second has obvious benefits; the first is really just obfuscation and cannot be relied on to provide real security. However, if you place any value on obfuscation, perhaps you can offset some of the downside of revealing internal hostnames by choosing hostnames that do not relay a lot of information about the value of the target. This is the approach I will describe below; see other documentation online for using distinct URLs, beginning with the ones listed at the end of this article. To implement this approach, you create CNAME resource records in your DNS zone files pointing to your proxy server.
We will only cover the basics here. In a nutshell, your configuration will include the following:
- An address resource record with externally-facing IP address of your proxy server. This server is presumably in a DMZ, and may be NATed, so this DNS zone is for your external view (if you are using BIND 9 style views).
- A CNAME resource record for each internal server for which you want to provide proxy access. These all point to your proxy server.
For example, they might look like the following (substituting a routable IP address for a.b.c.d):
proxy.exaple.com. 86400 IN A a.b.c.d app1.example.com. 86400 IN CNAME proxy.example.com.
The proxy server must be able to resolve the internal IP addresses of any hosts for which it acts as a proxy. Again, with BIND 9, this is simple to accomplish with views, which we will not cover here.
Some of the steps will be Debian (or Debian derivative) specific; adapt as needed. The main configuration items will be applicable to apache, regardless of OS platform.
Since we want our traffic over the public Internet to be encrypted, we must setup SSL, so apache has to listen on two ports:
Your mod_proxy settings should be close to what is shown below. On Debian, all of these settings are in /etc/apache2/mods-available/proxy.conf (your distribution may or may not; they can just as easily be in some standard configuration file for all settings).
#turning ProxyRequests on and allowing proxying from all may allow #spammers to use your proxy to send email. ProxyRequests Off AddDefaultCharset off Order deny,allow Allow from all # Enable/disable the handling of HTTP/1.1 "Via:" headers. # ("Full" adds the server version; "Block" removes all outgoing Via: headers) # Set to one of: Off | On | Full | Block ProxyVia On
By default, Debian ships with access set to ‘Deny from all’; if you fail to change this, you’ll see a message like the following in your error log:
[Mon Jul 21 13:57:06 2008] [error] [client a.b.c.d] client denied by server configuration: proxy:http://www.example.com/
Do not forget to enable the proxy_html module, as shown below for Debian.
Otherwise you’ll see an error message like the following in your error log:
[Mon Jul 21 14:23:42 2008] [warn] proxy: No protocol handler was valid for the URL /. If you are using a DSO version of mod_proxy, make sure the proxy submodules are included in the configuration using LoadModule.
Virtual Host Settings
This is the where the fun begins! The sole function of the first virtual host statement is to re-direct requests on TCP/80 to HTTPS.
<VirtualHost 192.168.100.1:80> ServerName svr1.example.com ServerAlias svr1 Redirect / https://srv1.example.com/ </VirtualHost> <VirtualHost 192.168.100.1:443> ServerName svr1.example.com ServerAlias svr1 ProxyPass / http://svr1.example.com/ ProxyPassReverse / http://svr1.example.com/ SSLEngine On SSLProxyEngine On SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL SSLCertificateFile /etc/ssl/certs/STAR_example_com.crt SSLCertificateKeyFile /etc/ssl/private/STAR.example.com.key SSLCertificateChainFile /etc/ssl/certs/STAR_example_com.ca-bundle <Location /> AuthType Basic AuthName "Example Inc. Proxy" AuthUserFile /etc/apache2/httpd.auth-basic AuthGroupFile /etc/apache2/httpd.auth-groups require group employees require user specialContractor </Location> </VirtualHost>
The real work for reverse proxies is done with two directives:
This directive maps the remote server’s URL space into the space of the local server. Since we use separate virtual hosts for each DNS name corresponding to individual backend servers, we only need one which maps to the root space. If there are parts of the internal server’s URL space you do not want to proxy, you can add directives to exclude those parts, but since the “first match wins” be sure to include these before the the all-encompassing directive. For example:
ProxyPass /some/part ! ProxyPass / http://app1.example.com
There are other, more complicated use cases for this directive when DNS names do not match. The approach described here makes the configuration much simpler.
This directive tells Apache to adjust the URL in certain HTTP responses. This is critical if the proxy URL namespace does not match the backend server’s. Since we are using the same DNS name inside and outside, this may only matter if you are using HTTPS to/from the end-user, but HTTP to the inside server (whether you do this should be considered carefully). It will not rewrite URL references inside the HTML (see mod_proxy_html if you need to do this).
You may have noticed the location block above with directives for authentication:
<Location /> AuthType Basic AuthName "Example Inc. Proxy" AuthUserFile /etc/apache2/httpd.auth-basic AuthGroupFile /etc/apache2/httpd.auth-groups require group employees require user specialContractor </Location>
This authentication is in addition to whatever the web service may require. This protects against unrestricted access to internal service, which could subject it to remote denial-of-service attacks. We can use basic (unencrypted authentication) since we have already re-directed to HTTPS. We use both a user and group file. This is so we can provide different proxied services for different groups of users, some that are perhaps only available to a small number of users. See the Apache documentation for details on these files. You can provide a simple web page for users to request a proxy account, and, for example, send the username and encrypted password via e-mail to someone who can manage inclusion in the files referenced in the location block.
If you are interested in the subtleties of reverse proxy configuration, check out the following pages:
- Running a Reverse Proxy in Apache
This is a very detailed description of reverse proxy configuration without matching DNS names for the external proxied service and the backend service.
- Apache mod_proxy documentation
This is the reference document for details on the individual directives, with examples.