BLOG | NGINX

Demandes d'échantillonnage avec journalisation conditionnelle NGINX

NGINX-Partie-de-F5-horiz-black-type-RGB
Vignette d'Owen Garrett
Owen Garrett
Publié le 24 avril 2019

NGINX peut enregistrer un journal très détaillé de chaque transaction qu'il traite. Ces journaux sont appelés journaux d’accès et vous pouvez affiner les détails enregistrés pour différents services ou emplacements avec un format de fichier journal personnalisable.

Par défaut, NGINX enregistre chaque transaction qu'il traite. Cela peut être nécessaire pour des raisons de conformité ou de sécurité, mais pour un site Web très fréquenté, le volume de données générées peut être écrasant. Dans cet article, nous montrons comment enregistrer de manière sélective des transactions en fonction de divers critères et comment utiliser ces connaissances pour échantillonner des points de données sur les demandes de manière rapide et légère.

Sauf indication contraire, cet article s’applique à la fois à NGINX Open Source et à NGINX Plus. Pour faciliter la lecture, nous ferons référence à NGINX tout au long du document.

Contexte – Présentation rapide de la configuration du journal d’accès NGINX

Les journaux d'accès NGINX sont définis à l'aide de la directive log_format . Vous pouvez définir plusieurs formats de journaux 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 points de données sur une demande :

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 du journal peut référencer des variables NGINX et d’autres valeurs calculées au moment de la journalisation.

Vous utilisez ensuite la directive access_log pour demander à NGINX d’enregistrer une transaction une fois celle-ci terminée. Cette directive spécifie l'emplacement du fichier journal et le format du journal à utiliser :

journal d'accès /var/log/nginx/access.log main;

Par défaut, NGINX enregistre toutes les transactions à l'aide de la configuration suivante :

access_log journaux/access.log combinés ;

Si vous définissez votre propre access_log, il remplace le journal d'accès par défaut.

Journalisation conditionnelle

Parfois, vous souhaiterez peut-être enregistrer uniquement certaines demandes. Cela se fait à l'aide d'une journalisation conditionnelle, comme suit :

server {
listen 80;

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

# Les auditeurs nécessitent un journal supplémentaire pour les requêtes vers /secure
access_log /var/log/nginx/secure.log notes if=$logme;

# Si nous avons un journal d'accès global, nous devons le re-déclarer ici
access_log /var/log/nginx/access.log main;

location / {
# ...
}
}

Les journaux d'accès ne sont pas hérités

Les paramètres du journal d'accès ne s'empilent pas et ne s'héritent pas ; une directive access_log dans un contexte remplace les journaux d'accès déclarés dans les contextes parents.

Par exemple, si vous souhaitez enregistrer des informations supplémentaires sur le trafic vers l'URI /secure , vous pouvez définir un journal d'accès dans un bloc d'emplacement /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 aborde ce problème. Il utilise deux journaux d'accès dans le même contexte, avec une journalisation conditionnelle qui enregistre les requêtes pour /secure dans un fichier journal dédié.

Défis liés aux journaux d'accès

Supposons que vous souhaitiez déterminer certaines informations statistiques sur le trafic vers votre site Web :

  • Quelle est la répartition géographique typique des utilisateurs ?
  • Quels chiffrements et protocoles SSL/TLS mes utilisateurs utilisent-ils ?
  • Quelle est la répartition des navigateurs Web ?

Le journal d’accès général n’est souvent pas un endroit approprié pour enregistrer ces informations. Vous ne souhaiterez peut-être pas polluer 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 à l'enregistrement de toutes les transactions serait trop élevée.

Dans ce cas, vous pouvez enregistrer un ensemble limité de champs dans un journal spécialisé. Pour réduire la charge sur le système, vous souhaiterez peut-être également échantillonner un sous-ensemble de requêtes.

Techniques d'échantillonnage

Échantillonnage de 1% des demandes

La configuration suivante utilise la variable $request_id comme identifiant unique pour chaque demande. Il utilise un bloc split_clients pour échantillonner les données en enregistrant seulement 1 % des requêtes :

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

