URLs mit dem Secure-Link-Modul in NGINX und NGINX Plus absichern

NGINX | 29. Juli 2016

Sowohl die Open-Source-Software NGINX als auch NGINX Plus sind als Webserver, Reverse-Proxys und Caches für Ihre Inhalte äußerst sicher und zuverlässig. Als zusätzlichen Schutz vor Zugriffen durch unbefugte Clients können Sie Direktiven aus dem Secure Link-Modul verwenden, um festzulegen, dass Clients einen bestimmten Hash-String in die URL der angeforderten Ressource aufnehmen müssen.

In diesem Blogbeitrag erläutern wir, wie Sie die beiden im Secure-Link-Modul implementierten Methoden konfigurieren. Die Beispielkonfigurationen schützen HTML-Dateien und Mediendateien mit Wiedergabelisten, können jedoch auf jede Art von HTTP-URL angewendet werden. Die Methoden gelten sowohl für NGINX als auch für NGINX Plus. Der Kürze halber beziehen wir uns im weiteren Verlauf des Blogbeitrags jedoch nur auf NGINX Plus.

Überblick über die Methoden des Secure-Link-Moduls

Das Secure-Link-Modul überprüft die Gültigkeit einer angeforderten Ressource, indem es eine codierte Zeichenfolge in der URL der HTTP-Anfrage mit der Zeichenfolge vergleicht, die es für diese Anfrage berechnet. Wenn ein Link eine begrenzte Gültigkeitsdauer hat und diese abgelaufen ist, gilt der Link als veraltet. Der Status dieser Prüfungen wird in der Variablen $secure_link erfasst und zur Steuerung des Verarbeitungsablaufs verwendet.

Wie bereits erwähnt, stellt das Modul zwei Methoden bereit. In einem bestimmten http-, server- oder location-Kontext kann jeweils nur eine davon konfiguriert werden.

  • Der erste und einfachere Modus wird durch die secure_link_secret-Direktive aktiviert. Die codierte Zeichenfolge ist ein MD5-Hash, der aus der Verkettung zweier Textzeichenfolgen berechnet wird: dem letzten Teil der URL und einem geheimen Wort, das in der NGINX Plus-Konfiguration definiert ist. (Einzelheiten zur ersten Textzeichenfolge finden Sie unter „Grundlegende gesicherte URLs verwenden“.) Um auf die geschützte Ressource zuzugreifen, muss der Client den Hash direkt nach dem URL-Präfix einfügen. Dieses Präfix ist eine beliebige Zeichenfolge ohne Schrägstriche. In dieser Beispiel-URL lautet das Präfix videos und die geschützte Ressource ist die Datei bunny.m3u8: /videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8 Ein Anwendungsfall für diese Methode liegt vor, wenn ein Benutzer ein Bild oder Dokument zur Freigabe auf einen Server hochlädt, aber verhindern möchte, dass Personen, die den Dateinamen kennen, darauf zugreifen können, bevor der offizielle Link veröffentlicht wurde.
  • Die zweite, flexiblere Methode wird durch die Direktiven secure_link und secure_link_md5 aktiviert. Hier ist die codierte Zeichenfolge ein MD5-Hash aus Variablen, die in der NGINX Plus-Konfigurationsdatei definiert sind. Meist wird die Variable $remote_addr einbezogen, um den Zugriff auf eine bestimmte Client-IP-Adresse zu beschränken. Sie können jedoch auch andere Werte verwenden, beispielsweise $http_user_agent. Diese Variable erfasst den User-Agent-Header und beschränkt so den Zugriff auf bestimmte Browser. Optional können Sie ein Ablaufdatum angeben, nach dem die URL nicht mehr funktioniert, selbst wenn der Hash korrekt ist. Der Client muss das Argument md5 an die Anfrage-URL anhängen, um den Hash anzugeben. Wenn die gehashte Zeichenfolge ein Ablaufdatum enthält, muss der Client außerdem das Argument expires anhängen, um das Datum anzugeben, wie in dieser Beispiel-URL zum Abrufen der geschützten Datei pricelist.html: /files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740

Das Secure-Link-Modul ist in den vorkompilierten Open-Source-NGINX-Binärdateien von nginx.org, den von Betriebssystemanbietern bereitgestellten NGINX-Paketen und in NGINX Plus enthalten. Wenn Sie NGINX aus dem Quellcode erstellen, ist es standardmäßig nicht enthalten. Aktivieren Sie es, indem Sie das Argument --with-http_secure_link_module für den Befehl configure angeben.

