Sonntag, 8. Juli 2012

Outlook RPC-over-HTTP Reverse Proxy - finally! (Updated)

Ich gebe zu, ich bin ein Fan eleganter Lösungen... der Gedanke, einen Exchange-Server dem Internet preiszugeben - total unelegant!

Dazu bietet Microsoft ja auch diverse Lösungen mit dem eigenen ISA oder Forefront TMG. Hat man aber keine echte DMZ (dazu ist die Firma einfach zu klein) bzw. sind die Internetdienste sowieso auf einen Root-Server ausgelagert, bietet sich eine eigene Lösung an, die auf zusätzliche MS Produkte und Lizenzen verzichtet.


Seit vielen Jahren setze ich auf Apache mit mod_proxy für die Anbindung von Active-Sync Clients bzw. für Outlook-Web-Access (OWA). Eine perfekte Anleitung findet sich z.B. bei MSXFAQ.de.


Leider funktionierte Outlooks RPC-over-HTTP noch nie über diesen Reverse-Proxy, da das Apache Team seit 2.0.55 nicht mehr die "Verbiegungen" des HTTP durch Microsofts RPC_DATA Erweiterungen toleriert.

Ich hatte mich zwischenzeitlich an SQUID versucht, allerdings finde ich, diese Software ist zu groß und zu mächtig, als dass man sie nur für Reverse-Proxy-Aufgaben für eine Handvoll URLs nutzen sollte.

Die Klagen der externen Outlook-Benutzer (die vorübergehend mit IMAP arbeiten mussten) bewegten mich nun doch nochmal, nach einer Lösung zu suchen. Diesmal war ich mit Pound erfolgreich.

Obwohl Debian Squeeze bereits v2.5 mitbringt (welches theoretisch bereits RPC-over-HTTP können sollte), habe ich mich für die aktuelle v2.6 vom Dezember 2011 entschieden - u.A. weil das Changelog in der v2.6c folgende Änderungen enthält.

Release 2.6c

Enhancements:
    - added support for OpenSSL 1.0
    - added some more detailed error logging

Bug fixes:
    - fix for RewriteLocation
    - fix for HTTPS back-ends
    - fix for RPC support
    - fix for possible request smuggling by using multiple headers


Die Konfiguration ist fast selbsterklärend.

## /etc/pound/pound.cfg
##
## see pound(8) for details


######################################################################
## global options:

User            "pound"
Group           "pound"
LogLevel        1

## check backend every X secs:
Alive           30
Grace           3600

# poundctl control socket
Control "/var/run/pound/poundctl.socket"

######################################################################
## listen, redirect and ... to:

ListenHTTPS
        Address 1.2.3.4
        Port    443

        Client 120

        ## allow PUT and DELETE also (by default only GET, POST and HEAD)?:
        xHTTP   4

        Cert            "/etc/pound/pound.pem"
        CAlist          "/etc/ssl/certs/ca.crt"

        HeadRemove      "X-Forwarded-Proto"
        AddHeader       "X-Forwarded-Proto: https"
        Service "exchange"
                IgnoreCase      1
                URL             "^/owa.*|^/Microsoft-Server-ActiveSync.*|^/rpc.*|^/exchange.*|^/exchweb.*|^/public.*|^OAB.*|^/Autodiscover.*"

                BackEnd
                        Address wan.unser.firmen.server.de
                        Port    443
                        HTTPS
                        TimeOut 180
                End

                Session
                        Type IP
                        TTL 1800
                End
        End

        Service
                Redirect        301     "https://anderer.ssl.content.de:8443/"
        End
End

Da der Apache auf dem gleichen Server läuft, habe ich dessen SSL-Port auf 8443 verlegt, so dass der Proxy auf dem Standard-Port lauschen kann (Outlook scheint man nicht von einem alternativen Port überzeugen zu können...).

Da man für die lokale Zertifikatsdatei den ungesicherten Schlüssel (ohne Passwort) anfügen muss,

        Cert            "/etc/pound/pound.pem"

sollte man darauf achten, dass die Datei einem chmod 600 unterzogen wird. Da sie beim Daemon-Start gelesen wird, kann sie root gehören, auch wenn der Daemon mit anderen Nutzerrechten läuft.

Ich sehe im Log zwar einige Meldungen:

pound: (7fd361b2b700) e500 for 87.3.2.1 response error read
...
pound: (7fd361b2b700) Can't read BIO_f_base64
...

allerdings scheint alles zu funktionieren.

Nachdem der  Exchange-Server für HTTP-over-RPC fit gemacht wurde (perfekte Anleitung bei MSXFAQ.de), der Port entsprechend weitergeleitet wurde und die Firewall abgedichtet wurde (Die Firewall akzeptiert auf dem weitergeleiteten HTTPS-Port natürlich ausschließlich Verbindungen vom Proxy-Server.), kann in den Outlook-Kontoeinstellungen unter Weitere Einstellungen -> Verbindung -> Verbindung mit Microsoft über HTTP herstellen -> Exchange-Proxyeinstellungen der Reverse Proxy eingetragen werden.
Ich habe die Erfahrung gemacht, dass sich der Outlook Client zumindest bei der Ersteinrichtung innerhalb der Domäne befinden sollte, da sonst Server- und Benutzerauflösung nicht funktioniert. Die Proxyeinstellung kann man vornehmen, sobald der lokale Check durchgeführt wurde.

Geht das auch mit RDP als RPC-over-HTTP???

Falls jemand mit Pound eine Verbindung zu einem RDP-Frondendserver hinbekommt, würde ich mich sehr über einen Kommentar freuen. Meine Zeilen:

        Service "rdp"
               IgnoreCase      1
               URL             "^/rpc\/rpcproxy\.dll\?localhost\:3388.*"

               BackEnd
                       Address wan.unser.firmen.server.de
                       Port    8443
                       HTTPS
                       TimeOut 180
               End

               Session
                       Type IP
                       TTL 1800
               End
       End