serveur {
écoute 80;

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

# ...
}

Échantillonnage à partir de 1% d'utilisateurs uniques

Supposons que nous souhaitons échantillonner un point de données de chaque utilisateur (ou de 1 % des utilisateurs), tel que l'en-tête User-Agent . Nous ne pouvons pas simplement échantillonner toutes les demandes, car les utilisateurs qui génèrent un grand nombre de demandes sont alors surreprésentés dans nos données.

Nous utilisons un bloc de carte pour détecter la présence d'un cookie de session, qui nous indique si une demande provient d'un nouvel utilisateur ou d'un utilisateur que nous avons déjà vu. Nous échantillonnons ensuite les requêtes provenant uniquement des nouveaux utilisateurs :

map $cookie_SESSION $logme {
"" $perhaps; # Si le cookie est manquant, nous enregistrons if $perhaps
default 0;
}

split_clients $request_id $perhaps {
1% 1; # $perhaps est vrai 1% du temps
* 0;
}

server {
listen 80;

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

# Facultatif : Si l'application ne génère pas de cookie de session, nous 
# générons notre propre
add_header Set-Cookie SESSION=1;

# ...
}

Échantillonnage de choses uniques

Cependant, tous les clients ne respectent pas les cookies de session. Par exemple, un robot d’exploration Web peut ignorer les cookies, de sorte que chaque requête qu’il émet est identifiée comme provenant d’un nouvel utilisateur, ce qui fausse nos résultats.

Ne serait-ce pas génial si nous pouvions échantillonner les demandes lorsque c'est la première fois que nous voyons une nouvelle chose ? L' élément peut être une nouvelle adresse IP, une nouvelle valeur de cookie de session, un nouvel en-tête d'agent utilisateur , un en-tête d'hôte jamais vu auparavant , ou même une combinaison de ceux-ci. De cette façon, nous échantillonnons les données pour chaque chose une seule fois.

