Dies ist der zweite Blogbeitrag unserer Reihe zur Bereitstellung von NGINX Open Source und NGINX Plus als API-Gateway.
Teil 1 bietet detaillierte Konfigurationsanweisungen für mehrere Anwendungsfälle.
Dieser Beitrag erweitert diese Anwendungsfälle und befasst sich mit einer Reihe von Sicherheitsvorkehrungen, die zum Schutz und zur Sicherung von Back-End-API-Diensten in der Produktion angewendet werden können:
Dieser Beitrag wurde ursprünglich im Jahr 2018 veröffentlicht und aktualisiert, um die aktuellen Best Practices für die API-Konfiguration widerzuspiegeln. Dabei werden verschachtelte Standortblöcke zum Weiterleiten von Anfragen verwendet, anstatt
Regeln neu zu schreiben.
Notiz : Sofern nicht anders angegeben, gelten alle Informationen in diesem Beitrag sowohl für NGINX Open Source als auch für NGINX Plus. Der Einfachheit halber wird im Rest des Blogs einfach von „NGINX“ gesprochen.
Im Gegensatz zu browserbasierten Clients können einzelne API-Clients Ihre APIs enorm belasten. Dabei geht der Verbrauch sogar so weit, dass ein so großer Teil der Systemressourcen verbraucht wird, dass andere API-Clients effektiv ausgesperrt werden. Diese Gefahr geht nicht nur von böswilligen Clients aus: Ein sich schlecht verhaltender oder fehlerhafter API-Client kann in eine Schleife geraten, die das Backend überlastet. Zum Schutz davor wenden wir eine Ratenbegrenzung an, um eine faire Nutzung durch jeden Client sicherzustellen und die Ressourcen der Backend-Dienste zu schützen.
NGINX kann Ratenbegrenzungen basierend auf jedem Attribut der Anfrage anwenden. Normalerweise wird die Client-IP-Adresse verwendet, wenn jedoch die Authentifizierung für die API aktiviert ist, ist die authentifizierte Client-ID ein zuverlässigeres und genaueres Attribut.
Die Ratenbegrenzungen selbst werden in der API-Gateway-Konfigurationsdatei der obersten Ebene definiert und können dann global, pro API oder sogar pro URI angewendet werden.
In diesem Beispiel definiert die Direktive „limit_req_zone“
in Zeile 4 eine Ratenbegrenzung von 10 Anfragen pro Sekunde für jede Client-IP-Adresse ( $binary_remote_addr
), und die Direktive in Zeile 5 definiert eine Begrenzung von 200 Anfragen pro Sekunde für jede authentifizierte Client-ID ( $http_apikey
). Dies veranschaulicht, wie wir mehrere Ratenbegrenzungen unabhängig davon definieren können, wo sie angewendet werden. Eine API kann mehrere Ratenbegrenzungen gleichzeitig anwenden oder unterschiedliche Ratenbegrenzungen für unterschiedliche Ressourcen anwenden.
Anschließend verwenden wir im folgenden Konfigurationsausschnitt die Direktive limit_req
, um die erste Ratenbegrenzung im Richtlinienabschnitt der in Teil 1<.htmla> beschriebenen „Warehouse API“ anzuwenden. Standardmäßig sendet NGINX die 503
(Dienst
nicht verfügbar)
-Antwort, wenn die Ratenbegrenzung überschritten wurde. Für API-Clients ist es jedoch hilfreich, explizit zu wissen, dass sie ihr Ratenlimit überschritten haben, damit sie ihr Verhalten ändern können. Zu diesem Zweck verwenden wir die Direktive limit_req_status
, um die 429
(Zu
viele
Anfragen)
-Antwort stattdessen.
Sie können zusätzliche Parameter für die Direktive „limit_req“
verwenden, um die Art und Weise zu optimieren, wie NGINX Ratenbegrenzungen erzwingt. So ist es beispielsweise möglich, Anfragen in eine Warteschlange zu stellen, statt sie bei Überschreiten des Grenzwerts direkt abzulehnen. Dadurch bleibt Zeit, bis die Anzahl der Anfragen unter den festgelegten Grenzwert fällt. Weitere Informationen zum Feinabstimmen von Ratenbegrenzungen finden Sie unter Ratenbegrenzung mit NGINX und NGINX Plus in unserem Blog.
Bei RESTful-APIs ist die HTTP-Methode (oder das Verb) ein wichtiger Teil jedes API-Aufrufs und für die API-Definition von großer Bedeutung. Nehmen Sie als Beispiel den Preisservice unserer Warehouse-API:
GET
/api/warehouse/pricing/item001
gibt den Preis von item001 zurückPATCH
/api/warehouse/pricing/item001
ändert den Preis von item001Wir können die URI-Routingdefinitionen in der Warehouse-API aktualisieren, um in Anfragen an den Preisdienst nur diese beiden HTTP-Methoden zu akzeptieren (und in Anfragen an den Inventardienst nur die GET-
Methode).
Mit dieser Konfiguration werden Anfragen an den Preisdienst mit anderen Methoden als den in Zeile 22 aufgeführten (und an den Inventardienst mit anderen Methoden als denen in Zeile 13) abgelehnt und nicht an die Back-End-Dienste weitergeleitet. NGINX sendet die 405
(Methode
nicht
zulässig)
-Antwort, um den API-Client über die genaue Art des Fehlers zu informieren, wie in der folgenden Konsolenablaufverfolgung gezeigt. Wenn eine Sicherheitsrichtlinie mit minimaler Offenlegung erforderlich ist, kann die Direktive error_page
verwendet werden, um diese Antwort stattdessen in einen weniger informativen Fehler umzuwandeln, zum Beispiel400
(Ungültige
Anforderung)
.
$ curl https://api.example.com/api/warehouse/pricing/item001 {"sku":"item001","price":179.99} $ curl -X DELETE https://api.example.com/api/warehouse/pricing/item001 {"status":405,"message":"Methode nicht zulässig"}
Teil 1 dieser Reihe beschreibt, wie Sie APIs durch die Aktivierung von Authentifizierungsoptionen wie API-Schlüsseln und JSON Web Tokens (JWTs) vor unbefugtem Zugriff schützen können. Wir können die authentifizierte ID oder Attribute der authentifizierten ID verwenden, um eine fein abgestufte Zugriffskontrolle durchzuführen.
Hier zeigen wir zwei solcher Beispiele.
Natürlich sind auch andere Authentifizierungsmethoden auf diese Beispielanwendungsfälle anwendbar, wie etwa die HTTP-Basisauthentifizierung und die OAuth 2.0-Token-Introspektion .
Nehmen wir an, wir möchten nur „Infrastrukturclients“ den Zugriff auf die Audit- Ressource des Warehouse-API-Inventardienstes erlauben. Wenn die API-Schlüsselauthentifizierung aktiviert ist, verwenden wir einen Map-
Block, um eine Whitelist mit Infrastruktur-Clientnamen zu erstellen, sodass die Variable $is_infrastructure
wie folgt ausgewertet wird:1
wenn ein entsprechender API-Schlüssel verwendet wird.
In der Definition der Warehouse-API fügen wir einen Standortblock
für die Bestandsprüfungsressource hinzu ( Zeilen 15–20 ). Der if
-Block stellt sicher, dass nur Infrastrukturclients auf die Ressource zugreifen können.
Beachten Sie, dass die Standortanweisung
in Zeile 15 den Modifikator =
(Gleichheitszeichen) verwendet, um eine genaue Übereinstimmung mit der Audit- Ressource zu erzielen. Genaue Übereinstimmungen haben Vorrang vor den für die anderen Ressourcen verwendeten Standardpfadpräfixdefinitionen. Die folgende Ablaufverfolgung zeigt, wie bei dieser Konfiguration ein Client, der nicht auf der Whitelist steht, nicht auf die Inventarüberwachungsressource zugreifen kann. Der angezeigte API-Schlüssel gehört zu client_two (wie in Teil 1 definiert).
$ curl -H "API-Schlüssel: QzVV6y1EmQFbbxOfRCwyJs35" https://api.example.com/api/warehouse/inventory/audit {"status":403,"message":"Verboten"}
Wie oben definiert akzeptiert der Preisdienst die Methoden GET
und PATCH
, die es Clients jeweils ermöglichen, den Preis eines bestimmten Artikels abzurufen und zu ändern. (Wir könnten auch die POST-
und DELETE
-Methoden zulassen, um eine vollständige Lebenszyklusverwaltung der Preisdaten zu ermöglichen.) In diesem Abschnitt erweitern wir diesen Anwendungsfall, um zu steuern, welche Methoden bestimmte Benutzer ausgeben können. Wenn die JWT-Authentifizierung für die Warehouse-API aktiviert ist, werden die Berechtigungen für jeden Client als benutzerdefinierte Ansprüche codiert. Die an Administratoren ausgegebenen JWTs, die Änderungen an Preisdaten vornehmen dürfen, enthalten die Anforderung „admin“:true
. Wir erweitern jetzt unsere Zugriffskontrolllogik, sodass nur Administratoren Änderungen vornehmen können.
Dieser Map-
Block, der unten zu api_gateway.conf hinzugefügt wird, verwendet die Anforderungsmethode ( $request_method
) als Eingabe und erstellt eine neue Variable, $admin_permitted_method
. Nur-Lese-Methoden sind immer zulässig (Zeilen 62–64), aber der Zugriff auf Schreibvorgänge hängt vom Wert des Administratoranspruchs im JWT ab (Zeile 65). Wir erweitern jetzt unsere Warehouse-API-Konfiguration, um sicherzustellen, dass nur Administratoren Preisänderungen vornehmen können.
Die Warehouse-API erfordert von allen Clients die Vorlage eines gültigen JWT (Zeile 7). Außerdem prüfen wir, ob Schreibvorgänge zulässig sind, indem wir die Variable $admin_permitted_method
auswerten (Zeile 25). Beachten Sie erneut, dass die JWT-Authentifizierung exklusiv für NGINX Plus ist.
HTTP-APIs verwenden den Anforderungstext üblicherweise, um Anweisungen und Daten für die Verarbeitung durch den Back-End-API-Dienst aufzunehmen. Dies gilt sowohl für XML/SOAP-APIs als auch für JSON/REST-APIs. Folglich kann der Anforderungstext einen Angriffsvektor für die Back-End-API-Dienste darstellen, die bei der Verarbeitung sehr großer Anforderungstexte anfällig für Pufferüberlaufangriffe sein können.
Standardmäßig lehnt NGINX Anfragen mit einem Textkörper größer als 1 MB ab. Dieser Wert kann für APIs erhöht werden, die speziell mit großen Nutzlasten wie der Bildverarbeitung arbeiten, für die meisten APIs legen wir jedoch einen niedrigeren Wert fest.
Die Direktive „client_max_body_size“
in Zeile 7 begrenzt die Größe des Anforderungstexts. Mit dieser Konfiguration können wir das Verhalten des API-Gateways beim Empfang von zwei verschiedenen PATCH
-Anfragen an den Preisdienst vergleichen. Der erste Curl
-Befehl sendet ein kleines JSON-Datenstück, während der zweite Befehl versucht, den Inhalt einer großen Datei ( /etc/services ) zu senden.
$ curl -iX PATCH -d '{"price":199.99}' https://api.example.com/api/warehouse/pricing/item001 HTTP/1.1 204 Kein Inhalt Server: nginx/1.19.5 Verbindung: Keep-Alive $ curl -iX PATCH -d@/etc/services https://api.example.com/api/warehouse/pricing/item001 HTTP/1.1 413 Anforderungseinheit zu groß Server: nginx/1.19.5 Inhaltstyp: application/json Inhaltslänge: 45 Verbindung: schließen {"status":413,"message":"Nutzlast zu groß"}
[ Herausgeber – Der folgende Anwendungsfall ist einer von mehreren für das NGINX JavaScript-Modul. Eine vollständige Liste finden Sie unter Anwendungsfälle für das NGINX-JavaScript-Modul . ]
Back-End-API-Dienste sind nicht nur anfällig für Pufferüberlaufangriffe mit großen Anforderungstexten, sondern können auch anfällig für Textkörper sein, die ungültige oder unerwartete Daten enthalten. Für Anwendungen, die korrekt formatiertes JSON im Anforderungstext erfordern, können wir das NGINX-JavaScript-Modul <.htmla> verwenden, um zu überprüfen, ob die JSON-Daten fehlerfrei analysiert werden, bevor sie an den Back-End-API-Dienst weitergeleitet werden.
Wenn das JavaScript-Modul installiert ist , verwenden wir die Direktive js_import,
um auf die Datei zu verweisen, die den JavaScript-Code für die Funktion enthält, die JSON-Daten validiert.
Die Direktive js_set
definiert eine neue Variable, $json_validated
, die durch Aufrufen der Funktion parseRequestBody
ausgewertet wird.
Die Funktion parseRequestBody
versucht, den Anforderungstext mit der Methode JSON.parse
zu analysieren (Zeile 6). Wenn die Analyse erfolgreich ist, wird der Name der beabsichtigten Upstream-Gruppe für diese Anforderung zurückgegeben (Zeile 8). Wenn der Anforderungstext nicht analysiert werden kann (was zu einer Ausnahme führt), wird eine lokale Serveradresse zurückgegeben (Zeile 11). Die Return-
Direktive füllt die Variable $json_validated,
sodass wir sie verwenden können, um zu bestimmen, wohin die Anfrage gesendet werden soll.
Im URI-Routing-Abschnitt der Warehouse-API ändern wir die Proxy_pass-
Direktive in Zeile 22. Es übergibt die Anforderung an den Back-End-API-Dienst wie in den in den vorherigen Abschnitten beschriebenen Warehouse-API-Konfigurationen, verwendet jetzt jedoch die Variable $json_validated
als Zieladresse. Wenn der Client-Text erfolgreich als JSON analysiert wurde, leiten wir einen Proxy an die in Zeile 15 definierte Upstream-Gruppe weiter. Wenn jedoch eine Ausnahme aufgetreten ist, verwenden wir den zurückgegebenen Wert 127.0.0.1:10415,
um eine Fehlerantwort an den Client zu senden.
Wenn Anfragen an diesen virtuellen Server weitergeleitet werden, sendet NGINX die 415
(Nicht unterstützter
Medientyp
)
-Antwort an den Client.
Wenn diese vollständige Konfiguration vorhanden ist, leitet NGINX Anfragen nur dann an den Back-End-API-Dienst weiter, wenn sie über korrekt formatierte JSON-Texte verfügen.
$ curl -iX POST -d '{"sku":"item002","price":85.00}' https://api.example.com/api/warehouse/pricing HTTP/1.1 201 Erstellt Server: nginx/1.19.5 Standort: /api/warehouse/pricing/item002 $ curl -X POST -d 'item002=85.00' https://api.example.com/api/warehouse/pricing {"status":415,"message":"Nicht unterstützter Medientyp"}
$request_body
Die JavaScript-Funktion parseRequestBody
verwendet die Variable $request_body,
um eine JSON-Analyse durchzuführen. NGINX füllt diese Variable jedoch nicht standardmäßig auf und streamt den Anforderungstext einfach an das Backend, ohne Zwischenkopien zu erstellen. Durch Verwendung der Mirror-
Direktive im URI-Routingabschnitt (Zeile 16) erstellen wir eine Kopie der Clientanforderung und füllen folglich die Variable $request_body
.
Die Anweisungen in den Zeilen 17 und 19 steuern, wie NGINX den Anforderungstext intern behandelt. Wir setzen client_body_buffer_size
auf die gleiche Größe wie client_max_body_size,
damit der Anforderungstext nicht auf die Festplatte geschrieben wird. Dadurch wird die Gesamtleistung durch Minimieren der Festplatten-E/A-Vorgänge verbessert, allerdings auf Kosten einer zusätzlichen Speichernutzung. Für die meisten API-Gateway-Anwendungsfälle mit kleinen Anforderungstexten ist dies ein guter Kompromiss.
Wie erwähnt, erstellt die Mirror-
Direktive eine Kopie der Client-Anfrage. Da wir diese Kopie nicht zum Auffüllen von $request_body
benötigen, senden wir sie an eine Sackgasse ( /_get_request_body
), die wir im Serverblock
in der API-Gateway-Konfiguration der obersten Ebene definieren.
Dieser Standort sendet lediglich die 204
(Kein
Inhalt)
Antwort. Da sich diese Antwort auf eine gespiegelte Anfrage bezieht, wird sie ignoriert und verursacht daher nur einen vernachlässigbaren Mehraufwand bei der Verarbeitung der ursprünglichen Clientanfrage.
In diesem zweiten Blogbeitrag unserer Reihe über die Bereitstellung von NGINX Open Source und NGINX Plus als API-Gateway haben wir uns auf die Herausforderung konzentriert, Backend-API-Dienste in einer Produktionsumgebung vor böswilligen und sich schlecht verhaltenden Clients zu schützen. NGINX verwendet zur Verwaltung des API-Verkehrs dieselbe Technologie, die auch zum Betrieb und Schutz der meistgenutzten Websites im Internet verwendet wird.
Schauen Sie sich den anderen Beitrag dieser Reihe an:
Um NGINX Plus als API-Gateway auszuprobieren, starten Sie noch heute Ihre kostenlose 30-Tage-Testversion oder kontaktieren Sie uns, um Ihre Anwendungsfälle zu besprechen . Verwenden Sie während Ihrer Testphase den vollständigen Satz an Konfigurationsdateien aus unserem GitHub Gist-Repository .
„Dieser Blogbeitrag kann auf Produkte verweisen, die nicht mehr verfügbar und/oder nicht mehr unterstützt werden. Die aktuellsten Informationen zu verfügbaren F5 NGINX-Produkten und -Lösungen finden Sie in unserer NGINX-Produktfamilie . NGINX ist jetzt Teil von F5. Alle vorherigen NGINX.com-Links werden auf ähnliche NGINX-Inhalte auf F5.com umgeleitet."