apache2 Proxy mit Cache

apache2 Proxy mit Cache

Ich nutze im ioBroker als Wetter Adapter Weather Undergound. Damit rasch das eigene Wetter abgefragt und in vis-2 mit Material-Design als HTML-Ausgabe dargestellt. Einfach, schlicht - tut es mir. Nur beziehe ich die Icons direkt von Weather Underground. Das muss doch auch anders gehen: Also hab ich mal eben einen apache2 Proxy davorgesetzt. Damit sind meine Zugriffe lokal und zusätzlich noch mit einem Cache. Es lohnt ja nicht jedes Bild immer und immer wieder zu laden - wenn man es doch in einem Cache ablegen könnte.

Bevor einer fragt: Ja, das ist sicherlich nicht sicher einfach so alles ohne irgendwelche Sicherheitsmaßnahmen umzusetzen. Das weiß ich auch. Zum einen läuft das bei mir nur intern und nicht öffentlich im Internet, zum anderen ging es mir um die Funktion. Ich wollte das einfach mal mit apache2 umgesetzt haben. Nicht mehr, nicht weniger 😉 Und betrachte dies als Beispiel: Bei mir war die Ausgangslage ioBroker. Wer weiß, was es bei Dir ist 😛

Das hier will ich haben:

Weather Underground im ioBroker

Das aktuelle Wetter inklusive Vorhersage 😉

Und so ist es gelöst - einfach folgenden HTML-Code in einem "Vis 2 Material-Widgets HTML-Vorlage" einfügen:

<style>
.grid-container {
    display: grid;
    grid-template-columns: 64px auto;
    gap: 5px;
    padding: 5px;
    font-size: 0.8em;
}
.grid-container div {
    margin: auto 0;
}
.grid-container img {
    width: 64px;
    height: 64px;
}
</style>

<div class="grid-container">
    <div>
        <img src="{v:weatherunderground.0.forecast.0d.iconURL;alt:0_userdata.0.Wetter.Tagesvorhersage.VorhersageIcon;v?v:alt}">
    </div>
    <div>
        <span style="font-weight:bold;">Jetzt</span><br>
        {weatherunderground.0.forecast.current.temp} °C
    </div>

    <div>
        <img src="{v:weatherunderground.0.forecast.0d.iconURL;alt:0_userdata.0.Wetter.Tagesvorhersage.VorhersageIcon;v?v:alt}">
    </div>
    <div>
        <span style="font-weight:bold;">Heute: {weatherunderground.0.forecast.0d.date;date(DD.MM.YYYY)}</span>
        <br>
        {weatherunderground.0.forecast.0d.tempMin}
        -
        {weatherunderground.0.forecast.0d.tempMax} °C
        ({weatherunderground.0.forecast.0d.cloudCover} % Wolkendichte)
        <br>
        Regen: {weatherunderground.0.forecast.0d.precipitationChance} % ({weatherunderground.0.forecast.0d.precipitationAllDay} mm)
        <br>
        {weatherunderground.0.forecast.0d.state}
    </div>

    <div>
        <img src="{weatherunderground.0.forecast.1d.iconURL}">
    </div>
    <div>
        <span style="font-weight:bold;">{weatherunderground.0.forecast.1d.date;date(DD.MM.YYYY)}</span>
        <br>
        {weatherunderground.0.forecast.1d.tempMin}
        -
        {weatherunderground.0.forecast.1d.tempMax} °C
        ({weatherunderground.0.forecast.1d.cloudCover} % Wolkendichte)
        <br>
        Regen: {weatherunderground.0.forecast.1d.precipitationChance} % ({weatherunderground.0.forecast.1d.precipitationAllDay} mm)
        <br>
        {weatherunderground.0.forecast.1d.state}
    </div>

    <div>
        <img src="{weatherunderground.0.forecast.2d.iconURL}">
    </div>
    <div>
        <span style="font-weight:bold;">{weatherunderground.0.forecast.2d.date;date(DD.MM.YYYY)}</span>
        <br>
        {weatherunderground.0.forecast.2d.tempMin}
        -
        {weatherunderground.0.forecast.2d.tempMax} °C
        ({weatherunderground.0.forecast.2d.cloudCover} % Wolkendichte)
        <br>
        Regen: {weatherunderground.0.forecast.2d.precipitationChance} % ({weatherunderground.0.forecast.2d.precipitationAllDay} mm)
        <br>
        {weatherunderground.0.forecast.2d.state}
    </div>

    <div>
        <img src="{weatherunderground.0.forecast.3d.iconURL}">
    </div>
    <div>
        <span style="font-weight:bold;">{weatherunderground.0.forecast.3d.date;date(DD.MM.YYYY)}</span>
        <br>
        {weatherunderground.0.forecast.3d.tempMin}
        -
        {weatherunderground.0.forecast.3d.tempMax} °C
        ({weatherunderground.0.forecast.3d.cloudCover} % Wolkendichte)
        <br>
        Regen: {weatherunderground.0.forecast.3d.precipitationChance} % ({weatherunderground.0.forecast.3d.precipitationAllDay} mm)
        <br>
        {weatherunderground.0.forecast.3d.state}
    </div>

    <div>
        <img src="{weatherunderground.0.forecast.4d.iconURL}">
    </div>
    <div>
        <span style="font-weight:bold;">{weatherunderground.0.forecast.4d.date;date(DD.MM.YYYY)}</span>
        <br>
        {weatherunderground.0.forecast.4d.tempMin}
        -
        {weatherunderground.0.forecast.4d.tempMax} °C
        ({weatherunderground.0.forecast.4d.cloudCover} % Wolkendichte)
        <br>
        Regen: {weatherunderground.0.forecast.4d.precipitationChance} % ({weatherunderground.0.forecast.4d.precipitationAllDay} mm)
        <br>
        {weatherunderground.0.forecast.4d.state}
    </div>        

    <div>
        <img src="{weatherunderground.0.forecast.5d.iconURL}">
    </div>
    <div>
        <span style="font-weight:bold;">{weatherunderground.0.forecast.5d.date;date(DD.MM.YYYY)}</span>
        <br>
        {weatherunderground.0.forecast.5d.tempMin}
        -
        {weatherunderground.0.forecast.5d.tempMax} °C
        ({weatherunderground.0.forecast.5d.cloudCover} % Wolkendichte)
        <br>
        Regen: {weatherunderground.0.forecast.5d.precipitationChance} % ({weatherunderground.0.forecast.5d.precipitationAllDay} mm)
        <br>
        {weatherunderground.0.forecast.5d.state}
    </div>

