Apache reverse proxy

From Newroco Tech Docs
Jump to navigationJump to search

could do with filling out more detail

A reverse proxy allows you to front multiple websites from a single public IP address, act as a load balancer and potentially defuse otherwise dangerous cyber attacks. There are different solutions, the instructions here are for an Apache server-based solution. Basic requirements are Apache, mod_ssl and mod_proxy installed and enabled.


Installation

Install apache2

apt-get install apache2

File /etc/apache2/mods-available/proxy.conf should look like this:

<IfModule mod_proxy.c>

        # If you want to use apache2 as a forward proxy, uncomment the
        # 'ProxyRequests On' line and the <Proxy *> block below.
        # WARNING: Be careful to restrict access inside the <Proxy *> block.
        # Open proxy servers are dangerous both to your network and to the
        # Internet at large.
        #
        # If you only want to use apache2 as a reverse proxy/gateway in
        # front of some web application server, you DON'T need
        # 'ProxyRequests On'.

        ProxyRequests Off

        <Proxy *>
                AddDefaultCharset off
                Order deny,allow
                Deny from all
        </Proxy>

        # 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 Off

        ProxyVia On
        ProxyPreserveHost On
        ProxyRequests Off
        ProxyTimeout 600

</IfModule>

Create file /etc/apache2/mods-available/proxy_http.conf and put this inside:

ProxyVia On
ProxyPreserveHost On
ProxyRequests Off

<Proxy *>
        Order deny,allow
        Allow from all
</Proxy>

Add these lines in /etc/apache2/apache2.conf to block paths that should not be visible

ProxyPass /.env !
ProxyPass /.git !

Enable these modules

a2enmod proxy
a2enmod proxy_http
a2enmod rewrite
a2enmod headers
a2enmod ssl
a2enmod remoteip

Optional: By default apache only looks at .conf files from /etc/apache2/sites-enabled for sites. If you want it to look at all files edit this line in /etc/apache2/apache2.conf

IncludeOptional sites-enabled/*

On busier proxies you can modify /etc/apache2/mods-enabled/mpm_event.conf for a better performance. Here is an example:

<IfModule mpm_event_module>
        StartServers                     5
        MinSpareThreads          25
        MaxSpareThreads          75
        ThreadLimit                      64
        ThreadsPerChild          25
        MaxRequestWorkers         250
        MaxConnectionsPerChild   0
</IfModule>

And restart apache2

systemctl restart apache2

Failover proxy

Create a user on failover-proxy with a private key and one on proxy with the public key that would sync the data from proxy to failover-proxy.

On failover-proxy create script /opt/bin/proxy_sync:

#!/bin/bash

#apache sync
/usr/bin/rsync -rl --safe-links --delete --rsync-path="/usr/bin/sudo /usr/bin/rsync" <user>@<proxy-ip>:/etc/apache2/ /etc/apache2/ 2>&1 >> /var/log/proxy_sync.log

#for letsencrypt certs
/usr/bin/rsync -rl --safe-links --delete --rsync-path="/usr/bin/sudo /usr/bin/rsync" <user>@<proxy-ip>:/etc/letsencrypt/ /etc/letsencrypt/ 2>&1 >> /var/log/proxy_sync.log

/usr/sbin/apache2ctl graceful

Make it executable

chmod +x /opt/bin/proxy_sync

Create a cron job for the script /etc/cron.d/proxy_sync:

*/15 * * * * <user> /opt/bin/proxy_sync

Keepalived

Keepalived is used to manage a floating IP.

To setup keepalived, install it on both servers:

sudo apt-get install keepalived

