Ghost, CDNs und die DSGVO
Als kleines Wochenendprojekt hatte ich diesen Blog aufgesetzt und mich bzgl. der Software schon vor einiger Zeit für Ghost entschieden. Nach den ersten Schritten im eigenen Ghost-Blog ist mir dann aufgefallen, dass obwohl selfhostet, die Seite einige externen Quellen ansteuert. Was am Ende dann in diesem Artikel endete.
Nach dem ersten Start meiner Instanz, habe ich im Browser überprüft, ob Cookies gesetzt werden, Schriftarten von externen Quellen nachgeladen werden oder generell CDNs genutzt werden. Da dieser Blog ein rein privates Projekt ist, will ich den Aufwand und die Angriffsfläche für geschäftstüchtige Rechtsanwälte aka. Abmahner, so klein wie möglich halten.
Eine kurze Google-Recherche ergab, dass sich mit dem Thema DSGVO (Englisch GDPR) und Ghost schon mal jemand detailliert befasst hat: Ghost DSGVO-konform machen
Das Fazit: Ghost ist out-of-the-box nicht so richtig konform mit der DSGVO. Zumindest nicht, ohne es entsprechen in der Datenschutzerklärung zu argumentieren. Neben dem Thema CDN gibt es noch ein paar weitere Baustellen und es lohnt sich daher den oben zitierten Artikel einmal durchzulesen.
Das Problem mit externen CDNs
Ghost lädt für die Funktionen der Suche und des Benutzerlogins JavaScript Libraries über das CDN jsDelivr nach. Und das erledigt der Webbrowser quasi sofort beim Aufruf des Blogs und ohne das der User im Vorfeld sich hätte drüber informieren oder dem widersprechen können.
Ich persönlich hätte damit jetzt kein Problem, wenn mein Browser sich bei einem CDN meldet und dort JavaScript Libraries nachlädt. Aber es hat ja schon Menschen gegeben haben, die beim Nachladen von Google Fonts Unwohlsein verspürten und Abmahnungen in Serie verschickten...
jsDelivr scheint grundsätzlich laut der veröffentlichten Nutzungsbedingungen und der Datenschutzerklärung eigentlich ziemlich stressfrei einsetzbar zu sein. Und als europäischer User wird man bevorzugt an einen europäischen Server weitergeleitet, aber halt auch nur bevorzugt. Das könnte man vermutlich in der eigenen Datenschutzerklärung als technisch notwendig argumentieren und dann auch so nutzen.
Jedoch will ich mir dazu kein juristisches Urteil erlauben und wer hier auf der sicheren Seite sein will, der schmeißt am besten die CDNs raus und lädt die externen Libs von einem eigenen Server oder direkt aus dem eigenen Ghost-Blog nach.
Aber Achtung! Wer die CDNs rauswirft, muss sich in Zukunft selber darum kümmern, die verwendeten Libraries aktuell zu halten. Dies wird entweder erforderlich, wenn es ein relevantes Update der externen Library gibt, oder ein Update innerhalb von Ghost als Abhängigkeit eine neuere Version der externen Library verwendet. Daher hätte ich persönlich das externe CDN eigentlich gerne beibehalten...
Je nach verwendetem Ghost-Theme können auch noch weitere CDNs hinzukommen. Es ist also eine gute Idee, das im eigenen Setup zu prüfen und im Blick zu behalten. Im Standard setzt Ghost mindestens auf zwei externe Libraries, bei denen es sich um die Suchfunktion (sodo-search) und das Loginportal (portal-client) kümmern. Ist die Kommentarfunktion für eingeloggte Benutzer aktiviert, kommt noch die Library dafür hinzu.
Umstellung auf lokale JavaScript Libraries
Jetzt aber los! Wie baut man Ghost so um, dass man die externen Libraries lokal bereitstellt? Im Folgenden gehen wir das für die zwei Standard-Libraries an, die Ghost für die Suchfunktion und das Login-Portal nutzt. Mein Vorgehen stellt eine für Docker angepasste Variante der im obigen Blog vorgestellten Methode dar.
- Für die Suchfunktion nutzt Ghost die Library sodo-search.min.js und wir benötigen neben der JS-Library das dazugehörige CSS-File main.min.css.
- Für den Login der Benutzer (intern oder extern) kommt die Library portal.min.js zum Einsatz.
- Für die Kommentarfunktion nutzt Ghost anscheinend ebenfalls eine externe Library – comments-ui.min.js zusammen mit einem dazugehörigen CSS-File. Da ich die Kommentarfunktion bei mir aktuell deaktiviert habe, ignoriere ich diese Library in meinem Setup zunächst. Das weitere Vorgehen wäre aber identisch wie hier beschrieben.
Hinweis: Soweit ich das nachvollziehen kann, werden alle diese Libraries direkt vom Ghost-Projekt entwickelt und betreut.
Das Ghost-Theme das ich verwende, ist das kostenlose Attila-Theme für Ghost.
Achtung! Die Version der Libraries muss zur jeweiligen Ghost-Version passen. Wie wir das sicherstellen, dazu später mehr.
Umgebungsvariablen
Ghost kann für die oben genannten Standard-Libraries, Stichwort Umgebungsvariablen, alternative URLs übergeben werden – auch dem Docker-Container. Somit kann fast alles im Ghost-Standard bleiben. Die Libraries selber werden Ghost dann zusammen mit dem Theme übergeben. Das wars im Prinzip schon.
1. Schritt: Die Libraries identifizieren
Zunächst müssen wir herausfinden, welche Libraries wir genau benötigen. Dazu rufen wir unseren Blog im Browser auf und öffnen die Developer-Tools – im Firefox rufen wir diese mit der Taste F12 auf.
Hier angekommen, wechseln wir auf den Reiter Network und laden die Seite im Hintergrund einmal neu. Jetzt sollte hier eine Liste ähnlich dieser zu sehen sein:
In der Spalte Domain sehen wir die URL cdn.jsdelivr.net
des CDNs jsDelivr und haben damit unsere gesuchten Kandidaten gefunden:
sodo-search.min.js
main.css
portal.min.js
Achtung! Im Reiter Network sehen wir aktuell nur die Ressourcen, welche jetzt gerade beim Aufruf dieser Seite geladen wurden.
Sollten an anderer Stelle noch weitere externe Elemente nachgeladen werden, sehen wir diese jetzt nicht. Das File main.css
sehen wir zum Beispiel nur, wenn wir die Suche (die Lupe) des Blogs aufrufen:
2. Libraries vom CDN herunterladen
Als nächstes laden wir die bei den Dateien sodo-search.min.js
, main.css
und portal.min.js
herunter. Unter Linux oder MacOS X verwende ich auf der Shell das Tool wget
dafür. Die entsprechende URL für die Library und die Version der verwendeten Library finden ebenfall im Network-Tab. Hier wählen wir die entsprechende Zeile aus und es sollte sich ein Bereich mit detaillierten Informationen zu der Quelle öffnen:
Ganz oben finden wir die URL zu der Library und wenn wir etwas weiter herunterscrollen, auch die Versionsinfo zu der verwenedeten Library. Dies Versionsnummer notieren wir bitte für gleich.
Damit man den Überblick behält, bietet es sich an, die Version der jeweiligen Library mit in den Dateinamen zu übernehmen. Aus sodo-search.min.js
habe ich meinem Fall sodo-search.1.5.1.min.js
gemacht.
3. Libraries Ghost zur Verfügung stellen
Wie bereits erwähnt, integriere ich die heruntergeladenen Libraries in mein Ghost-Theme und stelle sie so in Ghost bereit. Dazu lade ich zunächst mein Theme Attila lokal herunter, entpacke die ZIP-Datei und wechsele in den Unterordner des Themes ./assets/
und erstelle den neuen Ordner jsDelivr
. In diesem Ordner lege ich dann die eben heruntergeladenen Dateien ab, erstelle aus dem Theme-Ordner wieder eine ZIP-Datei. Diese wird wieder in Ghost hochgeladen und das Theme aktiviert.
4. Die Umgebungsvariablen
Im letzten Schritt geht es nun darum, Ghost auch noch mitzuteilen, dass es bitte nicht mehr die Libraries aus dem CDN nimmt, sondern die lokal gehosteten Libraries. Dazu verwenden wie entsprechende Umgebungsvariablen die Ghost im Standard selber bereits kennt und übergeben diese lediglich über unser docker-compose.yml
.
Laut der Ghost-Dokumentation gibt es dafür die folgenden Keywords:
portal
sodoSearch
comments
(aktuell bei mir deaktiviert)
Verwendung mit Docker
Jetzt ist die Frage, wie wir bekommen die neuen URLs an das Ghost im Docker-Container übergeben? In der Ghost-Dokumenation zur sodo-search-Library finden wir dazu ein JSON mit dem Schlüssel sodoSearch
und den Unterschlüsseln url
und styles
. Das können wir aber so direkt nicht verwenden.
"sodoSearch": {
"url": "https://cdn.jsdelivr.net/npm/@tryghost/sodo-search@~{version}/umd/sodo-search.min.js",
"styles": "https://cdn.jsdelivr.net/npm/@tryghost/sodo-search@~{version}/umd/main.css"
},
Für die Übergabe der Umgebungsvariablen an den Container, nutzen wir in unserer docker-compose.yml
das Attribut environment
.
Der Dokumentation des Ghost-Docker-Images entnehmen wir, dass wir untergeordnete Schlüssel des JSON – zum Beispiel url
und styles
– mit zwei Unterstrichen vom eigentlichen Schlüssel absetzen und so an Ghost übergeben: sodoSearch__url
.
Also wird aus dem JSON für die sodo-search
- und portal
-Library die folgende Konfiguration für unsere docker-compose.yml
. Dafür nutzen wir auch gleich unsere lokal abgelegten Libraries und ergänzen den Block environment:
um folgende Zeilen:
environment:
...
sodoSearch__url: /assets/jsdelivr/sodo-search-1.5.1.min.js
sodoSearch__styles: /assets/jsdelivr/sodo-search-1.5.1.main.css
portal__url: /assets/jsdelivr/portal-2.4.6.min.js
Jetzt haben wir alle Änderungen vorgenommen und können die Änderungen "scharf" schalten, indem wir den Docker-Container neu starten.
5. Überprüfung
Als nächstes heißt es zu prüfen, ob alles klappt hat. Geht die Suche auf dem Blog? Kann ich mich am Blog mit meinem Benutzer anmelden?
In jedem Fall sollten wir unbedingt einen Blick in die Developer-Tools werfen. Zum einen darf bei den Domains nur noch die eigene Domain auftauchen und eben nicht mehr das CDN und wir dürfen keine 404-Fehler oder ähnliches sehen. Haben wir mit Pfaden oder Dateinamen etwas falsch gemacht, würden wir hier die entsprechenden Fehler finden.
Da hat was nicht geklappt:
Und so ist es korrekt:
Next Steps
Was bleibt zu tun? Zum einen sollte man nochmal genau kontrollieren ob Ghost oder das Theme irgendwo anders noch weitere externe Libraries nachlädt.
Was ich aber in jedem Fall entdeckt habe ist, das Ghost Fonts bei Google holt und auf der Seite der Blog-Administration eine ganze Menge externer Fonts von fonts.bunny.net nachlädt. Also, nächtes Topic steht dann wohl schon fest.