Einfache abgesicherte URLs verwenden

Die einfachere Methode zum Absichern von URLs verwendet die Direktive „secure_link_secret“. Im folgenden Beispielcode sichern wir eine HTTP-Live-Streaming-Medienwiedergabeliste (HLS) namens „/bunny.m3u8“. Sie ist im Verzeichnis „/opt/secure/hls“ gespeichert, wird Clients jedoch über eine URL bereitgestellt, die mit dem Präfix „videos“ beginnt.

server {
    listen 80;
    server_name secure-link-demo;

    location /videos {
        secure_link_secret enigma;
        if ($secure_link = "") { return 403; }

        rewrite ^ /secure/$secure_link;
    }

    location /secure {
        internal;
        root /opt;
    }
}

Mit dieser Konfiguration müssen Clients für den Zugriff auf die Datei /opt/secure/hls/bunny.m3u8 die folgende URL aufrufen:

/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8

Die gehashte Zeichenfolge folgt direkt auf das Präfix, bei dem es sich um eine beliebige Zeichenfolge ohne Schrägstriche handelt (hier: videos).

Der Hash wird aus einer Zeichenfolge berechnet, die zwei Elemente miteinander verknüpft:

  • Der Teil der URL, der auf den Hash folgt, hier „hls/bunny.m3u8“.
  • Der Parameter der secure_link_secret-Direktive, hier enigma.

Wenn die Anforderungs-URL des Clients nicht den richtigen Hash enthält, setzt NGINX Plus die Variable $secure_link auf eine leere Zeichenfolge. Die if-Prüfung schlägt fehl und NGINX Plus gibt in der HTTP-Antwort den Statuscode 403 Forbidden zurück.

Andernfalls (d. h., der Hash ist korrekt) schreibt die Direktive „rewrite“ die URL um – in unserem Beispiel in „/secure/hls/bunny.m3u8“ (die Variable „$secure_link“ erfasst den Teil der URL, der auf den Hash folgt). URLs, die mit „/secure“ beginnen, werden vom zweiten „location“-Block verarbeitet. Die Direktive „root“ in diesem Block legt „/opt“ als Stammverzeichnis für angeforderte Dateien fest, und die Direktive „internal“ gibt an, dass der Block nur für intern generierte Anfragen verwendet wird.

Generieren des Hashs auf dem Client für eine einfache gesicherte URL

Um den MD5-Hash im Hexadezimalformat zu erhalten, den der Client in die URL aufnehmen muss, führen wir den Befehl „openssl md5“ mit der Option „-hex“ aus:

# echo -n 'hls/bunny.m3u8enigma' | openssl md5 -hex(stdin)= 80e2dfecb5f54513ad4e2e6217d36fd4

Eine Erläuterung zur programmgesteuerten Erzeugung von Hashes finden Sie unter „Programmgesteuertes Erzeugen des Hashes“.

Serverantwort auf einfach gesicherte URLs

Die folgenden beispielhaften curl-Befehle zeigen, wie der Server auf verschiedene geschützte URLs reagiert.

Wenn die URL den korrekten MD5-Hash enthält, lautet die Antwort 200 OK:

# curl -I http://secure-link-demo/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8 | head -n 1HTTP/1.1 200 OK

Wenn der MD5-Hash falsch ist, lautet die Antwort 403 Forbidden:

# curl -I http://secure-link-demo/videos/2c5e80de986b6fc80dd33e16cf824123/hls/bunny.m3u8 | head -n 1HTTP/1.1 403 Forbidden

Wenn der Hash für „bunny.m3u8“ für eine andere Datei verwendet wird, lautet die Antwort ebenfalls 403 Forbidden:

# curl -I http://secure-link-demo/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hs/oven.m3u8 | head -n 1HTTP/1.1 403 Forbidden

Verwendung gesicherter URLs mit Ablaufdatum

Die flexiblere Methode zum Absichern von URLs verwendet die Direktiven „secure_link“ und „secure_link_md5“. In diesem Beispiel verwenden wir sie, um den Zugriff auf die Datei „/var/www/files/pricelist.html“ ausschließlich Clients mit der IP-Adresse 192.168.33.14 und nur bis zum 31. Dezember 2016 zu erlauben.

