Wie Sie Drittanbieter-Skripts auf Single-Origin-Websites in den Griff bekommen
Fast alle Webseiten laden heute Ressourcen von anderen Origins als dem der entsprechenden Seite. Diese Skripte von Drittanbietern verlangsamen Ihre Website, erschweren die Erstellung einer strengen Content Security Policy und gewähren dem Drittanbieter vollen Zugriff auf Ihre Website. Möglicherweise bieten Compute@Edge und edgebasiertes Proxying einen besseren Weg.
Unser Developer Hub ist ein gutes Beispiel für eine statisch generierte Website, die die meisten ihrer Seiten und Ressourcen aus einem Cloud Storage Bucket bezieht (in unserem Fall Google Cloud Storage). Doch wie bei vielen Websites werden auf diesen Seiten auch Inhalte von anderen Domains abgerufen. So nutzen wir zum Beispiel:
Google Analytics zur Messung des Traffics
Sentry zur Meldung und Zusammenfassung von JavaScript Fehlern
FormKeep zum Sammeln von Nutzertendenzen und -Feedback
Swiftype zur Suche auf der Website
Diese Anbieter sind sehr beliebt – vielleicht verwenden Sie sie bereits auf Ihrer eigenen Website. Laut einer vom Browser-Datenschutzassistenten Ghostery 2020 in Auftrag gegebenen Studie führt eine herkömmliche Nachrichten- und Medienwebsite alleine für das Tracking mehr als 10 Drittanbieter-Skripts aus.
Datenschutz, Sicherheit, Performance- und Compliance-Probleme
Zweifelsohne bestehen bei der Nutzung von Drittanbieter-Skripts Datenschutzprobleme, vor allem bei denjenigen, die das Nutzerverhalten tracken. Über Plugins wie Ghostery können sich Endnutzer angemessen schützen. Tatsächlich werden derartige Schutzmechanismen mehr und mehr in Browser integriert.
Auch die Legislative fährt inzwischen einen schärferen Kurs. Ein Gericht in Deutschland hat vor Kurzem einen Websitebetreiber für die Nutzung von Google Fonts mit einer Geldstrafe belegt, weil dadurch die IP-Adressen der Endnutzer gegenüber Google offengelegt werden.
Doch auch wenn sich der Websitebetreiber für die Nutzung bestimmter Drittanbieterdienste entscheidet, hat er – wenn überhaupt – nur wenig Kontrolle darüber, was die Drittanbieter tun oder welche Daten erhoben werden. In manchen Fällen wissen Entwicklerteams nicht einmal, was auf der Website geladen wird, wenn Tools wie Google Tag Manager verwendet werden, um die Kontrolle über Skripte von Drittanbietern an andere Teams innerhalb des Unternehmens zu delegieren.
Wenn wir als Entwickler mehr direkte Kontrolle über das Verhalten der Skripte von Drittanbietern hätten, könnten wir vielleicht die Interessen unserer Nutzer besser schützen und gleichzeitig die Vorteile der Services Dritter nutzen.
Es geht hier aber nicht nur um Datenschutz. Sobald ein paar Tracker, Analytics-Tools, Schriftarten und weitere Drittanbieterservices hinzukommen, müssen auf den Geräten der Endnutzer Ressourcen von 20, 30, 50 oder noch mehr unterschiedlichen Domains abgerufen werden, nur um eine einzige Webseite aufzubauen.
In der Praxis bedeutet das, dass Sie keine wirksame Content-Sicherheitsrichtlinie erstellen können, Browser mehrere einzelne (unsachgemäß priorisierte) TCP-Verbindungen zu unterschiedlichen Servern herstellen müssen und die Verfügbarkeit Ihrer Website letztlich von der Verfügbarkeit all dieser Drittparteien abhängt. Was ist zum Beispiel, wenn Ihr Schriftartenanbieter ausfällt oder von einem Land blockiert wird und Ihre Website als leere Seite dargestellt wird?
Proxies schaffen Abhilfe
Wenn Sie Ihre Website über Fastly bereitstellen, profitieren Sie bereits von einem edgebasierten Reverse Proxy, der auf bewährten Sicherheitsstrategien und dem aktuellsten Protocol-Support aufbaut und eine einzige Domain weltweit bereitstellen kann, während Anfragen wie gehabt an unterschiedliche Backend-Server geroutet werden. Viele unserer Kunden nutzen diese Funktion, um eine Microservices-auf-der-Edge-Architektur aufzubauen.
Nach dem gleichen Prinzip können viele Drittanbieter-Skripts ersetzt werden. Sehen wir uns an, wie das funktionieren kann:
Der Drittanbieter (z. B. www.google-analytics.com) wird Ihrem Fastly Service als neues Backend hinzugefügt.
Das
<script>
Tag im HTML-Code ist so abgeändert, dass das Laden von Inhalten über einen lokalen Pfad erfolgt, z. B./services/analytics
.Anfragen an dieses Verzeichnis werden von Fastly in den korrekten Backend-Pfad umgewandelt und genau zu diesem Backend geroutet.
Der Bibliothek-Code, den die Drittpartei bereitstellt, wird an Fastly übermittelt und nach Bedarf umgewandelt – beispielsweise, um die URL des Datensammlers des Drittanbieters zu finden und durch Ihren Proxy Endpoint zu ersetzen (dies kann etwas riskant sein, aber darauf gehen wir später ein).
Nachfolgende Anfragen, die vom Skript des Drittanbieters gestellt werden, werden an Ihren Fastly Service gesendet, geprüft und bei Bedarf gefiltert und dann an den Drittanbieter weitergeleitet.
Auf diese Weise können wir folgende Vorteile erzielen:
Eine strenge
Content-Sicherheitsrichtlinie
Maximale Wirksamkeit bei der HTTP-Priorisierung
Schutz vor Ausfällen Dritter
Kontrolle über die gemeinsame Datennutzung mit Dritten
Umgehung von Blocking/Filter-Plugins auf Client-Seite
Der letzte Punkt ist zugegebenermaßen etwas umstritten. Aber ich nehme an, dass es Ihnen wichtig ist, die Auswirkungen auf Ihre Endnutzer so gering wie möglich zu halten, wenn Sie sich die Mühe machen, Proxys von Drittanbietern zu nutzen. Sehen wir uns also an, wie dies für einige der im Developer Hub verfügbaren Drittanbieter umgesetzt werden kann.
Der Developer Hub ist eine GatsbyJS Anwendung, die von einem in JavaScript geschriebenen Compute@Edge Service gesteuert wird. Erfahren Sie in einem früheren Blogpost mehr darüber, wie wir die Anwendung auf Compute@Edge migriert haben.
HTTP-APIs (FormKeep und Swiftype)
Fangen wir ganz einfach an: Einige Drittanbieter verwenden gar keine Skripte, sondern sind lediglich API-Endpoints, an die wir von unserem eigenen clientseitigen Skript aus Queries senden. FormKeep empfängt zum Beispiel Daten aus unserem Feedback-Formular in einem HTTP POST und Swiftype liefert Ergebnisse für Suchanfragen. Das Hinzufügen dieser Komponenten zur primären Domain ist ganz einfach.
Ändern Sie Ihr Compute@Edge Programm dazu so ab, dass es einen speziellen Pfad erkennt, und leiten Sie Anfragen auf diesem Pfad an ein neues Backend (in unserem Beispiel nennen wir es „formkeep“) weiter:
// src/index.js (Fastly Compute@Edge app)
const req = event.request
const reqUrl = new URL(req.url)
const reqPath = reqUrl.pathname
let backendName;
if (reqPath === "/api/internal/feedback") {
backendName = "formkeep";
reqUrl.pathname = "/f/xxxxxxxxxxxx"
} else {
backendName = "gcs";
}
let beReq = new Request(reqUrl, req);
let beResp = await fetch(beReq, { backend: backendName });
return beResp;
Modifizieren Sie anschließend das Verhalten Ihrer Frontend-Anwendung oder HTML-Seite dahingehend, dass die API-Anfrage auf den neuen Pfad verwiesen wird:
// feedback.html (client side HTML page)
async function handleFormSubmit(evt) {
const data = new FormData(evt.target)
buttonEl.current.disabled = true
await fetch("/api/internal/feedback", {
method: "post",
body: data,
headers: { accept: "application/json" },
})
setIsSubmitted(true)
}
Fügen Sie abschließend das Backend hinzu – entweder im Web-Interface oder mithilfe der Fastly CLI – und deployen Sie die aktualisierte Anwendung.
fastly backend create --name=formkeep --host=formkeep.com --version=active --autoclone
fastly compute publish
Die Flags --version=active
und --autoclone
veranlassen, dass die derzeit aktive Version des Service geklont wird, während das neue Backend zwar zur geklonten Version hinzugefügt aber nicht aktiviert wird. Über den Befehl compute publish
wird Ihr aktualisierter Code zunächst im Entwurfsmodus hochgeladen und anschließend aktiviert.
Diese Art der Integration von Drittanbietern mit Fastly ist so einfach, dass es eigentlich keinen Grund gibt, sich anderweitig zu entscheiden.
Konfigurierbare Clients (Sentry)
Einige Dienste von Drittanbietern bieten einen JavaScript Client an, der im Browser ausgeführt werden muss, wie z. B. der Fehlererfassungsservice Sentry. Wenn Sie Glück haben, erlaubt der Anbieter die Konfiguration des Hostnamens und Pfads der von seinem Client gestellten Anfragen.
Sentry ist ein gutes Beispiel für die Verwendung der Tunnel-Option. Die Konfiguration kann überall dort vorgenommen werden, wo Sie Ihre Sentry Konfiguration speichern. Für den Developer Hub verwenden wir das Sentry Plugin für Gatsby und die Konfiguration wird im Array plugins
in unserer gatsby-config.js
gespeichert:
// gatsby-config.js
{
resolve: "@sentry/gatsby",
options: {
dsn: "https://#######@###.ingest.sentry.io/######",
tunnel: "/api/internal/errors",
sampleRate: 0.7,
tracesSampleRate: 0.7,
release: process.env.COMMIT_SHA,
}
}
Wenn Sie Sentry außerhalb eines Anwendungs-Frameworks wie Gatsby verwenden, setzen Sie die Tunnel
-Option höchstwahrscheinlich dort ein, wo Sie Sentry.init
aufrufen.
Ändern Sie nun Ihre Compute@Edge Anwendung so ab, dass der neue Pfad hinzugefügt wird, und leiten Sie auf den Pfad um, den Sentry erwartet:
// src/index.js (Fastly Compute@Edge app)
if (reqPath === "/api/internal/feedback") {
backendName = "formkeep";
reqUrl.pathname = "/f/xxxxxxxxxxx"
} else if (reqPath === "/api/internal/errors") {
backendName = "sentry";
reqUrl.pathname = "/api/" + SENTRY_PROJECT_ID + "/envelope/"
} else {
backendName = "gcs";
}
Wie auch bei der ersten Methode müssen Sie zunächst das neue Backend hinzufügen (wobei der Name mit dem im Code übereinstimmen muss) und dann eine neue Version Ihres Programms deployen:
fastly backend create --name=sentry --host=oXXXXXXXX.ingest.sentry.io --version=active --autoclone
fastly compute publish
Ein weiterer Vorteil des Gatsby Plugins von Sentry ist, dass der Sentry Client-Code in unserem Website-Bundle gebündelt wird, sodass wir uns keine Gedanken um die Anfrage machen müssen, über die die eigentliche Bibliothek geladen wird, sondern nur um diejenige, die Daten an Sentrys Datensammler sendet.
Clients dynamisch umschreiben (Google Analytics)
Andere Skripte erfordern ein wenig mehr Aufwand. Google Analytics (GA) kodiert die Ziel-URL fest in das Tracking-Skript und das Gatsby Plugin für GA lädt die Bibliothek direkt von Google. In diesen Fällen könnten Sie eine modifizierte Version der Client-Bibliothek selbst hosten. Allerdings profitieren Sie dann nicht von den Aktualisierungen, die der Anbieter an seinem Client-Code vornimmt.
Stellvertretend bietet sich eine Streamingtransformation in Compute@Edge an, um diese URLs im laufenden Betrieb abzuändern.
Dieselbe Methode kann auch für Google Fonts verwendet werden, da die eigentlichen Schriftdateien über die von Google zurückgeschickten CSS geladen werden und diese ohnehin über die Hauptdomain geroutet werden müssen. Der Fastly Kunde Houzz nutzt diese Lösung, um eine datenschutzfreundliche Methode zum Laden von Schriftarten von Google zu entwickeln.
Fügen Sie zuerst eine einfache, über einen Stream durchführbare Suchen-und-Ersetzen-Funktion hinzu:
// src/index.js (Fastly Compute@Edge app)
const streamReplace = (inputStream, targetStr, replacementStr) => {
let buffer = ""
const decoder = new TextDecoder()
const encoder = new TextEncoder()
const inputReader = inputStream.getReader()
const outputStream = new ReadableStream({
start() {
buffer = ""
},
pull(controller) {
return inputReader.read().then(({ value: chunk, done: readerDone }) => {
buffer += decoder.decode(chunk)
if (buffer.length > targetStr.length) {
buffer = buffer.replaceAll(targetStr, replacementStr)
controller.enqueue(encoder.encode(buffer.slice(0, buffer.length - targetStr.length)))
buffer = buffer.slice(0 - targetStr.length)
}
// Flush the queue, and close the stream if we're done
if (readerDone) {
controller.enqueue(encoder.encode(buffer))
controller.close()
} else {
controller.enqueue(encoder.encode(""))
}
})
},
})
return outputStream
}
Direkt nach dem Fetch zum Backend wird beResp.body
zu einem lesbaren Stream. Mit der Funktion „stream replace“ können wir die GA Domain durch eine eigene ersetzen:
let beResp = await fetch(beReq, { backend: backendName });
const respContentType = beResp.headers.get("content-type") || ""
if (respContentType.startsWith("text/")) {
const newRespStream = streamReplace(
beResp.body,
"www.google-analytics.com",
"developer.fastly.com/api/internal/analytics"
)
beResp = new Response(newRespStream, { headers: beResp.headers })
}
return beResp;
Im Gatsby Plugin für GA ist das Tag <script>
in jeder Seite fest kodiert, sodass es auf alle Textantworten angewendet werden muss, da jede HTML-Seite sowohl die in der analytics.js
-Bibliothek selbst fest kodierten Hostnamen als auch das Markup zum Laden der Bibliothek enthält. Mit dem gatsby-plugin-google-gtag scheint es eine Alternative zu geben, mit der das Tag <script>
in einen lokalen Pfad umgeschrieben werden kann. Für diesen Blogpost hielt ich es jedoch für sinnvoll, eine ultimative Ausweichlösung vorzustellen, die bei fast allen Anwendungen funktionieren sollte.
Beachten Sie, dass das Umschreiben des Codes von Drittanbietern auf diese Weise mit einem gewissen Risiko verbunden ist. Einige Bibliotheken von Drittanbietern rufen möglicherweise Daten von mehreren Hostnamen ab. Dies könnte zu einer Veränderung des Hostnamens führen, von dem diese Daten abgerufen werden. Andere wiederum versuchen sogar, den Aufbau von URLs zu verschleiern, um genau diese Art des Umschreibens zu vermeiden! Wir haben diesbezüglich gute Erfahrungen mit Google Analytics und Google Fonts gemacht.
Wenn Sie die Modifikation lediglich auf eine URL anwenden müssen, könnten Sie das if
-Statement ändern, um die zuvor definierte reqPath
-Variable zu überprüfen.
Fügen Sie nun den neuen Pfad zum Umleitungscode hinzu:
if (reqPath === "/api/internal/feedback") {
backendName = "formkeep";
reqUrl.pathname = "/f/xxxxxxxxxxx"
} else if (reqPath === "/api/internal/errors") {
backendName = "sentry";
reqUrl.pathname = "/api/" + SENTRY_PROJECT_ID + "/envelope/"
} else if (reqPath.startsWith("/api/internal/analytics")) {
backendName = "ga";
reqUrl.pathname = reqPath.replace("/api/internal/analytics/", "/")
} else {
backendName = "gcs";
}
Selbstverständlich muss auch hier wieder das Google Analytics Backend hinzugefügt, der neue Code hochgeladen und die aktualisierte Version des Service aktiviert werden:
fastly backend create --name=ga --host=www.google-analytics.com --version=active --autoclone
fastly compute publish
Cookies aus Anfragen entfernen
Indem Sie alle Anfragen an Ihre Domain leiten, haben Sie bereits viel mehr Kontrolle über das Verhalten von Drittanbietern. So erkennt der Drittanbieter beispielsweise nicht mehr die IP-Adresse Ihrer Endnutzer, da alle von Ihnen gesendeten Anfragen von Fastly kommen.
Sie können auch proaktiv unnötige Daten aus der Anfrage entfernen. Die vermutlich wichtigsten Attribute sind hier die Header X-Forwarded-For
, Fastly-Client-IP
und Cookie
, über die andernfalls personenbezogene Daten an Dritte weitergegeben und die sämtliche Datenschutzvorteile des Proxys zunichte machen würden. Diese lassen sich ganz einfach entfernen, kurz bevor Sie die Anfrage an das Backend senden:
beReq.headers.delete("cookie");
beReq.headers.delete("x-forwarded-for");
beReq.headers.delete("fastly-client-ip");
Es gibt aber noch viele andere Schutzmaßnahmen, die Sie ergreifen können. Dazu zählt auch, den Body Content der Anfrage zu filtern oder eine Kopie der Anfrage zur Prüfung an einen Logging Endpoint zu schicken.
Fazit
Das Bündeln aller Ressourcen und Anfragen Ihrer Website in einer einzigen Domain bringt erhebliche Vorteile mit sich und hilft, ungewollte Beeinträchtigungen bei Performance und Datenschutz zu vermeiden. Compute@Edge und Edge Computing im Allgemeinen haben das Potenzial, diese Dinge im Laufe der Zeit weiter zu vereinfachen. Aber bereits jetzt gibt es einige leistungsstarke Möglichkeiten, um das Laden von Websites zu Ihrem eigenen Vorteil und dem Ihrer Nutzer zu gestalten.