Anfragen mit bedingter NGINX-Protokollierung stichprobenartig erfassen

NGINX | 24. April 2019

NGINX kann jede verarbeitete Transaktion sehr detailliert protokollieren. Solche Protokolle werden als „Zugriffsprotokolle“ bezeichnet. Mithilfe eines anpassbaren Protokolldateiformats können Sie genau festlegen, welche Details für verschiedene Dienste oder Speicherorte aufgezeichnet werden.

Standardmäßig protokolliert NGINX jede verarbeitete Transaktion. Dies kann aus Compliance- oder Sicherheitsgründen erforderlich sein, doch bei einer stark frequentierten Website kann die Menge der erzeugten Daten überwältigend sein. In diesem Artikel zeigen wir, wie sich Transaktionen anhand verschiedener Kriterien selektiv protokollieren lassen und wie Sie dieses Wissen nutzen können, um schnell und ressourcenschonend Stichproben von Anfragedaten zu erfassen.

Sofern nicht anders angegeben, gilt dieser Beitrag sowohl für NGINX Open Source als auch für NGINX Plus. Der besseren Lesbarkeit halber verwenden wir im Folgenden durchgehend die Bezeichnung „NGINX“.

Hintergrund – Kurzer Überblick über die Konfiguration des NGINX-Zugriffsprotokolls

NGINX-Zugriffsprotokolle werden mit der Direktive „log_format“ definiert. Sie können mehrere unterschiedlich benannte Protokollformate festlegen, beispielsweise ein vollständiges Protokollformat namens „main“ und ein verkürztes Protokollformat namens „notes“, um drei Datenpunkte zu einer Anfrage aufzuzeichnen:

log_format main  '$remote_addr - $remote_user [$time_local] "$request" '
                 '$status $body_bytes_sent "$http_referer" '
                 '"$http_user_agent" "$http_x_forwarded_for"';

log_format notes '$remote_addr "$request" $status';

Das Protokollformat kann auf NGINX-Variablen und andere zum Zeitpunkt der Protokollierung berechnete Werte verweisen.

Anschließend verwenden Sie die Direktive „access_log“, um NGINX anzuweisen, eine Transaktion nach ihrem Abschluss zu protokollieren. Diese Direktive gibt den Speicherort der Protokolldatei und das zu verwendende Protokollformat an:

access_log  /var/log/nginx/access.log main;

Standardmäßig protokolliert NGINX alle Transaktionen mit der folgenden Konfiguration:

access_log logs/access.log combined;

Wenn Sie ein eigenes access_log definieren, überschreibt (ersetzt) es das standardmäßige Zugriffsprotokoll.

Bedingte Protokollierung

Manchmal möchten Sie möglicherweise nur bestimmte Anfragen protokollieren. Dies lässt sich mithilfe der bedingten Protokollierung wie folgt umsetzen:

server {
    listen 80;

    set $logme 0;
    if ( $uri ~ ^/secure ) {
        set $logme 1;
    }

    # Auditors require an additional log for requests to /secure
    access_log  /var/log/nginx/secure.log notes if=$logme;

    # If we have a global access log, we need to re-declare it here
    access_log  /var/log/nginx/access.log main;

    location / {
        # ...
    }
}

Zugriffsprotokolle werden nicht vererbt

Zugriffsprotokolleinstellungen werden weder kumuliert noch vererbt; eine access_log-Direktive in einem Kontext überschreibt (ersetzt) die in übergeordneten Kontexten deklarierten Zugriffsprotokolle.

Wenn Sie beispielsweise zusätzliche Informationen zum Datenverkehr für den URI /secure protokollieren möchten, können Sie ein Zugriffsprotokoll in einem location-Block für /secure {...} definieren. Dieses Zugriffsprotokoll ersetzt das allgemeine Zugriffsprotokoll, das an anderer Stelle in der Konfiguration definiert ist.