Unser virtueller Server lauscht auf Port 80 und verarbeitet alle abgesicherten HTTP-Anfragen im location /files-Block, in dem die root-Direktive /var/www als Stammverzeichnis für die angeforderten Dateien festlegt.

Die secure_link-Direktive definiert zwei Variablen, die Argumente in der Anfrage-URL erfassen: $arg_md5 wird auf den Wert des Arguments md5 gesetzt und $arg_expires auf den Wert des Arguments expires.

Die Direktive „secure_link_md5“ definiert den Ausdruck, der gehasht wird, um den MD5-Wert für die Anfrage zu erzeugen. Bei der Verarbeitung der URL wird der Hash mit dem Wert von $arg_md5 verglichen. Der hier verwendete Beispielausdruck enthält die in der Anfrage übermittelte Ablaufzeit (die in der Variablen $secure_link_expires erfasst wird), die URL ($uri), die IP-Adresse des Clients ($remote_addr) und das Wort „enigma“.

server {
    listen 80;
        server_name secure-link-demo;

        location /files {
            root /var/www;
            secure_link $arg_md5,$arg_expires;
            secure_link_md5 "$secure_link_expires$uri$remote_addr enigma";

            if ($secure_link = "") { return 403; }
            if ($secure_link = "0") { return 410; }
        }
}

Mit dieser Konfiguration muss ein Client mit der IP-Adresse 192.168.33.14 für den Zugriff auf /var/www/files/pricelist.html die Anfrage-URL vor Sa., 31. Dez. 2016, 23:59:00 UTC senden:

/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740

Wenn der Hash in der vom Client gesendeten URL (erfasst in der Variable $arg_md5) nicht mit dem anhand der Direktive secure_link_md5 berechneten Hash übereinstimmt, setzt NGINX Plus die Variable $secure_link auf eine leere Zeichenfolge. Der if-Test schlägt fehl und NGINX Plus gibt in der HTTP-Antwort den Statuscode 403 Forbidden zurück.

Wenn die Hashwerte übereinstimmen, der Link jedoch abgelaufen ist, setzt NGINX Plus die Variable $secure_link auf 0; auch hier schlägt die if-Prüfung fehl, aber diesmal gibt NGINX Plus in der HTTP-Antwort den Statuscode 410 Gone zurück.

Generieren des Hashwerts und der Ablaufzeit auf einem Client

Sehen wir uns nun an, wie ein Client die Argumente md5 und expires berechnet, die in die URL aufgenommen werden sollen.

Im ersten Schritt wird die dem Ablaufdatum entsprechende Unix-Zeit ermittelt, da dieser Wert in Form der Variable $secure_link_expires in den Hash-Ausdruck einfließt. Um die Unix-Zeit – die Anzahl der seit Beginn der Unix-Epoche (1970-01-01 00:00:00 UTC) vergangenen Sekunden – zu ermitteln, verwenden wir den Befehl date mit der Option -d und der Formatangabe +%s.

In unserem Beispiel legen wir den Ablaufzeitpunkt auf Samstag, den 31. Dezember 2016, 23:59:00 Uhr UTC fest. Der Befehl lautet daher:

# date -d "2016-12-31 23:59" +%s1483228740

Der Client fügt diesen Wert als Argument expires=1483228740 in die Anfrage-URL ein.

Nun verarbeiten wir die durch die secure_link_md5-Direktive definierte Zeichenfolge – $secure_link_expires$uri$remote_addr enigma – mit drei Befehlen:

  • Der Befehl openssl md5 mit der Option -binary erzeugt den MD5-Hash im Binärformat.
  • Der Befehl „openssl base64“ wendet eine Base64-Codierung auf den Hashwert an.
  • Die tr-Befehle ersetzen im codierten Wert das Pluszeichen ( + ) durch den Bindestrich ( - ) und den Schrägstrich ( / ) durch den Unterstrich ( _ ) und löschen das Gleichheitszeichen (=).

Für unser Beispiel lautet der vollständige Befehl:

# echo -n '1483228740/files/pricelist.html192.168.33.14 enigma' | openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =AUEnXC7T-Tfv9WLsWbf-mw

Der Client fügt diesen Wert als das Argument „md5=AUEnXC7T-Tfv9WLsWbf-mw“ in die Anfrage-URL ein.