</div>

Im Objekt {weatherunderground.0.forecast.0d.iconURL} stehen URLs wie diese hier: https://www.wunderground.com/static/i/c/v4/28.svg. Und ich hätte gerne, dass daraus das hier wird: https://10.96.100.66:8082/proxy.0/img/https://www.wunderground.com/static/i/c/v4/28.svg. Damit vis-2 keinen externen Apache direkt aufruft, nutze ich den Proxy-Adapter. In diesem ist folgender Pfad gesetzt: aus /proxy.0/img/... mache https://10.96.100.66:4444/img/... wie in folgendem Screenshot gezeigt:

ioBroker Proxy-Adapter

ioBroker Proxy-Adapter Pfad-Einstellung

So wie ich vis-2 in meinem Webbrowser öffne und mir die Wettervorhersage anschaue, werden im Webbrowser die Bilder über die gleiche Adresse geladen, wie vis-2 selbst. Im Hintergrund setzt der Proxy-Adapter, wie oben gezeigt, die Anfragen auf den lokal installieren apache2 um - welcher die Proxy Anfragen an Port 4444 annimmt.

Dazu habe ich in der apache2 Konfig einen neuen vhost angelegt. Dieser lauscht auf Port 4444 und ist via https erreichbar. Als Cache nutze ich das apache2 Modul cache_disk. Damit das korrekt funktioniert, lasse ich die eventuell von der eigentlichen Anfrage gelieferten Header zum Caching durch meine eigenen überschreiben. Als nächstes wird die URL umgeschrieben. Aus https://<meine ioBroker>:4444/img/<https://das eigentliche Bild> soll einfach der Teil nach /img/ extrahiert und als eigentliche Bild-Quelle verwendet werden. Hier gibt es lediglich einen kleinen Haken: apache2 ersetzt // bei https:// durch einen /. Es wird aus der URL also http:/<wohin auch immer>. Deswegen wird die neue URL aus dem Protokoll, verbunden durch einen / und der eigentlichen Anfrage, zusammengesetzt. Sollte es zu einem Fehler kommen, lasse ich ein fallback.svg anzeigen. Und zuletzt lasse ich noch in die Standardlogdaten entsprechende Hinweise eintragen.