bleiben leider ohne den ersehnten Erfolg (die Firewall leitet hier den Port 8443 auf den HTTPS des Frontendservers).

Update (2013-05-12):

Nachdem ich ein Update auf Debian Wheezy gemacht habe, funktioniert der Pound Proxy wegen der geänderten OpenSSL-Version nicht mehr. Inzwischen ist im Wheezy Repository ebenfalls die Version 2.6 des Pound Proxy. Allerdings bekomme ich dort keine Verbindung mit meinem Exchange 2007.

pound: BIO_do_handshake with :443 failed: error:1412F152:SSL routines:SSL_PARSE_SERVERHELLO_TLSEXT:unsafe legacy renegotiation disabled

Beim Kompilieren aus den 2.6er Quellen funktioniert wieder alles.

Update (2014-06-03):

Da wir inzwischen Mails für mehr als einen Active Directory Forrest hosten, wurde es erforderlich, dass die Autodiscovery Möglichkeiten richtig genutzt werden.
Dazu muss neben dem DNS-Eintrag autodiscover.mail.domain auch ein entsprechender Alternative Name im Zertifikat des Proxies eingetragen sein.
Eine Umleitung eines unverschlüsselten (http) Apache Virtual Host unter der Adresse auf den Proxy ohne neues Zertifikat funktionierte bei mir nicht.


Update (2014-11-11):

Ich habe nun herausgefunden, was die Fehlermeldung zu bedeuten hat:

pound: BIO_do_handshake with :443 failed: error:1412F152:SSL routines:SSL_PARSE_SERVERHELLO_TLSEXT:unsafe legacy renegotiation disabled

Es handelt sich um die Verbindung zu meinem Exchange Server. In meiner pound Konfiguration nutze ich dafür ein HTTPS Backend.

Darauf gestoßen bin ich bei ExploreSecurity. Sofern ich per openssl s_client eine Verbindung unter der Angabe von -no_legacy_server_connect aufzubauen versuche, bekomme ich immer den obigen Fehler. Sobald ich jedoch eine -cipher Liste dazu angebe, kommt eine Verbindung zustande.

Leider benutzt pound die Cipher Liste nur als Server. Für die Client-Verbindung kann ich keine Cipher-Liste angeben, was zu dem Fehler führt.

Ab der Version 2.7d kann nun auch für das Backend eine Cipher Liste angegeben werden:

Release 2.7d

Enhancements:
    - added "Disable PROTO" directives (fix for Poodle vulnerability)
    - added Cert, Disable and Cipher directives for HTTPS back-ends.

Meine Konfiguration sieht nun so aus:

## /etc/pound/pound.cfg
##
## see pound(8) for details


######################################################################
## global options:

User            "pound"
Group           "pound"
LogLevel        1

## check backend every X secs:
Alive           30
Grace           3600

# poundctl control socket
Control "/var/run/pound/poundctl.socket"

######################################################################
## listen, redirect and ... to:

ListenHTTPS
        Address 1.2.3.4
        Port    443

        Client 120

        ## allow PUT and DELETE also (by default only GET, POST and HEAD)?:
        xHTTP   4

        Cert            "/etc/pound/pound.pem"
        CAlist          "/etc/ssl/certs/ca.crt"

        Disable         SSLv3

        HeadRemove      "X-Forwarded-Proto"
        AddHeader       "X-Forwarded-Proto: https"
        Service "exchange"
                IgnoreCase      1
                URL             "^/owa.*|^/Microsoft-Server-ActiveSync.*|^/rpc.*|^/exchange.*|^/exchweb.*|^/public.*|^OAB.*|^/Autodiscover.*"

                BackEnd
                        Address wan.unser.firmen.server.de
                        Port    443
                        HTTPS
                        Ciphers "ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM"
                        TimeOut 180
                End

                Session
                        Type IP
                        TTL 1800
                End
        End

        Service
                Redirect        301     "https://anderer.ssl.content.de:8443/"
        End
End


Damit das Abschalten des SSLv3 Protokolls funktioniert, muss man darauf achten nur ein Space bzw. ein TAB-Stop zwischen Disable und SSLv3 zu lassen.
Alternativ ist ein winziger Patch erforderlich:

--- config.c.orig       2014-11-11 19:51:04.631219007 +0100
+++ config.c    2014-11-11 19:55:04.042209120 +0100
@@ -1450,3 +1450,3 @@
     || regcomp(&SSLAllowClientRenegotiation, "^[ \t]*SSLAllowClientRenegotiation[ \t]+([012])[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
-    || regcomp(&DisableProto, "^[ \t]*Disable[ \t](SSLv2|SSLv3|TLSv1|TLSv1_1|TLSv1_2)[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
+    || regcomp(&DisableProto, "^[ \t]*Disable[ \t]+(SSLv2|SSLv3|TLSv1|TLSv1_1|TLSv1_2)[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)
     || regcomp(&SSLHonorCipherOrder, "^[ \t]*SSLHonorCipherOrder[ \t]+([01])[ \t]*$", REG_ICASE | REG_NEWLINE | REG_EXTENDED)

Update (2015-01-07):

Leider hatte die Version 2.7d einen Fehler in der URL-Redirect Generierung, so wurde z.B. ein "-" Bindestrich im Domain-Namen durch sein URL-Encoding ersetzt und folglich konnte die umgeleitete URL nicht gefunden werden: "example-corp.com" wurde zu "example%2Dcorp.com".

Die Version 2.7f ist um diesen Fehler bereinigt und enthält ebenfalls den Disable Patch oben.