Programmgesteuertes Generieren des Hashwerts

Wenn Ihr NGINX Plus-Webserver dynamische Inhalte von einem Anwendungsserver bereitstellt, müssen sowohl NGINX Plus als auch der Anwendungsserver dieselbe gesicherte URL verwenden. Sie können den Hash für das md5-Argument in der URL programmgesteuert generieren. Die folgende Node.js-Funktion generiert einen Hash, der mit dem im obigen NGINX Plus-Konfigurationsausschnitt definierten Hash übereinstimmt. Sie verwendet eine Ablaufzeit, eine URL, eine Client-IP-Adresse und ein Geheimwort als Argumente und gibt den Base64-codierten MD5-Hash im Binärformat zurück.

var crypto = require("crypto");

function generateSecurePathHash(expires, url, client_ip, secret) {
    if (!expires || !url || !client_ip || !secret) {
        return undefined;
    }
    
    var input = expires + url + client_ip + " " + secret;
    var binaryHash = crypto.createHash("md5").update(input).digest();
    var base64Value = new Buffer(binaryHash).toString('base64');
    return base64Value.replace(/=/g, '').replace(/+/g, '-').replace(///g, '_');
}

Um den Hash für unser aktuelles Beispiel zu berechnen, übergeben wir diese Argumente:

generateSecurePathHash(new Date('12/31/2016 23:59:00').getTime()), '/files/pricelist.html', “192.168.33.14”, "enigma");

Serverantwort auf gesicherte URLs mit Ablaufzeiten

Die folgenden beispielhaften curl-Befehle zeigen, wie der Server auf gesicherte URLs reagiert.

Wenn ein Client mit der IP-Adresse 192.168.33.14 den korrekten MD5-Hash und die korrekte Ablaufzeit übermittelt, lautet die Antwort 200 OK:

# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740' | head -n 1
HTTP/1.1 200 OK

Wenn ein Client mit einer anderen IP-Adresse dieselbe URL sendet, lautet die Antwort 403 Forbidden:

# curl -I --interface "192.168.33.33" 'http://secure-link-demo/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740' | head -n 1HTTP/1.1 403 Forbidden

Wenn der Hashwert des Arguments „md5“ falsch ist, lautet die Antwort 403 Forbidden:

# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=qeUNjiY2FTIVMaXUsxG-7w&expires=1483228740' | head -n 1HTTP/1.1 403 Forbidden

Wenn die URL abgelaufen ist (das durch das Argument „expires“ angegebene Datum liegt in der Vergangenheit), lautet die Antwort 410 Gone:

# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=Z2rNva2InyVcRTlhqAkT4Q&expires=1467417540' | head -n 1HTTP/1.1 410 Gone

Beispiel – Segmentdateien mit einem Ablaufdatum schützen

Hier ist ein weiteres Beispiel für eine gesicherte URL mit Ablaufdatum, die sowohl die Wiedergabeliste eines Medien-Assets als auch die Segmentdateien schützt.

Ein Unterschied zum vorherigen Beispiel besteht darin, dass wir hier einen map-Konfigurationsblock hinzufügen, um die Dateierweiterung aus der Wiedergabeliste (Datei mit der Endung .m3u8) und den HLS-Segmenten (Dateien mit der Endung .ts) zu entfernen, wenn wir den Dateinamen in der Variablen $file_name erfassen, die an die Direktive secure_link_md5 übergeben wird. Dadurch werden sowohl Anfragen für die einzelnen .ts-Segmente als auch für die Wiedergabeliste abgesichert.

Ein weiterer Unterschied zum ersten Beispiel besteht darin, dass wir die Variable $http_user_agent (die den User-Agent-Header erfasst) in die Direktive secure_link_md5 aufnehmen, um den Zugriff auf Clients mit bestimmten Webbrowsern zu beschränken (beispielsweise damit die URL in Safari funktioniert, nicht aber in Chrome oder Firefox).

map $uri $file_name {
    default none;
    "~*/s/(?<name>.*).m3u8" $name;
    "~*/s/(?<name>.*).ts" $name;
}

server {
    listen 80;
    server_name secure-link-demo;

    location /s {
        root /opt;
        secure_link $arg_md5,$arg_expires;
        secure_link_md5 "$secure_link_expires$file_name$http_user_agent enigma";

        if ($secure_link = "") { return 403; }
        if ($secure_link = "0") { return 410; }
    }
}