So sieht das ganze aus:

Listen 4444
<VirtualHost *:4444>
    ServerName imgprx.gehirn-mag.net.internal

    SSLEngine on
    SSLCertificateFile  /etc/apache2/ssl/fullchain.pem
    SSLCertificateKeyFile /etc/apache2/ssl/key.pem

    # --- Caching ---
    Header unset Cache-Control
    Header unset Pragma
    Header set Cache-Control "public, max-age=86400"
    CacheQuickHandler off
    CacheLock on
    CacheLockPath /tmp/mod_cache-lock
    CacheIgnoreHeaders Set-Cookie Cache-Control Pragma Expires
    CacheRoot /var/cache/apache2/mod_cache_disk
    CacheEnable disk /
    CacheDisable /fallback.svg
    CacheDefaultExpire 604800
    CacheMaxExpire 604800

    # --- URL Rewrite für /img/<https://asfasfdsa> ---
    # Apache macht aus https://bla... https:/bla... <-- Split in zwei Teile
    SSLProxyEngine On
    RewriteEngine On
    RewriteRule ^/img/(https?:)(.+)$ - [E=TARGET:$1/$2,E=TPROTO:$1,E=TURL:$2]
    RewriteCond %{ENV:TARGET} !=""
    RewriteRule ^(.*)$ %{ENV:TARGET} [P,L]

    # --- Fallback bei Fehlern ---
    ProxyErrorOverride On
    ErrorDocument 404 /fallback.svg
    ErrorDocument 500 /fallback.svg
    ErrorDocument 502 /fallback.svg
    ErrorDocument 503 /fallback.svg
    ErrorDocument 504 /fallback.svg
    Alias /fallback.svg /var/www/vhosts/proxy/fallback.svg

    # --- Log ---
    CustomLog /var/log/apache2/other_vhosts_access.log "%h %l %u %t \"%r\" %>s %b \"cache:%{cache-status}e\" \"target:%{TARGET}e\""
    ErrorLog  /var/log/apache2/error.log
</VirtualHost>

Im HTML-Vorlage Widget vom ioBroker habe ich die URLs nun wie folgen geändert: <img src="/proxy.0/img/{weatherunderground.0.forecast.1d.iconURL}">. Also lediglich ein /proxy.0/img vorangestellt. Bekommen habe ich also, dass im Webbrowser die externen Bilder ebenfalls über vis-2 geladen werden. Allerdings geht im Hintergrund ein apache2 ran, welcher die gewünschten Bilder aus dem Internet im Sinne eines Proxys lädt und diese sogar noch in einem Cache ablegt.

Und, nochmals erwähnt: Das läuft bei mir nur intern. Letztendlich lädt dieser Proxy alles aus dem Netz, was hinter /img/ steht. Sollte man das also wirklich ungeschützt ins Internet packen wollen, dann wären noch diverse weitere Einstellungen zum Thema Sicherheit sehr sinnvoll. Für meine Zwecke, wo ich im ersten Schritt nur wissen wollte, ob und wie man so etwas in apache2 lösen kann, tut es das mehr wie Dicke. Ich weiß, dass es geht - denn ich habe eine lauffähige Konfig 😀

stunnel: noch lange nicht ausgepopt

stunnel: noch lange nicht ausgepopt

Wer kennt das nicht? Irgendeine alte Software die betriebsnotwendig ist und schon seit geraumer Zeit keine Updates mehr bekommen hat. Und die dümpelt halt so vor sich hin. So lange alle Clients sich noch verbinden bzw. die Verbindungen zu anderen Servern aufgebaut werden können scheint ja alles wunderbar zu sein. Es scheint so. Bis irgendeiner meint seine TLS-Einstellungen zu aktualisieren...Und das ist gar nicht so unwahrscheinlich wenn man zum Beispiel den Empfehlungen des BSI folgen mag.Von SSL ist da schon lange keine Rede mehr und sogar TLSv1.1 fehlt neuerdings. Weitere Infos dazu siehe hier:

Besonders prekär ist es, wenn die Anwendung noch gegen openssl 0.9.x verlinkt ist. Damit geht maximal TLSv1.0. Ihr seht schon, das Unheil nimmt seinen Lauf sobald diese alte Software versucht verschlüsselte Verbindungen aufzubauen bzw. über verschlüsselte Verbindungen erreicht werden soll. Die Verbindung startet zwar, wird aber gleich wieder beendet. Meist sind die Fehlermeldungen im Log dazu wenig aussagekräftig.

Interesse daran wie man mittels tcpdump und openssl solche Verbindungsprobleme erkennen kann? Schreibt mir einfach (Mail oder Kommentar), dann reiche ich einen passenden Blogeintrag nach.

Und wie kann man das lösen? Ein Update der Software wäre möglich. Nur gibt es leider viele Gründe warum dies noch nicht passiert ist bzw. die nächsten Tage ebenfalls nicht passieren wird. Sind wir ehrlich zueinander: Das Verständnis dafür mag sich als Admin in Grenzen halten. Dennoch kennen wir alle die Situation. Die gibt es oft. Zu oft. Mit zu vielen Gründen.

Also gut, das geht so nicht. Also was tun wenn guter Rat teuer ist? Es gibt da so ein altes Sprichwort: "Wenn der Prophet nicht zum Berg kommt, dann kommt halt der Berg zum Propheten" (frei nach Francis Bacon 1625). Ein Update der Software ist nicht möglich, die Sicherheitseinstellungen der gegenüber liegenden Seite wird man wohl kaum reduzieren wollen. Bleibt also der Weg durch die goldene Mitte: Ein Proxy muss her! Der macht aus Klartext aktuelle Verschlüsselung, aus zu alter Verschlüsselung aktuelle Verschlüsselung oder, und das soll es ebenfalls geben: Aus alter Verschlüsselung gar keine Verschlüsselung. Auch hierfür gibt es Fälle wo das völlig ausreichend ist.

stunnel