Das Beispiel im vorherigen Abschnitt löst dieses Problem. Es verwendet zwei Zugriffsprotokolle im selben Kontext und protokolliert mithilfe der bedingten Protokollierung Anfragen an /secure in einer separaten Protokolldatei.

Herausforderungen bei Zugriffsprotokollen

Angenommen, Sie möchten einige statistische Informationen über den Datenverkehr auf Ihrer Website ermitteln:

  • Wie sieht die typische geografische Verteilung der Nutzer aus?
  • Welche SSL/TLS-Verschlüsselungsverfahren und -Protokolle verwenden meine Nutzer?
  • Wie verteilen sich die Webbrowser?

Das allgemeine Zugriffsprotokoll ist häufig nicht der geeignete Ort, um diese Informationen zu erfassen. Möglicherweise möchten Sie das Zugriffsprotokoll nicht mit den zusätzlichen Feldern überfrachten, die für Ihre Untersuchung erforderlich sind. Zudem wäre der Aufwand für die Protokollierung aller Transaktionen auf einer stark frequentierten Website zu hoch.

In diesem Fall können Sie eine begrenzte Auswahl an Feldern in einem speziellen Protokoll erfassen. Um die Systemlast zu reduzieren, können Sie außerdem eine Teilmenge der Anfragen stichprobenartig erfassen.

Stichprobenverfahren

Stichproben aus 1 % der Anfragen

Die folgende Konfiguration verwendet die Variable „$request_id“ als eindeutige Kennung für jede Anfrage. Sie verwendet einen „split_clients“-Block, um Daten stichprobenartig zu erfassen, indem nur 1 % der Anfragen protokolliert werden:

split_clients $request_id $logme {
    1%     1;
    *      0;
}

server {
    listen 80;

    access_log  /var/log/nginx/secure.log notes if=$logme;

    # ...
}

Stichprobe aus 1 % der eindeutigen Benutzer

Angenommen, wir möchten von jedem Benutzer (oder von 1 % der Benutzer) einen Datenpunkt erfassen, beispielsweise den User-Agent-Header. Wir können nicht einfach eine Stichprobe aus allen Anfragen ziehen, da Benutzer, die sehr viele Anfragen erzeugen, in unseren Daten sonst überrepräsentiert wären.

Wir verwenden einen map-Block, um zu erkennen, ob ein Sitzungs-Cookie vorhanden ist. Daran können wir erkennen, ob eine Anfrage von einem neuen Benutzer oder von einem Benutzer stammt, den wir bereits gesehen haben. Anschließend erfassen wir ausschließlich Stichproben von Anfragen neuer Benutzer:

map $cookie_SESSION $logme {
    "" $perhaps;  # If the cookie is missing, we log if $perhaps
    default   0;
}

split_clients $request_id $perhaps {
    1%        1;  # $perhaps is true 1% of the time
    *         0;
}

server {
    listen 80;

    access_log  /var/log/nginx/secure.log notes if=$logme;
  
    # Optional: If the application does not generate a session cookie, we 
    # generate our own
    add_header Set-Cookie SESSION=1;

    # ...
}

Eindeutige Elemente stichprobenartig erfassen

Allerdings berücksichtigen nicht alle Clients Sitzungscookies. Beispielsweise könnte ein Webcrawler Cookies ignorieren, sodass jede von ihm gesendete Anfrage als Anfrage eines neuen Benutzers erkannt wird, was unsere Ergebnisse verfälscht.

Wäre es nicht großartig, wenn wir Anfragen erfassen könnten, sobald wir zum ersten Mal auf ein neues Element stoßen? Bei diesem Element kann es sich um eine neue IP-Adresse, einen neuen Wert eines Sitzungscookies, einen neuen User-Agent-Header, einen bisher unbekannten Host-Header oder sogar eine Kombination daraus handeln. Auf diese Weise erfassen wir Daten für jedes Element nur einmal.

