Categories
Development

SSL für Apache HTTP

Der letzte Post endete mit einer Fehlermeldung:

Analyse

Der Fehler trat auf, nachdem ich meinen PoC von meinem Entwicklungsrechner in die Cloud transferierte und dort laufen ließ und dort testen wollte.

Auf der Console des Servers sieht der Aufruf mittels "curl http://localhost/private/index.html" auch erstmal gut aus.

Der Zugriff aus dem Browser mittels IP "http://<server-ip>/private/index.html" schlägt mit obiger Fehlermeldung komplett fehl.

Lokal nachstellen

Um das Problem lokal auf dem Entwicklerlaptop nachstellen zu können, muss erstmal die lokale (Docker-)IP ermittelt werden:

ping host.docker.internal

Ping wird ausgeführt für host.docker.internal [192.168.2.149] mit 32 Bytes Daten:
Antwort von 192.168.2.149: Bytes=32 Zeit<1ms TTL=128
Antwort von 192.168.2.149: Bytes=32 Zeit<1ms TTL=128
Antwort von 192.168.2.149: Bytes=32 Zeit<1ms TTL=128

Der Aufruf der PRIVATE Page, nach OneLogin Login versteht sich, funktioniert über localhost:

Der Aufruf der PRIVATE Page funktioniert nicht über die IP:

Auszug aus dem Logfile des Apache HTTP Servers:

reverseproxy_1  | [Thu Jun 09 13:00:54.048377 2022] [auth_openidc:error] [pid 256:tid 140563693147904] [client 172.24.0.1:33902] oidc_authenticate_user:
 the URL hostname (localhost) of the configured OIDCRedirectURI does not match the URL hostname of the URL being accessed (192.168.2.149): the "state" a
nd "session" cookies will not be shared between the two!, referer: http://192.168.2.149/
reverseproxy_1  | 172.24.0.1 - - [09/Jun/2022:13:00:54 +0000] "GET /private/index.html HTTP/1.1" 500 531

Der Fehler besagt also, "the URL hostname (localhost) of the configured OIDCRedirectURI does not match the URL hostname of the URL being accessed (192.168.2.149)".

OneLogin Konfigurationsanpassung

Die Redirect URIs Liste muss um die IP des Servers erweitert werden:

Allerdings ist http als Protokoll lediglich für localhost zugelassen, nicht aber für alle anderen URIs, wie zB unsere Server IP oder später der Domain Name des Servers.

Die Redirect URI muss also mit https eingetragen werden.

Daraus ergibt sich allerdings auch, dass unser Server über https erreichbar sein muss!

Apache mit HTTPS

Ein selbstsigniertes Zertifikat ist für unseren PoC vollkommen ausreichend. Eine Signierung durch zB Let's Encrypt ist nicht notwendig.

Für diesen PoC brauchen wir lediglich den Reverse Proxy, der im ersten Schritt eine Seite über http und https ausliefern kann. Später kommt dann die Authentifizierung mit OneLogin hinzu.

Schritt 1: http

Das Dockerfile des Apache aus den vorherigen PoCs uA schon mit OpenID Module:

FROM httpd:2.4
RUN apt update && apt install -y \
	libapache2-mod-auth-openidc \
	ca-certificates
RUN cp /usr/lib/apache2/modules/mod_auth_openidc.so /usr/local/apache2/modules/
RUN mv conf/httpd.conf conf/container_httpd.conf
CMD ["httpd-foreground"]

Gestartet wird mit Docker Compose:

version: '3.8'
services:

  reverseproxy:
    build: ./reverseproxy
    hostname: reverseproxy
    volumes:
      - ./reverseproxy/public_html:/usr/local/apache2/htdocs
      - ${PWD}/reverseproxy/conf/reverseproxy_httpd.conf:/usr/local/apache2/conf/httpd.conf
      - ${PWD}/reverseproxy/conf/reverseproxy.conf:/usr/local/apache2/conf/reverseproxy.conf
      - ${PWD}/reverseproxy/conf/reverseproxy-ssl.conf:/usr/local/apache2/conf/reverseproxy-ssl.conf
    ports:
      - 80:80
      - 443:443

Die Einbindung der Konfigurationsdateien erfolgt über reverseproxy_httpd.conf, die in den Container als httpd.conf hineinkopiert wird:

# load original configuration first
Include conf/container_httpd.conf

# customized configuration
ServerName reverseproxy
Include conf/reverseproxy.conf
Include conf/reverseproxy-ssl.conf

Die Konfiguration für http erfolgt in reverseproxy.conf:

<VirtualHost *:80>
    ServerAdmin deringo@github.com
    DocumentRoot "/usr/local/apache2/htdocs"
    ServerName localhost
</VirtualHost>

Die Konfiguration für https wird später in reverseproxy-ssl.conf erfolgen, die Datei ist im ersten Schritt leer.

Starten mittels Docker Compose:

docker-compose up

Die Seite ist sowohl über localhost, als auch über IP erreichbar:

Schritt 2: Port 443

Der Server soll über den https-Port 443 die Seiten ausliefern.

Dazu wird die http/Port 80-Konfiguration angepasst kopiert.

Wichtig ist auch das "Listen 443", das am Anfang hinzugefügt werden muss.

Listen 443
<VirtualHost *:443>
    ServerAdmin deringo@github.com
    DocumentRoot "/usr/local/apache2/htdocs"
    ServerName localhost
</VirtualHost>

Server neu durchstarten:

docker exec -it selfsignedtest_reverseproxy_1 bash
apachectl configtest && apachectl restart

Die Seite ist über http://localhost:443 erreichbar. Richtig: http, nicht https! Verschlüsselt wird hier noch nix.

Schritt 3: https

Als ersten Teilschritt wird die reverseproxy-ssl.conf mit der Default-SSL-Konfiguration überschrieben und alle Kommentare gelöscht.

Folgende Änderungen werden gemacht:

ServerName localhost:443
ServerAdmin deringo@github.com

SSLEngine off

#SSLCertificateFile "/usr/local/apache2/conf/server.crt"
#SSLCertificateKeyFile "/usr/local/apache2/conf/server.key"

Server neu starten, anschließend ist die Seite weiterhin unverschlüsselt über http://localhost:443 erreichbar.

Als nächster Teilschritt wird SSLEngine on gestellt und die beiden CertificateFile Einträge wieder ent-kommentiert.
Außerdem müssen die Module mod_socache_shmcb.so und mod_ssl.so geladen werden.

LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule ssl_module modules/mod_ssl.so

ServerName localhost:443
ServerAdmin deringo@github.com

SSLEngine on

SSLCertificateFile "/usr/local/apache2/conf/server.crt"
SSLCertificateKeyFile "/usr/local/apache2/conf/server.key"

Ein Neustart des Servers schlägt jetzt fehl, da die CertificateFiles noch nicht existieren.

Certificate Files erzeugen lassen:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /usr/local/apache2/conf/server.key -out /usr/local/apache2/conf/server.crt

Server neu durchstarten und der unverschlüsselte Aufruf wird abgewiesen:

Der verschlüsselte Aufruf über https://localhost funktioniert:

Allerdings wird die Seite als "Nicht sicher" angezeigt und es musste einmalig ein entsprechender Hinweis weggeklickt werden, da das Zertifikat nicht von einer vertrauenswürdigen Stelle signiert wurde.

Da am Ende praktisch nichts von der Standard-Konfiguration für SSL geändert wurde, wird die Konfiguration wieder vereinfacht:

LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule ssl_module modules/mod_ssl.so

Include conf/extra/httpd-ssl.conf

Schritt 4: OneLogin

Die OneLogin Konfiguration wird hinzugefügt:

## Default SSL
LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule ssl_module modules/mod_ssl.so

Include conf/extra/httpd-ssl.conf

##
LoadModule proxy_module modules/mod_proxy.so
LoadModule xml2enc_module modules/mod_xml2enc.so
LoadModule proxy_html_module modules/mod_proxy_html.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so
LoadModule proxy_http_module modules/mod_proxy_http.so

LoadModule auth_openidc_module modules/mod_auth_openidc.so

<VirtualHost localhost:443>
    ServerAdmin deringo@github.com
    DocumentRoot "/usr/local/apache2/htdocs"
    ServerName localhost:443
    ## mod_auth_openidc
    ## https://github.com/zmartzone/mod_auth_openidc
    
    #this is required by mod_auth_openidc
    OIDCCryptoPassphrase <INSERT-HERE a-random-secret>

    OIDCProviderMetadataURL <INSERT-HERE>

    OIDCClientID <INSERT-HERE>
    OIDCClientSecret <INSERT-HERE>
    # OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content
    OIDCRedirectURI https://localhost/private/redirect_uri

    ## OIDCScope params
    ## to put params including roles into header
    OIDCScope "openid email profile groups params"


    <Location /private/>
        AuthType openid-connect
        Require valid-user
    </Location>

</VirtualHost>                                  

Alle Seiten sind über https erreichbar.

Aber leider auch die Private Page OHNE dass vorher ein Login über OneLogin erfolgen musste.

Irgendwie klappt das nicht mit dem überschreiben/erweitern der default SSL Konfiguration. Also doch alles in eine Datei:

LoadModule proxy_module modules/mod_proxy.so
LoadModule xml2enc_module modules/mod_xml2enc.so
LoadModule proxy_html_module modules/mod_proxy_html.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so
LoadModule proxy_http_module modules/mod_proxy_http.so

LoadModule auth_openidc_module modules/mod_auth_openidc.so



LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
LoadModule ssl_module modules/mod_ssl.so

Listen 443

SSLCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3DES
SSLProxyCipherSuite HIGH:MEDIUM:!MD5:!RC4:!3DES

SSLHonorCipherOrder on 

SSLProtocol all -SSLv3
SSLProxyProtocol all -SSLv3