De toute évidence, nous devons stocker l’état (une liste des éléments que nous avons vus), et pour cela nous nous tournons vers le magasin clé-valeur de NGINX Plus. Le magasin clé-valeur conserve une base de données clé-valeur en mémoire accessible à partir de la configuration NGINX Plus à l'aide de variables ; la base de données prend éventuellement en charge l'expiration automatique des entrées (paramètre de délai d'expiration ), le stockage persistant ( état ) et la synchronisation du cluster ( sync ). Pour chaque chose qui n'est pas déjà dans le magasin, nous enregistrons la demande et ajoutons l' objet au magasin afin qu'il ne soit pas enregistré à nouveau.

Dans NGINX Plus R18 et versions ultérieures, il est très facile de définir des paires clé-valeur lors du traitement d'une transaction :

# Définir une zone keyval avec les paramètres appropriéskeyval_zone zone=clients:80m timeout=3600s;

# Créer une variable $seen pour chaque $remote_addr unique
keyval $remote_addr $seen zone=clients;

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

server {
listen 80;

# si $seen est vide, mettre à jour le keyval (set $seen 1;) et enregistrer ceci 
# request (set $logme 1;)
# Sinon, $logme n'est pas défini et nous n'enregistrons pas la requête
# Notez que $seen se réinitialise à "" après le délai d'attente configuré
if ($seen = "") {
set $seen 1;
set $logme 1;
}
access_log /var/log/nginx/secure.log notes if=$logme;

emplacement / {
retour 200 "Tout est OK : -$seen-$logme-\n" ;
}

emplacement /api {
api ;
}
}

Un exemple concret : échantillonnage des paramètres TLS

Cet article a été inspiré par un problème réel : comment puis-je configurer TLS conformément aux bonnes pratiques sans exclure les utilisateurs disposant d’appareils hérités ?

Les meilleures pratiques TLS sont une cible mouvante. TLS 1.3 a été ratifié il y a un an, mais de nombreux clients ne parlent que des versions précédentes de TLS ; les chiffrements sont déclarés « non sécurisés » et retirés, mais les implémentations plus anciennes s'appuient sur eux ; les certificats ECC offrent de meilleures performances que RSA, mais tous les clients ne peuvent pas accepter ECC. De nombreuses attaques TLS reposent sur un « homme du milieu » qui intercepte la négociation du chiffrement et force le client et le serveur à sélectionner un chiffrement moins sécurisé. Par conséquent, il est important de configurer NGINX Plus pour ne pas prendre en charge les chiffrements faibles ou hérités, mais cela pourrait exclure les clients hérités.

Dans l'exemple de configuration suivant, nous échantillonnons chaque client TLS, en enregistrant le protocole SSL, le chiffrement et l'en-tête User-Agent . En supposant que chaque client sélectionne les protocoles les plus récents et les chiffrements les plus sécurisés qu’il prend en charge, nous pouvons ensuite évaluer les données échantillonnées et déterminer quelle proportion de clients est exclue si nous supprimons la prise en charge des protocoles et chiffrements plus anciens.

Nous identifions chaque client par sa combinaison unique d'adresse IP et d'agent utilisateur , mais l'identification des clients par cookie de session ou une autre méthode fonctionne tout aussi bien.

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

# Définir une zone keyval avec les paramètres appropriés
keyval_zone zone=clients:80m timeout=3600s;

# Créer une variable $seen pour chaque combinaison unique de $remote_addr et 
# En-tête 'User-Agent'
keyval $remote_addr:$http_user_agent $seen zone=clients;

serveur {
écouter 443 ssl;

# configuration SSL NGINX par défaut
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

si ($seen = "") {
définir $seen 1;
définir $logme 1 ;
}
access_log /tmp/sslparams.log sslparams si = $logme ;

# ...
}

Cela génère un fichier journal avec des entrées comme les suivantes :

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 comme Mac OS X) AppleWebKit/601.1.46 (KHTML, comme 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 ; tablette ; version : 65.0) Gecko/65.0 Firefox/65.0"
TLSv1 AES128-SHA 5.5 .5.5 "Mozilla/5.0 (Android 4.4.2 ; Tablette ; 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; fr-CA; XT1068 Build/LXB22.46-28) AppleWebKit/537.36 (KHTML, comme Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.10.2.1164 Mobile Safari/537.36"

Nous pouvons ensuite traiter le fichier en utilisant diverses méthodes pour déterminer la répartition 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 chiffrements à faible volume et moins sécurisés, vérifions les journaux pour déterminer quels clients les utilisent, puis prenons une décision éclairée concernant la suppression des chiffrements de la configuration NGINX Plus.

Conclusion

La journalisation conditionnelle de NGINX peut être utilisée pour échantillonner un sous-ensemble des requêtes gérées par NGINX et écrire un journal standard ou à usage spécial. Cette technique est utile si vous avez besoin de prélever un échantillon rapide de trafic pour une analyse statistique, par exemple pour déterminer la propagation des paramètres SSL.

Vous devez réfléchir à la manière dont vous échantillonnez les données afin que les utilisateurs occupés ou les robots ne soient pas surreprésentés. Vous pouvez utiliser des variables dans la configuration NGINX, ainsi que les directives map et split_clients , pour sélectionner et filtrer les requêtes.

Pour les situations où la décision est plus complexe ou lorsqu'une grande confiance en la précision est souhaitée, vous pouvez créer des sélecteurs sophistiqués dans la configuration NGINX. Le magasin clé-valeur NGINX Plus vous permet d'accumuler l'état et de le partager entre les instances NGINX Plus d'un cluster si nécessaire.

Essayez vous-même la demande d'échantillonnage avec NGINX Plus – démarrez votre essai gratuit de 30 jours dès aujourd'hui ou contactez-nous pour discuter de vos cas d'utilisation.


« Cet article de blog peut faire référence à des produits qui ne sont plus disponibles et/ou qui ne sont plus pris en charge. Pour obtenir les informations les plus récentes sur les produits et solutions F5 NGINX disponibles, explorez notre famille de produits NGINX . NGINX fait désormais partie de F5. Tous les liens NGINX.com précédents redirigeront vers un contenu NGINX similaire sur F5.com."