Apache Reverse Proxy

Aus UUGRN

Dieser Artikel beschreibt, wie man mit Apache einen Reverse Proxy aufbauen kann. Üblicherweise wird das verwendet, um Applikationen, die auf einem nicht direkt vom Internet aus erreichbaren Server laufen auf einem externen Webserver zugreifbar zu machen.

Anwendungsfall[Bearbeiten]

Der Focus dieses Artikels besteht allerdings darin den Anwendungsfall zu beschreiben, bei dem ein Webserver hinter einem NAT-Router mit und ohne PortForwarding betrieben wird.

Das Ziel aller Bemühungen soll in jedem Fall sein: Die Website http://www.foo.example.com/ soll auf der IP-Adresse des Proxy Servers auflaufen und von dort aus zum RealServer durchgereicht werden.

Wir gehen in den Beispielen davon aus, dass wir root-Rechte oder vergleichtbare Berechtigungen für die Konfiguration beider Webserver haben. Es muss die Konfiguration verändert werden können, es müssen ggf. Softwarepakete installiert werden, Apache muss neu gestartet werden.

Begriffe[Bearbeiten]

Im folgenden werden die Begriffe Proxy, Realserver, öffentliche IP-Adresse und Website wie folgt verwendet:

Der Proxy ist ein Apache, der auf einem per Internet erreichbaren Server läuft, z.B. auf einem Root-Server. Der Proxy-Server verwendet in den folgenden Beispielen verschiedene Websites auf einer oder verschiedenen offiziellen IP-Adressen, z.B. als Namebased Virtual Hosts. Die DNS-Einträge der jeweiligen Websites zeigen auf die offiziellen IP-Adressen.

Der RealServer steht irgendwo, wo er nicht ohne weiteres von extern erreichbar ist, etwa hinter einem NAT-Router, Firewall oder ähnlichem. Es werden beide Szenarien betrachtet: Der RealServer ist über eine (bekannte) IP-Adresse mit Port-Forwarding oder ähnlichem grundsätzlich von extern erreichbar, hat aber eine dynamisch vergebene IP-Adresse, die sich täglich oder häufiger ändert. Die Auflösung der IP-Adresse erfolgt über einen DynDNS-Hostname, z.B. example.homeip.net.

In einem erweiterten Szenario läuft der RealServer irgendwo, kann aber mindestens eine SSH-Verbindung Richtung Proxy aufbauen. Im einfachsten Fall ist es hier ein NAT-Router, der keine statischen Portweiterleitungen nach innen hat oder welche, die nicht selbst verwaltet werden können (z.B. Firmennetz, LAN-Party, ...)


Fall 1: RealServer per DynDNS von aussen erreichbar[Bearbeiten]

Für diesen Fall benötigen wir den DynDNS-Namen, hier example.homeip.net. Es wird angenommen, dass hinter diesem Hostname eine öffentliche IP-Adresse steckt und dass auf Port 80/tcp entweder direkt oder via Port-Forwarding auf dem NAT-Router direkt der RealServer angesprochen werden kann.

ProxyPass auf dem Proxy[Bearbeiten]

Der Zugriff für ProxyPass erfolgt entsprehend, z.B.

<VirtualHost *:80>
    ServerName          www.foo.example.com
    ProxyPass / http://example.homeip.net/ connectiontimeout=5 timeout=30
</VirtualHost>

(gekürzt auf die wesentlichen Direktiven)

Fall 2: RealServer nur via SSH-Tunnel erreichbar[Bearbeiten]

In diesem Fall ist der RealServer irgendwo. Der RealServer verfügt über eine wie-auch-immer Methode, um eine SSH-Verbindung zum Proxy aufbauen zu können und dass es möglich ist hier PortForwading zu betreiben.

SSH-Tunnel[Bearbeiten]

Auf dem RealServer einzurichten:

~/.ssh/config
ServerAliveInterval = 15
ServerAliveCountMax = 8 