Natürlich müssen wir den Zustand speichern (eine Liste der bereits erfassten Dinge). Dazu verwenden wir den Schlüssel-Wert-Speicher von NGINX Plus. Der Schlüssel-Wert-Speicher verwaltet eine In-Memory-Schlüssel-Wert-Datenbank, auf die über Variablen in der NGINX Plus-Konfiguration zugegriffen werden kann. Optional unterstützt die Datenbank das automatische Ablaufen von Einträgen (Parameter timeout), persistente Speicherung (state) und Clustersynchronisierung (sync). Für jedes Ding, das noch nicht im Speicher vorhanden ist, protokollieren wir die Anfrage und fügen das Ding dem Speicher hinzu, damit es nicht erneut protokolliert wird.

In NGINX Plus R18 und höher ist es sehr einfach, Schlüssel-Wert-Paare während der Verarbeitung einer Transaktion festzulegen:

# Define a keyval zone with appropriate parameterskeyval_zone zone=clients:80m timeout=3600s;

# Create a variable $seen for each unique $remote_addr
keyval $remote_addr $seen zone=clients;

log_format notes '$remote_addr "$request" $status';

server {
    listen 80;

    # if $seen is empty, update the keyval (set $seen 1;) and log this 
    # request (set $logme 1;)
    # Otherwise, $logme is not set and we don't log the request
    # Note that $seen resets to "" after the configured timeout
    if ($seen = "") {
       set $seen 1;
       set $logme 1;
    }
    access_log  /var/log/nginx/secure.log notes if=$logme;

    location / {
        return 200 "All OK: -$seen-$logme-\n";
    }

    location /api {
        api;
    }
}

Ein praxisnahes Beispiel – Stichprobenartige Erfassung von TLS-Parametern

Dieser Artikel wurde durch ein Problem aus der Praxis angeregt: Wie kann ich TLS gemäß bewährten Verfahren konfigurieren, ohne Nutzer mit älteren Geräten auszuschließen?

Bewährte Verfahren für TLS ändern sich ständig. TLS 1.3 wurde vor einem Jahr ratifiziert, doch viele Clients unterstützen nur ältere TLS-Versionen. Verschlüsselungsverfahren werden als „unsicher“ eingestuft und ausgemustert, ältere Implementierungen sind jedoch weiterhin auf sie angewiesen. ECC-Zertifikate bieten eine höhere Leistung als RSA-Zertifikate, werden aber nicht von allen Clients unterstützt. Viele TLS-Angriffe basieren auf einem „Man-in-the-Middle“, der den Handshake zur Aushandlung des Verschlüsselungsverfahrens abfängt und Client und Server dazu zwingt, ein weniger sicheres Verfahren auszuwählen. Daher ist es wichtig, NGINX Plus so zu konfigurieren, dass schwache oder veraltete Verschlüsselungsverfahren nicht unterstützt werden. Dies kann jedoch dazu führen, dass ältere Clients ausgeschlossen werden.

Im folgenden Konfigurationsbeispiel erfassen wir stichprobenartig jeden TLS-Client und protokollieren dabei das SSL-Protokoll, die Verschlüsselungssuite und den User-Agent-Header. Unter der Annahme, dass jeder Client die neuesten von ihm unterstützten Protokolle und die sichersten von ihm unterstützten Verschlüsselungssuiten auswählt, können wir anschließend die Stichprobendaten auswerten und ermitteln, welcher Anteil der Clients ausgeschlossen wird, wenn wir die Unterstützung für ältere Protokolle und Verschlüsselungssuiten entfernen.

Wir identifizieren jeden Client anhand seiner eindeutigen Kombination aus IP-Adresse und User-Agent, aber die Identifizierung von Clients anhand eines Sitzungscookies oder einer anderen Methode funktioniert ebenso gut.

log_format sslparams '$ssl_protocol $ssl_cipher '
                     '$remote_addr "$http_user_agent"';

# Define a keyval zone with appropriate parameters
keyval_zone zone=clients:80m timeout=3600s;

