NGINX peut consigner de manière très détaillée chaque transaction qu’il traite. Ces journaux sont appelés « journaux d’accès », et vous pouvez ajuster précisément le niveau de détail enregistré pour différents services ou emplacements grâce à un format de fichier journal personnalisable.
Par défaut, NGINX consigne chaque transaction qu’il traite. Cela peut être nécessaire à des fins de conformité ou de sécurité, mais pour un site web très fréquenté, le volume de données généré peut devenir considérable. Dans cet article, nous expliquons comment consigner sélectivement les transactions selon différents critères et comment mettre ces connaissances à profit pour échantillonner rapidement et efficacement des données sur les requêtes.
Sauf indication contraire, cet article s’applique à la fois à NGINX Open Source et à NGINX Plus. Pour faciliter la lecture, nous utiliserons le terme « NGINX » dans la suite de l’article.
Contexte – Présentation rapide de la configuration des journaux d’accès de NGINX
Les journaux d’accès NGINX sont définis à l’aide de la directive « log_format ». Vous pouvez définir plusieurs formats de journal nommés différents ; par exemple, un format de journal complet nommé « main » et un format de journal abrégé nommé « notes » pour enregistrer trois informations sur une requête :
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';Le format de journalisation peut faire référence à des variables NGINX et à d’autres valeurs calculées au moment de la journalisation.
Vous utilisez ensuite la directive « access_log » pour demander à NGINX de consigner une transaction une fois celle-ci terminée. Cette directive indique l’emplacement du fichier journal et le format de journalisation à utiliser :
access_log /var/log/nginx/access.log main;Par défaut, NGINX journalise toutes les transactions à l’aide de la configuration suivante :
access_log logs/access.log combined;Si vous définissez votre propre access_log, celui-ci remplace le journal d’accès par défaut.
Journalisation conditionnelle
Il peut arriver que vous souhaitiez ne journaliser que certaines requêtes. Pour ce faire, utilisez la journalisation conditionnelle comme suit :
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 / {
# ...
}
}Les journaux d’accès ne sont pas hérités
Les paramètres des journaux d’accès ne se cumulent pas et ne sont pas hérités ; une directive « access_log » définie dans un contexte remplace les journaux d’accès déclarés dans les contextes parents.
Par exemple, si vous souhaitez consigner des informations supplémentaires sur le trafic vers l’URI /secure, vous pouvez définir un journal d’accès dans un bloc location /secure {...}. Ce journal d’accès remplace le journal d’accès général défini ailleurs dans la configuration.
L’exemple de la section précédente résout ce problème. Il utilise deux journaux d’accès dans le même contexte, avec une journalisation conditionnelle qui consigne les requêtes adressées à /secure dans un fichier journal dédié.
Difficultés liées aux journaux d’accès
Supposons que vous souhaitiez obtenir des statistiques sur le trafic de votre site web :
- Quelle est la répartition géographique habituelle des utilisateurs ?
- Quels chiffrements et protocoles SSL/TLS mes utilisateurs emploient-ils ?
- Quelle est la répartition des navigateurs web ?
Le journal d’accès général n’est souvent pas l’endroit approprié pour consigner ces informations. Vous ne souhaitez peut-être pas encombrer le journal d’accès avec les champs supplémentaires nécessaires à votre étude et, sur un site très fréquenté, la surcharge liée à la journalisation de toutes les transactions serait trop importante.
Dans ce cas, vous pouvez consigner un nombre limité de champs dans un journal dédié. Pour réduire la charge du système, vous pouvez également choisir de n’échantillonner qu’un sous-ensemble des requêtes.
Techniques d’échantillonnage
Échantillonnage de 1 % des requêtes
La configuration suivante utilise la variable « $request_id » comme identifiant unique pour chaque requête. Elle utilise un bloc « split_clients » pour échantillonner les données en ne journalisant que 1 % des requêtes :
split_clients $request_id $logme {
1% 1;
* 0;
}
server {
listen 80;
access_log /var/log/nginx/secure.log notes if=$logme;
# ...
}Échantillonnage de 1 % des utilisateurs uniques
Supposons que nous souhaitions échantillonner un point de données pour chaque utilisateur (ou pour 1 % des utilisateurs), comme l’en-tête User-Agent. Nous ne pouvons pas simplement effectuer un échantillonnage sur l’ensemble des requêtes, car les utilisateurs qui en génèrent un grand nombre seraient alors surreprésentés dans nos données.
Nous utilisons un bloc « map » pour détecter la présence d’un cookie de session, ce qui nous permet de savoir si une requête provient d’un nouvel utilisateur ou d’un utilisateur déjà rencontré. Nous échantillonnons ensuite uniquement les requêtes provenant de nouveaux utilisateurs :
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;
# ...
}Échantillonnage d’éléments uniques
Cependant, tous les clients ne prennent pas en charge les cookies de session. Par exemple, un robot d’indexation peut ignorer les cookies, si bien que chacune de ses requêtes est considérée comme provenant d’un nouvel utilisateur, ce qui fausse nos résultats.
Ne serait-il pas formidable de pouvoir échantillonner les requêtes lorsque nous rencontrons un nouvel élément pour la première fois ? Cet élément peut être une nouvelle adresse IP, une nouvelle valeur de cookie de session, un nouvel en-tête User-Agent, un en-tête d’hôte encore jamais rencontré, voire une combinaison de ces éléments. Ainsi, nous n’échantillonnons les données qu’une seule fois pour chaque élément.
De toute évidence, nous devons conserver un état (une liste des « éléments » déjà rencontrés) ; pour cela, nous utilisons le magasin clé-valeur de NGINX Plus. Ce magasin gère une base de données clé-valeur en mémoire, accessible depuis la configuration de NGINX Plus au moyen de variables. La base de données peut également prendre en charge l’expiration automatique des entrées (paramètre timeout), le stockage persistant (state) et la synchronisation au sein d’un cluster (sync). Pour chaque « élément » qui ne figure pas encore dans le magasin, nous journalisons la requête et ajoutons cet « élément » au magasin afin qu’il ne soit pas journalisé à nouveau.
Dans NGINX Plus R18 et les versions ultérieures, il est très facile de définir des paires clé-valeur lors du traitement d’une transaction:
# 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;
}
}Un exemple concret – Échantillonnage des paramètres TLS
Cet article est né d’un problème concret : comment configurer TLS conformément aux bonnes pratiques sans exclure les utilisateurs d’appareils anciens ?
Les bonnes pratiques en matière de TLS évoluent constamment. TLS 1.3 a été ratifié il y a un an, mais de nombreux clients ne prennent en charge que les versions antérieures de TLS ; certains algorithmes de chiffrement sont déclarés « non sécurisés » et abandonnés, alors que des implémentations plus anciennes en dépendent ; les certificats ECC offrent de meilleures performances que les certificats RSA, mais tous les clients ne les prennent pas en charge. De nombreuses attaques TLS reposent sur un « homme du milieu » qui intercepte la négociation des algorithmes de chiffrement et contraint le client et le serveur à en sélectionner un moins sécurisé. Il est donc important de configurer NGINX Plus afin qu’il ne prenne pas en charge les algorithmes de chiffrement faibles ou obsolètes, même si cela risque d’exclure les clients plus anciens.
Dans l’exemple de configuration suivant, nous échantillonnons chaque client TLS en consignant le protocole SSL, la suite de chiffrement et l’en-tête User-Agent utilisé. En supposant que chaque client sélectionne les protocoles les plus récents et les suites de chiffrement les plus sûres qu’il prend en charge, nous pouvons ensuite analyser les données échantillonnées et déterminer quelle proportion de clients serait exclue si nous cessions de prendre en charge les anciens protocoles et suites de chiffrement.
Nous identifions chaque client par la combinaison unique de son adresse IP et de son User-Agent, mais il est tout aussi possible d’identifier les clients à l’aide d’un cookie de session ou d’une autre méthode.
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;
# ...
}Cela génère un fichier journal contenant des entrées comme celle-ci :
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"Nous pouvons ensuite traiter le fichier à l’aide de différentes méthodes afin de déterminer la dispersion des données :
$ 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 ==Nous identifions les suites de chiffrement peu utilisées et moins sécurisées, consultons les journaux pour déterminer quels clients les utilisent, puis décidons en connaissance de cause de les supprimer ou non de la configuration de NGINX Plus.
Conclusion
La journalisation conditionnelle de NGINX permet d’échantillonner un sous-ensemble des requêtes gérées par NGINX et de les consigner dans un journal standard ou dédié à un usage particulier. Cette technique est utile pour prélever rapidement un échantillon du trafic à des fins d’analyse statistique, par exemple pour déterminer la répartition des paramètres SSL.
Vous devez réfléchir à la manière dont vous échantillonnez les données afin que les utilisateurs très actifs ou les robots d’indexation ne soient pas surreprésentés. Vous pouvez utiliser des variables dans la configuration de NGINX, ainsi que les directives « map » et « split_clients », pour sélectionner et filtrer les requêtes.
Dans les situations où la décision est plus complexe, ou lorsqu’un haut degré de fiabilité est requis, vous pouvez créer des sélecteurs sophistiqués dans la configuration NGINX. Le magasin clé-valeur de NGINX Plus vous permet d’accumuler des données d’état et de les partager, si nécessaire, entre les instances NGINX Plus d’un cluster.
Testez vous-même l’échantillonnage des requêtes avec NGINX Plus : commencez votre essai gratuit de 30 jours dès aujourd’hui ou contactez-nous pour discuter de vos cas d’utilisation.
À propos de l’auteur