SSLPassPhraseDialog  builtin

SSLSessionCache        "shmcb:/usr/local/apache2/logs/ssl_scache(512000)"
SSLSessionCacheTimeout  300


##
## SSL Virtual Host Context
##

<VirtualHost _default_:443>

#   General setup for the virtual host
ServerName localhost:443
ServerAdmin deringo@github.com
DocumentRoot "/usr/local/apache2/htdocs"
ErrorLog /proc/self/fd/2
TransferLog /proc/self/fd/1

#   SSL Engine Switch:
#   Enable/Disable SSL for this virtual host.
SSLEngine on

SSLCertificateFile "/usr/local/apache2/conf/server.crt"
SSLCertificateKeyFile "/usr/local/apache2/conf/server.key"

<FilesMatch "\.(cgi|shtml|phtml|php)$">
    SSLOptions +StdEnvVars
</FilesMatch>
<Directory "/usr/local/apache2/cgi-bin">
    SSLOptions +StdEnvVars
</Directory>

BrowserMatch "MSIE [2-5]" \
         nokeepalive ssl-unclean-shutdown \
         downgrade-1.0 force-response-1.0

CustomLog /proc/self/fd/1 \
          "%%t %%h %%{SSL_PROTOCOL}x %%{SSL_CIPHER}x \"%%r\" %%b"




    ## mod_auth_openidc
    ## https://github.com/zmartzone/mod_auth_openidc
    
    #this is required by mod_auth_openidc
    OIDCCryptoPassphrase <INSERT-HERE a-random-secret>

    OIDCProviderMetadataURL <INSERT-HERE>

    OIDCClientID <INSERT-HERE>
    OIDCClientSecret <INSERT-HERE>
    # OIDCRedirectURI is a vanity URL that must point to a path protected by this module but must NOT point to any content
    OIDCRedirectURI https://localhost/private/redirect_uri

    ## OIDCScope params
    ## to put params including roles into header
    OIDCScope "openid email profile groups params"


    <Location /private/>
        AuthType openid-connect
        Require valid-user
    </Location>

</VirtualHost>

So funktioniert es wie gewünscht und die Private Page wird erst nach Login angezeigt.

Allerdings nur über localhost. Nicht über die IP.

Die OIDCRedirectURI wird von https://localhost/private/redirect_uri auf https://192.168.2.149/private/redirect_uri geändert und schon funktioniert es genau anders herum: Nur über die IP, nicht über localhost.

Das ist nicht ganz so, wie ich mir das vorgestellt habe, aber für den PoC sollte es ausreichend sein.

Schritt 5: Nur https

Alles was über http rein kommt, soll über https weitergeleitet werden:

LoadModule rewrite_module modules/mod_rewrite.so
<VirtualHost *:80>
    ServerAdmin deringo@github.com
    DocumentRoot "/usr/local/apache2/htdocs"
    ServerName localhost
    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteCond %%{HTTPS} off
        RewriteRule (.*) https://%%{HTTP_HOST}%%{REQUEST_URI} [R=301,L]
    </IfModule>
</VirtualHost>

Schritt 6: Docker

Die Generierung der Zertifikate erfolgte im laufenden Container. Dieser Schritt soll schon beim Erzeugen des Images erfolgen.

Ein Problem war, dass dies interaktiv erfolgen musste. Mit dem Parameter -subj "/" können Key & Zertifikat ohne Interaktion generiert werden. Im Subject könnten Daten eingetragen werden, dies ist aber für den PoC gar nicht erforderlich.

Einmal der Befehl vollständig, wie er im Container ausgeführt werden könnte:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /usr/local/apache2/conf/server.key -out /usr/local/apache2/conf/server.crt -subj "/"

Der Befehl wird in das Dockerfile hinzugefügt.

Schritt 7: Trusted Certs

Aus den Erkenntnissen des letzten Posts folgt, dass die Möglichkeit vorgesehen werden muss, eine vertrauenswürdige Zertifikatskette einzubinden.

Dazu müssen die *.crt-Dateien in das conf-Verzeichnis kopiert werden.

FROM httpd:2.4
RUN apt update && apt install -y \
	libapache2-mod-auth-openidc \
	ca-certificates
RUN cp /usr/lib/apache2/modules/mod_auth_openidc.so /usr/local/apache2/modules/
RUN mv conf/httpd.conf conf/container_httpd.conf
RUN openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /usr/local/apache2/conf/server.key -out /usr/local/apache2/conf/server.crt -subj "/"
COPY ./conf/*.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates
CMD ["httpd-foreground"]

Schritt 8: ShowHeaders

Ich hatte ShowHeaders zwischendurch vermisst, so kommt es am Ende auch wieder mit rein.

Auch das ShowHeaders Image wird für Trusted Certs erweitert.

GitHub

Die Dateien zu diesem Post sind im OneLogin-GitHub-Projekt unter version7 zu finden.

Leave a Reply

Your email address will not be published. Required fields are marked *