# Create a variable $seen for each unique combination of $remote_addr and 
# 'User-Agent' header
keyval $remote_addr:$http_user_agent $seen zone=clients;

server {
    listen 443 ssl;

    # default NGINX SSL configuration
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    if ($seen = "") {
       set $seen 1;
       set $logme 1;
    }
    access_log  /tmp/sslparams.log sslparams if=$logme;

    # ...
}

Dadurch wird eine Protokolldatei mit Einträgen wie dem folgenden erstellt:

TLSv1.2 AES128-SHA 1.1.1.1 "Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0"
TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 2.2.2.2 "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1"
TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 3.3.3.3 "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:58.0) Gecko/20100101 Firefox/58.0"
TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 4.4.4.4 "Mozilla/5.0 (Android 4.4.2; Tablet; rv:65.0) Gecko/65.0 Firefox/65.0"
TLSv1 AES128-SHA 5.5.5.5 "Mozilla/5.0 (Android 4.4.2; Tablet; rv:65.0) Gecko/65.0 Firefox/65.0"
TLSv1.2 ECDHE-RSA-CHACHA20-POLY1305 6.6.6.6 "Mozilla/5.0 (Linux; U; Android 5.0.2; en-US; XT1068 Build/LXB22.46-28) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.10.2.1164 Mobile Safari/537.36"

Anschließend können wir die Datei mit verschiedenen Methoden verarbeiten, um die Streuung der Daten zu bestimmen:

$ cat /tmp/sslparams.log | cut -d ' ' -f 2,2 | sort | uniq -c | sort -rn | perl -ane 'printf "%30s %s\n", $F[1], "="x$F[0];'    ECDHE-RSA-AES128-GCM-SHA256 =========================
    ECDHE-RSA-AES256-GCM-SHA384 ========
                     AES128-SHA ====
    ECDHE-RSA-CHACHA20-POLY1305 ==
        ECDHE-RSA-AES256-SHA384 ==

Wir identifizieren die selten verwendeten, weniger sicheren Verschlüsselungsverfahren, prüfen anhand der Protokolle, welche Clients sie verwenden, und treffen anschließend eine fundierte Entscheidung darüber, ob diese Verschlüsselungsverfahren aus der NGINX Plus-Konfiguration entfernt werden sollen.

Fazit

Mit der bedingten Protokollierung von NGINX lässt sich eine Stichprobe aus den von NGINX verarbeiteten Anfragen auswählen und in einem Standardprotokoll oder einem speziellen Protokoll erfassen. Diese Technik ist hilfreich, wenn Sie kurzfristig eine Stichprobe des Datenverkehrs für statistische Analysen benötigen, beispielsweise um die Verteilung von SSL-Parametern zu ermitteln.

Sie sollten sorgfältig überlegen, wie Sie Daten erfassen, damit besonders aktive Benutzer oder Webcrawler nicht überrepräsentiert sind. Sie können Variablen in der NGINX-Konfiguration zusammen mit den Direktiven map und split_clients verwenden, um Anfragen auszuwählen und zu filtern.

Für Situationen, in denen die Entscheidung komplexer ist oder eine hohe Genauigkeit gewährleistet sein soll, können Sie in der NGINX-Konfiguration ausgefeilte Auswahlmechanismen erstellen. Mit dem Schlüssel-Wert-Speicher von NGINX Plus können Sie Zustandsinformationen sammeln und bei Bedarf zwischen NGINX Plus-Instanzen in einem Cluster austauschen.

Testen Sie Request-Sampling mit NGINX Plus selbst – starten Sie noch heute Ihre kostenlose 30-Tage-Testversion oder kontaktieren Sie uns, um Ihre Anwendungsfälle zu besprechen.


Teilen
Schlagwörter: SSL/TLS, F5 NGINX, Tech, 2016

Über den Autor

Owen Garrett
Owen GarrettSenior Director, Produktmanagement

Weitere Blogbeiträge von Owen Garrett

Ä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.