Articles de blog associés

Tendances actuelles des technologies cloud natives, de l’ingénierie de plateforme et de l’IA
Découvrez les principaux enseignements tirés des réponses à l’enquête annuelle F5 NGINX sur les dernières tendances en matière de technologies cloud natives, de sécurité, d’ingénierie de plateforme et d’IA.
Quels contrôleurs Ingress NGINX sont affectés par les vulnérabilités CVE-2022-4886, CVE-2023-5043 et CVE-2023-5044 ?
Lorsque de nouvelles CVE sont signalées, il est facile de ne plus savoir si elles concernent un outil de contrôleur Ingress NGINX particulier, car il existe plusieurs projets. Cet article présente les CVE et explique comment distinguer les différents projets de contrôleurs Ingress afin de dissiper toute confusion.
Votre API est-elle en temps réel ? Testez sa latence avec l’outil rtapi de NGINX
Votre API répond-elle aux requêtes en moins de 30 ms ? Si ce n’est pas le cas, il ne s’agit pas d’une API en temps réel offrant la faible latence exigée par la grande majorité des organisations. Mesurez la latence de votre API avec l’outil rtapi de NGINX et découvrez comment NGINX peut l’aider à offrir une réactivité en temps réel.

Un testeur d’expressions régulières pour NGINX et NGINX Plus
Le testeur d’expressions régulières (regex) pour NGINX et NGINX Plus vous évite de tâtonner en vous indiquant si une expression régulière utilisée dans un bloc location ou map correspond aux valeurs comme prévu. Il génère la configuration appropriée et affiche les valeurs des groupes de capture.
Caches partagés avec les clusters de cache NGINX Plus, partie 1
Optimisez la capacité de votre cache web en le répartissant entre plusieurs serveurs de cache web NGINX ou NGINX Plus. Découvrez comment procéder dans cet article.
Introduction aux microservices
Les microservices suscitent actuellement beaucoup d’intérêt. Cet article de blog est le premier d’une série de sept consacrée à la conception, au développement et au déploiement de microservices.