Für solche Dinge nehme ich gerne stunnel (https://www.stunnel.org/). Der ist klein und schlank und läuft unter aller gängigen Unix/Linux Systemen als auch unter Windows. Das Tool gibt es bereits seit über 20 Jahren und wird immer noch aktiv entwickelt. Hut ab!

Ok, als ich das erste Mal mit stunnel zu tun hatte waren meine Beweggründe eher anders herum: Viele Webfrontends damals kannten noch gar keine Verschlüsselung. Also fix stunnel davor geschaltet und schon war die Seite via https anstatt http zu erreichen. Die Richtung ist aber genau genommen völlig egal - es gibt ein Grundprinzip welches man bei stunnel immer im Hinterkopf behalten muss: Auf der einen Seite ist verschlüsselt, auf der anderen Seite ist Klartext. Wer aus alter Verschlüsselung neue machen will braucht somit zwei stunnel: Einen der übers Netz alte Verschlüsselung an nimmt und Klartext nach localhost leitet. Auf localhost geht ein stunnel ran der Klartext nach aktueller Verschlüsselung umwandelt. Reicht eine der beiden Richtungen reicht logischerweise auch nur ein stunnel.

alpine

Kein Platz stunnel irgendwo mit drauf zu packen? stunnel kann zwar drauf funktioniert aber nicht wie erhofft da am Installationsort eine völlig veraltete openSSL Bibliothek installiert ist? Für sowas nehme ich ganz gerne alpine Linux. Klein, sehr, sehr schlank und bringt stunnel in den zusätzlichen Paketen mit. Egal ob als Mini-VM oder als Container: läuft 🙂

Infos zu alpine? Gibt es hier: https://alpinelinux.org/

Beispiel 1: pop3 mit STARTTLS

Genug der großen Reden, ein paar Beispiele müssen her! Die Konfig ist simpel: Zuerst ein paar globale Einstellungen und im Anschluss daran die jeweiligen Proxys. Das Format sollte jedem bekannt sein: Ini lässt grüßen.

;debug = info
;foreground = yes
;options = -NO_SSLv3
sslVersion = all
ciphers = ALL

[stls2pop3]
protocol = pop3
client = no
accept = 10110
connect = pop.gehirn-mag.net:110
cert = /etc/stunnel/stunnel.pem

Im globalen Abschnitt kann z.B. stunnel dazu gebracht werden im Vordergrund zu starten. Perfekt für Diagnose. Ansonsten habe ich hier nur sichergestellt, dass alles was "alte" Verschlüsselung ist verbinden darf.

Zum Proxy stls2pop3 selbst: Mir ist einmal eine Software begegnet die konnte POP3 und POP3S. Ok, POP3S ist raus. Nur wurde bei POP3, sobald angeobten, STLS (STARTTLS) verwendet. Logisch, ohne Schalter zum abstellen. Versteht sich. Oder auch nicht.

client auf no gestellt, es sollen eingehende Verbindungen verschlüsselt angenommen werden. Das soll für den Moment reichen, mehr Wissen braucht es zum client Schalter im Moment noch gar nicht. Mit accept wird der Port auf 10110 gestellt. Und via connect wird angegeben wohin stunnel sich bei eingehenden Anfragen letztendlich verbinden soll. In dem Beispiel steht ein ganz normaler Server welcher via POP3 angesprochen werden soll. Nur Moment, damit das funktioniert muss man beachten, dass bei POP3 für STARTTLS nur die Kurzform STLS verwendet werden muss. Das sollte man wissen. Und somit sollte auch stunnel darüber Bescheid wissen. Deswegen noch fix protocol auf pop3 gestellt. In der manpage zu stunnel.conf sind die Protokolle aufgelistet welche stunnel versteht: Eine ordentliche Menge. Die manpage kann hier nachgelesen werden: https://www.stunnel.org/static/stunnel.html#SERVICE-LEVEL-OPTIONS. Im Bereich "Service Level Options" einfach mal nach "protocol" suchen. Infos zum POP3 Protokoll uns STLS: https://de.wikipedia.org/wiki/Post_Office_Protocol#Verschl%C3%BCsselung.

Zu guter Letzt: Das Zertifikat via cert: Bei vielen Distributionen wird beim installieren bereits ein passendes angelegt. Das Zertifikat fehlt oder soll durch ein anderes ersetzt werden? Siehe stunnel HowTo im Abschnitt "Generating the stunnel certificate and private key (pem)": https://www.stunnel.org/howto.html.

Jetzt noch in der Anwendung rasch angegeben, dass der zu erreichende POP3 Server unter der IP-Adresse xxx.xxx.xxx.xxx bzw. folgendem DNS-Namen ... auf Port 10110 erreichbar ist. Und sehe wie die Anwendung über das hoffnungslos veraltete POP3 STARTTLS wieder verbindet. Ok, im Klartext auf das Ziel. Mag Situationen geben wo das reicht. Falls nicht lies einfach im nächsten Abschnitt weiter. In dem wird durch einen zweiten Proxy die Verschlüsselung auf neu getrimmt.

Beispiel 2: Aus https alt mach neu

So, ein Proxy reicht also nicht. Dann halt mit zwei: Aus https alt mach http Klartext welches nur über localhost sich verbindet. Und zwar auf einen zweiten Proxy welcher aus Klartext https neu macht.

;debug = info
;foreground = yes
;options = -NO_SSLv3
sslVersion = all
ciphers = ALL

[https_in]
client = no
accept = 10443
connect = localhost:54321
cert = /etc/stunnel/stunnel.pem

[https_out]
client = yes
accept = localhost:54321
connect = www.gehirn-mag.net:443
cert = /etc/stunnel/stunnel.pem

Das Prinzip Konfig wie gehabt. Zuerst der Kopf und dann der erste Proxy. Hier mit dem Namen https_in. Wie in Lösung 1 beschrieben macht der aus https Klartext http. Und verbindet sich als Ziel auf localhost:54321. An der Adresse lauscht der zweite Proxy https_out. Ein Unterschied gibt es: Hier steht client auf yes. Bei yes geht stunnel der Annahme, dass das Verbindungsziel Verschlüsselung benötigt. Somit ist der eingehende Teil die Klartextseite. Bei no ist das gerade anders herum (siehe manpage). Der Rest, oh Wunder, ist wie gehabt und bereits bekannt.

Noch fix das Ziel in der Anwendung geändert auf die Adresse des Proxyservers mit Port 10443 und schon wird aus https ganz arg alt https ganz arg neu gemacht.

Fazit

Und wieder mal die Welt gerettet. Dank einem kleinen, schlanken Tool. Das trotz seines Alters alles andere als ins Alter gekommen ist. Es ist kein Fehler in einem ruhigen Moment mal die Webseite und die Beispiele etwas genauer zu betrachten. So viele Dinge die man darüber hinaus mit stunnel noch machen kann. Es lohnt sich.