Zusammenfassung

Mit dem Secure-Link-Modul in NGINX können Sie Dateien vor unbefugtem Zugriff schützen, indem Sie codierte Daten wie den Hash eines bestimmten Teils der URL hinzufügen. Durch das Hinzufügen einer Ablaufzeit lässt sich außerdem die Gültigkeitsdauer von Links begrenzen, was die Sicherheit zusätzlich erhöht.

Wenn Sie NGINX Plus ausprobieren möchten, starten Sie noch heute Ihre kostenlose 30-Tage-Testversion oder kontaktieren Sie uns, um Ihre Anwendungsfälle zu besprechen.


Teilen
Schlagwörter: Security, F5 NGINX, Tech, 2016

Über den Autor

Kunal Pariani
Kunal ParianiArchitekt für technische Lösungen

Weitere Blogbeiträge von Kunal Pariani

Ähnliche Blogbeiträge

Die Zukunft von NGINX: Zurück zu unseren Open-Source-Wurzeln
NGINX | 23.08.2022

Die Zukunft von NGINX: Zurück zu unseren Open-Source-Wurzeln

Beim NGINX Sprint 2022 besinnen wir uns auf unsere Open-Source-Wurzeln. Erfahren Sie mehr über die Initiativen, die wir im kommenden Jahr verfolgen, und teilen Sie uns mit, was Sie sich wünschen.

Ein Leitfaden zur Auswahl eines Ingress-Controllers, Teil 1: Ermitteln Sie Ihre Anforderungen
NGINX | 08.09.2021

Ein Leitfaden zur Auswahl eines Ingress-Controllers, Teil 1: Ermitteln Sie Ihre Anforderungen

Ein Ingress-Controller kann eines der leistungsstärksten Tools in Ihrem Kubernetes-Stack sein. Erfahren Sie, wie Sie Ihre Ingress-Anforderungen ermitteln, um die beste Option auszuwählen.

Bereitstellung des NGINX Ingress Controllers auf Amazon EKS: So haben wir getestet
NGINX | 16.08.2021

Bereitstellung des NGINX Ingress Controllers auf Amazon EKS: So haben wir getestet

Wir haben kürzlich den Lösungsleitfaden zum NGINX Ingress Controller um Empfehlungen zur Dimensionierung für Amazon Elastic Kubernetes Service (EKS) ergänzt. In diesem Blogbeitrag erläutern wir, wie wir diese Empfehlungen erarbeitet haben, und stellen Ihnen alle Informationen zur Verfügung, die Sie benötigen, um selbst ähnliche Tests durchzuführen.

Protokollierung des Datenverkehrs zwischen NGINX und Upstream-Servern bei CDN77
NGINX | 12.12.2018

Protokollierung des Datenverkehrs zwischen NGINX und Upstream-Servern bei CDN77

Bei der Fehlersuche bei Datenverkehrsproblemen zwischen NGINX und Upstream-Servern konnte CDN77 die benötigten Daten nur aus dem Fehlerprotokoll mit aktivierter Debug-Ausgabe beziehen, was die Leistung beeinträchtigt und riesige Dateien erzeugt. Daher entwickelte das Team ein neues benutzerdefiniertes Modul, das ausschließlich die benötigten Daten protokolliert.

Ankündigung von NGINX Plus R16
NGINX | 05.09.2018

Ankündigung von NGINX Plus R16

NGINX Plus R16 ist eine einheitliche, elastische Ingress- und Egress-Schicht für Anwendungen, die die Funktionen eines Load Balancers, API-Gateways und einer WAF vereint und neue Features wie clusterfähige Ratenbegrenzung, Schlüssel-Wert-Speicher, „Random with Two Choices“, verbessertes UDP-Load-Balancing und vieles mehr bietet.

5 Gründe für den Umstieg auf softwarebasiertes Load Balancing
NGINX | 13.10.2016

5 Gründe für den Umstieg auf softwarebasiertes Load Balancing

Entdecken Sie fünf Gründe für den Wechsel zu einem softwarebasierten Load Balancer wie NGINX Plus, darunter Kosteneinsparungen, die Einführung von DevOps und eine einheitliche Lösung für alle Umgebungen.