Host example_tunnel
        Hostname proxy.example.com
        RemoteForward 127.0.0.1:20080 127.0.0.1:80
        RemoteForward 127.0.0.1:20443 127.0.0.1:443
        BatchMode yes
        ConnectionAttempts 5
        ConnectTimeout 10
        ExitOnForwardFailure yes
        KbdInteractiveAuthentication no
        PasswordAuthentication no
        StrictHostKeyChecking yes

Funktional relevant sind hier folgende Zeilen:

        RemoteForward 127.0.0.1:20080 127.0.0.1:80
        RemoteForward 127.0.0.1:20443 127.0.0.1:443


Tunnel aufbauen
ssh -N example_tunnel
Achtung
StrictHostKeyChecking yes verhindert, dass beim ersten Verbindungsaufbau nach example_tunnel nach dem unbekannten Hostkey gefragt wird. Dieser sollte bereits in ~/.ssh/known_hosts enthalten sein (# StrictHostKeyChecking yes).

ProxyPass auf dem Proxy[Bearbeiten]

Damit bekommen wir auf dem Proxy die beiden Ports 20080 und 20443, die 1:1 mit den Ports 80 und 443 auf dem RealServer verbunden sind. Der Zugriff für ProxyPass erfolgt entsprehend, zB

<VirtualHost *:80>
    ServerName          www.foo.example.com
    ProxyPass / http://127.0.0.1:20080/ connectiontimeout=5 timeout=30
</VirtualHost>

(gekürzt auf die wesentlichen Direktiven)


Namensbasierte VirtualHosts auf dem RealServer[Bearbeiten]

Egal ob der RealServer via DynDNS oder via SSH-Tunnel erreicht wird: Im Normalfall stellt der Proxy einen HTTP-Request hin zum konfigurierten RealServer, zum Beispiel http://example.homeip.net/. Wenn auf dem RealServer verschiedene namensbasierte VirtualHosts eingerichtet werden, können diese im Normalfall nicht erreicht werden, denn hierfür wird bei einer HTTP/1.1 Anfrage der Wert aus dem Host Request-Header verwendet, hier wäre das also Host: example.homeip.net und nicht Host: www.foo.example.com oder bei SSH-Tunnel Host: 127.0.0.1:<tunnelport>.

Im Normalfall landen Anfragen für unbekannte Hostnamen im default-VirtualHost von Apache. Will man mehr als nur eine Webseite auf einem RealServer betreiben, muss man sich hier etwas ausdenken, zum Beispiel verschiedene DynDNS-Einträge mit der gleichen IP-Adresse, mehrere CNAMEs auf einen DynDNS-Hostname, bei SSH-Tunnel ggf. mehrere verschiedene Hostnamen in /etc/hosts für 127.0.0.1.

Praktischerweise müssen wir hier keine DNS-Hacks veranstalten, da mod_proxy die ProxyPreserveHost Directive kennt, mit der der ursprüngliche Host: www.foo.example.com im Request an den RealServer erhalten bleibt. Der RealServer kann nun also wieder namensbasiert unterscheiden und wir benötigen nicht für jede Website einen eigenen DNS-Eintrag für den RealServer (CNAME auf example.homeip.net oder A-Record mit 127.0.0.1 für den SSH-Tunnel).

VirtualHost auf dem Proxy
# -----------------------
# www.foo.example.com
# -----------------------
<VirtualHost *:80>
    ServerAdmin         proxy@example.com
    ServerName          www.foo.example.com

    ErrorLog            …
    CustomLog           …
    CustomLog           …

# bei SSH Tunnel:
#    ProxyPass / http://127.0.0.1:20080/ connectiontimeout=5 timeout=30
#    ProxyPassReverse / http://127.0.0.1:20080/

# bei DynDNS:
    ProxyPass /        http://example.homeip.net/ connectiontimeout=5 timeout=30
    ProxyPassReverse / http://example.homeip.net/

# in jedem Fall wollen wir, dass der RealServer den ursprünglichen Host-Request Header erhält
    ProxyPreserveHost On
    
</VirtualHost>
VirtualHost auf dem RealServer
<VirtualHost *:80>
        ServerAdmin webmaster@example.com

        ServerName www.foo.example.com

        DocumentRoot /var/www/www.foo.example.com/
        <Directory />
                Options FollowSymLinks
                AllowOverride None
        </Directory>
        <Directory /var/www/www.foo.example.com/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride None
                Order allow,deny
                allow from all
        </Directory>

…

# Wir wollen, dass unser Apache als Client-IP-Adresse *nicht* den Proxy sieht 
# sondern die IP-Adresse des Clients, der sich zum Proxy verbunden hat.
# Das erledigt mod_remote_ip:  
#
# http://httpd.apache.org/docs/2.3/mod/mod_remoteip.html
#
        RemoteIPHeader X-Forwarded-For
        RemoteIPTrustedProxy sigsys.de
        RemoteIPTrustedProxy 127.0.0.1
        RemoteIPProxiesHeader X-Forwarded-By 

…
</VirtualHost>

mod_remoteip[Bearbeiten]

Bei allen Reverse-Proxy Szenarien besteht möglicherweise das Problem, dass in den Logfiles des RealServers nur die IP-Adresse des Proxy oder gar 127.0.0.1 durch den SSH-Tunnel erscheint.

mod_remoteip für Apache 2.2 bauen[Bearbeiten]

Das Modul mod_remoteip ist leider bei Apache 2.2 noch nicht verfügbar, wurde aber backported. siehe: http://www.serverphorums.com/read.php?10,340545

# wget "https://raw.github.com/gist/1042237/078b18a627657151fb90ddc027caad67e7191e89/apache-2.2-mod_remoteip.c"
# mv apache-2.2-mod_remoteip.c mod_remoteip.c
# apxs2 -i -a -c mod_remoteip.c
# /etc/init.d/apache2 restart

(getestet unter Debian Squeeze/amd64)


VirtualHost[Bearbeiten]

VirtualHost Config auf dem RealServer'
<VirtualHost *:80>
        ServerName www.foo.example.com

        RemoteIPHeader X-Forwarded-For
        RemoteIPTrustedProxy proxy.example.com
        RemoteIPTrustedProxy 127.0.0.1
        RemoteIPProxiesHeader X-Forwarded-By 

</VirtualHost>

Motivation und Variationen[Bearbeiten]

Eine der Hauptmotivationen hinter diesem Setup ist, den eigentlichen RealServer möglichst "im Hintergrund" zu behalten. Für Aussenstehende ist bei sorgfältiger Konfiguration nicht ersichtlich, dass verschiedene VirtualHosts durch andere Server bedient werden.

  • RealServer unsichtbar hinter einer dynamischen IP betreiben
  • IPv4-RealServer per IPv6 erreichbar machen
  • IPv6-RealServer via IPv4 erreichbar machen
  • RealServer via Mobilfunk anbinden und hinter einem Rootserver verstecken
  • RealServer hinter einer komplexeren Firewall/Proxy-Kaskade betreiben, bei der als Minimalvoraussetzung eine SSH-Session nach außen aufgemacht werden kann, siehe auch Firewall Piercing.
  • einen RealServer hinter mehreren Proxy-Servern betreiben (Fallback ...)
  • mehrere RealServer hinter einem Proxy Server betreiben
  • Kombination von 2 Dynamischen IP-Adressen für Proxy und RealServer (nicht einfach rückverfolgbar)
  • Kaskade aus mehreren Reverse-Proxies, zB CloudServer.org==(via SSH-Tunnel)==>Rootserver.de ==(SSH Tunnel)==>DSL-Anschluss/DynDNS