Copy the nagios check "check_http" to /usr/local/bin, from the /usr/lib/nagios/plugins of a server that has nagios-plugins installed (please don't install nagios-plugins on the reverse proxy, that package would install many dependencies).

Finally create the following /etc/keepalived/keepalived.conf on the master:

global_defs {
	notification_email {
		<email>
	}

	notification_email_from <email>
	smtp_server 127.0.0.1   
}

vrrp_script chk_apache {           	
        script "/usr/local/bin/check_http -H 127.0.0.1 -e 200"    	
        interval 3                      # check every 2 seconds
        weight 2                        # add 2 points of prio if OK
}

vrrp_instance floating_ip {
        interface ens3
        state MASTER
        virtual_router_id 51
        priority 101
        authentication {
            auth_type PASS
            auth_pass SHAREDPASSWORD
        }
        virtual_ipaddress {
                <select-an-ip>
        }

        track_script {
            chk_apache
        }
}

Create exactly the same file on the failover proxy, just change priority from 101 to 100.

Explanations of the apache check

The track_script "chk_apache" uses nagios' script check_http to ask a webpage to the local apache every three seconds - and checks for a 200 "OK" error code. If the test succeeds, it adds 2 points to the priority of the server.

The server with the highest priority gets the IP address. In effect, we would have the master with a priority of 101+2, and the failover with a priority of 100+2.

If the check fails on the master, its priority goes down to 100, and the IP migrates to the failover, which still has a priority of 100+2.

Adding a vhost

In Apache we use vhost declarations to define each reverse proxy FQDN. In Ubuntu/Debian systems these are found in /etc/apache2/sites-available, typically one per vhost using a suitably descriptive name. They can also be wrapped into a single file, or of course into the main apache conf file. As they are effectively (includes) of the Apache conf, every change requires an Apache restart to apply:

apache2ctl restart

In the Debian/Ubuntu model you also need to enable a site one it's been defined, which is done with a link to the /etc/apache2/sites-available/ file newly created:

cd /etc/apache2/sites-enabled
ln -s ../sites-available/yournewvhost

This approach allows you to quickly and easily take a specific site offline if there's a problem, just by deleting the link in /etc/apache2/sites-enabled and restarting Apache.

Assuming your sites will be https from the proxy outwards, start with a 301 to force https:

<VirtualHost *:80>

       ServerName my.domain.name
       ServerAlias my.alias.domain

Redirect 301 / https://my.domain.name

       ProxyPass / http://my.realserver.nameorIP/
       ProxyPassReverse / http://my.realserve.nameorIP/

       CustomLog /var/log/apache2/my.domain.name.access.log combined
       ErrorLog /var/log/apache2/my.domain.name.error.log

</VirtualHost>

And then add an SSL entry

<VirtualHost *:443>
       ServerName my.domain.name

       SSLEngine on
       SSLCertificateFile /etc/apache2/ssl/mycertificate.crt
       SSLCertificateKeyFile /etc/apache2/ssl/mykey.key
       SSLCertificateChainFile /etc/apache2/ssl/myintermediatecertificateifneeded.crt

       ProxyPass / http://myrealserver.nameorIP/
       ProxyPassReverse / http://my.domain.name/
       ProxyPassReverse / http://myrealserver.nameorIP/

       CustomLog /var/log/apache2/my.domain.name.access.log combined
       ErrorLog /var/log/apache2/my.domain.name.error.log
</VirtualHost>

If you want the SSL proxy to also connect to the target as SSL, change the ProxyPass URLs appropriately and add to the vhost SSL (*:443) part

SSLProxyEngine On

If you run into issues with SSL connects to the target service with Apache log entries like

Error during SSL Handshake with remote server

then you may need additional settings in the vhost statement; try

SSLProxyVerify none
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off

and if that works selectively comment out to optimise (least settings for desired functionality).

Enabling SSL on a VM hosting a CMS behind a proxy

The CMS tries to detect if it's running on SSL automatically by checking some SERVER environment variables. When SSL is served from the proxy, the CMS can't detect it by normal means, so it needs a bit of help. In our case the solution is to edit the .htaccess file and add this directive:

<IfModule mod_env.c>
   SetEnv HTTPS on
</IfModule>

This was the fix for both Joomla and Wordpress VMs

Maintenance Page

If you are working on a website and want to show a maintenance page for everyone except you, this can be done from the Apache vhost on the proxy.

<VirtualHost *:443>
        ServerName my.website.com

        ProxyPass / http://ip.add.re.ss/
        ProxyPassReverse / http://ip.add.er.ss/

RewriteEngine On
RewriteCond %{REMOTE_ADDR} !^my\.ip\.addr\.ess
RewriteCond %{REQUEST_FILENAME} !/index.html
RewriteCond %{REQUEST_FILENAME} !/css/app.css
RewriteCond %{REQUEST_FILENAME} !/logo.png
RewriteRule ^.*$ /var/www/maintenance/index.html [L]
Header Set Cache-Control "max-age=0, no-store"

        CustomLog /var/log/apache2/my.website.com.access.log combined
        ErrorLog /var/log/apache2/my.website.com.error.log
</VirtualHost>

Redirect source IP to VMs

On the vm vhost add/edit the following lines:

SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" forwarded
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined env=!forwarded
CustomLog ${APACHE_LOG_DIR}/access.log forwarded env=forwarded

And enable 2 modules:

a2enmod remoteip && a2enmod headers

Redirect except one IP

Note: This works only for apache 2.4 onwards. For older versions of apache find something around RewriteEngine.

<If "%{REMOTE_ADDR} !='my.ip.add.re.ss'">
Redirect 301 / https://my.website.com
</If>

Get client's real IP address

It is a well known problem that when you run an apache web server behind a proxy, you cannot get client real IP address. To solve this issue we just have to run next commands on each backend server:

sudo a2enmod remoteip
sudo echo "RemoteIPHeader X-Forwarded-For" >> /etc/apache2/apache2.conf
sudo sed -i "s/LogFormat \"%v:%p %h/LogFormat \"%v:%p %a/g" /etc/apache2/apache2.conf
sudo sed -i "s/LogFormat \"%h/LogFormat \"%a/g" /etc/apache2/apache2.conf
sudo apache2ctl graceful

How to reverse proxy WebSockets

Enable the websockets module if not already

a2enmod proxy_wstunnel

Add these lines before the ProxyPass lines

        RewriteEngine On
        RewriteCond %{HTTP:Upgrade} =websocket [NC]
        RewriteRule /(.*)           ws://172.17.16.17/$1 [P,L]
        RewriteCond %{HTTP:Upgrade} !=websocket [NC]
        RewriteRule /(.*)           http://172.17.16.17/$1 [P,L]