Analyse de logs Apacheđ
Introduction
Cette partie a pour objet de trouver le moyen d'obtenir le plus d'informations possible des logs issus d'Apache. Ce serveur HTTP est assez verbeux lorsqu'il est bien configuré et on peut obtenir un maximum d'informations sur les clients qui accÚdent à ses services.
Contenu des logs
Avant de commencer, étudtions un peu ce qu'on peut récupérer dans un log Apache:
188.165.15.236 - - [06/Sep/2015:06:59:32 +0200] "GET /kb/GIS/generate_osm/ HTTP/1.1" 200 7314 "-" "Mozilla/5.0 (compatible; AhrefsBot/5.0; +http://ahrefs.com/robot/)"
Voici ce qu'on peut récupérer dans l'ordre d'apparition:
- L'adresse IP qui peut ĂȘtre du type IPv4 ou IPv6.
- L'identifiant de l'utilisateur si on travaille en mode authentifié (sinon "-").
- Le type d'authentification de l'utilisateur
- La date de connexion (avec le timestamp complet). On s'en servira pour donner le timestamp de l'Ă©vĂšnement logstash.
- La requĂȘte HTTP qui peut ĂȘtre dĂ©composĂ©e en trois Ă©lĂ©ments:
- La commande HTTP (GET/POST/PUT/DELETE).
- La ressource (la page qui a été demandée).
- Le type de protocole et sa version.
- Le code de retour du serveur HTTP.
- Le nombre d'octets envoyés.
- Le champ qui suit est le referrer, c'est Ă dire de quelle page on provient.
- Enfin, on trouve le code du User-Agent du client HTTP.
Au final, cela fait beaucoup de champs différents à gérer, sachant qu'on peut les combiner ensemble pour tenter de répondre aux questions que peuvent se poser tous les webmasters. Essayons de nous limiter aux plus pertinentes (c'est à dire celles qui m'intéressent !).
MĂ©triques
Logstash vient avec un filtre grok prĂȘt Ă l'emploi. Ce dernier se nomme %{COMBINEDAPACHELOG}
et permet d'extraire tous les champs du log Apache. Mais dans notre cas, il nous faut Ă©galement ajouter d'autres donnĂ©es. En effet, si vous avez plusieurs virtualhost, Apache n'indique pas dans les logs le domaine virtuel qui a Ă©tĂ© requĂȘtĂ©. Cette information peut ĂȘtre obtenue Ă partir du nom du fichier, si vous suivez la rĂšgle de log prĂ©sentĂ©e ci-dessus dans votre configuration Apache.
Autre information qu'on peut Ă©galement extraire du nom des fichiers de log, c'est la nature de la connexion: HTTP ou HTTPS.
Enfin, il serait bon de savoir si on a une adresse IPv4 ou IPv6. De plus, depuis l'adresse IP, on peut déduire à peu prÚs la provenance du client.
Bien entendu, pour respecter la rÚgle des documents logstash, nous allons utiliser le type "apache" pour distinguer ces données des autres systÚmes.
Fichier de configuration de logstash
Voici le filtre pour logstash qui correspond à la configuration cible exposée ci-dessus.
file { path => "/home/medspx/projects/ELK/data/apache2/access_*.log" start_position => beginning type => "apache" } filter { # Filtre pour les logs d'Apache if [type] == "apache" { # L'intégralité des données du log est récupérée via le motif déjà existant grok { match => { "message" => "%{COMBINEDAPACHELOG}" } } # On récupÚre la date de l'évÚnement comme champ interne de Logstash date { match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ] } # On va traiter l'adresse IP du client via geoip pour avoir plus d'information sur la provenance. geoip { source => "clientip" } # On va récupérer le domaine virtuel et le type de connexion depuis le nom du fichier grok { match => { "path" => "access_%{WORD:virtualhost}_%{WORD:tls}" } } } }
Nourrir Logstash
L'idée est assez simple. Elle consiste à préparer une concaténation de fichiers de logs Apache par domaine virtuel et par type de connexion (tls ou plain). Vous pouvez bien entendu mettre quelquechose de plus complexe en oeuvre si vous voulez avoir du quasi temps-réel. Mais ce n'est pas ce que je cherche à faire. Tout au plus, je veux pouvoir allumer ma station de travail, lancer la pile ELK et une fois que c'est fait, rapatrier les logs qui m'intéressent pour les injecter dans Logstash pour pouvoir faire une analyse via Kibana.
Attention, Logstash et Elasticsearch sont quand mĂȘme des gros bloats en Java (on ne se refait pas !): ils sont particuliĂšrement peu efficaces sur du matĂ©riel conventionnel, hors datacenter. Par exemple, le chargement de 73 Mo de logs Apache (1 an des logs de mon serveur auto-hĂ©bergĂ© prend prĂšs de 40 minutes Ă logstash pour ĂȘtre digĂ©rĂ© complĂštement. Ensuite, Elasticsearch harmonise ses index et le processus prend 10 minutes supplĂ©mentairesâŠ
Une fois les donnĂ©es chargĂ©es, le noeud Elasticsearch lors de son lancement initial prend environ 1 minute pour dĂ©marrer mais il charge progressivement la RAM qui lui a Ă©tĂ© affectĂ©e pendant prĂšs de 10 minutes sur cette mĂȘme machineâŠ
Reporting
Introduction
Grùce au filtre logstash précédent, nous disposons de trÚs nombreuses variables. Créer l'ensemble des rapports possibles prendrait sans doute un livre entier aussi, j'ai décidé de limiter le reporting aux éléments qui suivent. Retenez bien que Kibana travaille sur un secteur de temps donné et que donc toutes ces mesures sont dépendantes d'une période de temps.
- Pourcentage d'adresse IPv6/IPv4 par rapport au nombre de connexions sur une période donnée.
- Quelles sont les 10 pages les plus visitées ?
- Classement des virtualhosts par ordre de fréquentation.
- Quelles sont les 50 pages html les plus lourdes ?
- Ratio connexions HTTPS/HTTP/nombre de connexions ?
- Classement par pays de l'ensemble des requĂȘtes ?
- Quelles sont les adresses IP qui accĂšdent aux services non publics ?
- Proportion du traffic requĂȘtĂ© par des robots/crawlers/lecteurs RSS ?
- Fréquentation en nombre de hits des pages publiques ?
- Quelles sont les requĂȘtes qui aboutissent Ă des codes d'erreur ?
Métrique: 10 pages les plus visitées
Ce métrique est trÚs simple. Voici la recette pour le constituer:
- Filtre: *
- Visualize: New Pie Chart.
- Buckets: split slices.
- Aggregation: Terms
- Field: request.raw
- Order:Top, Size: 10.
- Order by: metric: Count
Métrique: Classement des virtualdomains par ordre de fréquentation
Autre métrique assez simple qui travaille sur le champ virtualdomain que nous avons défini dans le filtre Logstash.
- Filtre: *
- Visualize: New Pie Chart.
- Buckets: split slices.
- Aggregation: Terms
- Field: virtualdomain.raw
- Order:Top, Size: 10.
- Order by: metric: Count
MĂ©trique: Ratio connexions HTTPS/HTTP/nombre de connexions
Pour cette mesure, nous allons utiliser deux diagrammes. Le premier est un camembert qui montre le nombre de requĂȘtes TLS vs clair. Ensuite, nous allons utiliser des barres verticales par virtualdomain que nous allons subdiviser en fonction du critĂšre TLS.
L'attribut utilisé est bien sûr l'attribut tls.raw que nous avons configuré dans notre filtre Logstash.
- Filtre: *
- Visualize: New Pie Chart.
- Buckets: split slices.
- Aggregation: Terms
- Field: tls.raw
- Order:Top, Size: 2.
- Order by: metric: Count
Barres verticales:
- Filtre: *
- Visualize: Vertical Bar Chart.
- Buckets: X-Axis.
- Aggregation: Terms
- Field: virtualdomain.raw
- Order:Top, Size: 10.
- Order by: metric: Count
- Split bars
- Aggregation: Terms
- Field: tls.raw
- Order:Top, Size: 2.
- Order by: metric: Count
MĂ©trique: Proportion du traffic requĂȘtĂ© par des robots/crawlers/lecteurs RSS
Ici, nous allons utiliser des filtres⊠La syntaxe de ces filtres est assez simple, comme nous allons pouvoir nous en rendre compte.
Nous voulons extraire les connexions issues des lecteurs RSS et des crawlers. On peut repĂ©rer ces connexions grĂące au champ agent. En rĂšgle gĂ©nĂ©rale, un lecteur RSS contient au moins la chaĂźne de caractĂšres 'rss' dans son champ agent. Pour les crawlers, on peut dire la mĂȘme chose avec la chaĂźne 'bot'. Donc, nous voulons les agents contenant 'bot' d'un cĂŽtĂ©, les agents contenant 'rss' de l'autre et aussi toutes les connexions autres.
Filtrer sur 'bot' sur le champ agent donne lieu Ă la syntaxe suivante: agent:*bot*
. Pour les lecteurs RSS, on trouve bien entendu: agent:*rss*
. Pour tout le reste, il suffit de prendre l'inverse. Cela donne la syntaxe suivante: NOT agent:*bot* AND NOT agent:*rss*
!
Pour créer le métrique, nous allons employer un camembert avec trois catégories: bot, rss et le reste. Pour cela, voici la recette:
- Filtre: *
- Visualize: New Pie Chart.
- Buckets: split slices.
- Aggregation: Filters
- Filtre 1: agent:bot
- Filtre 2: agent:rss
- Filtre 3: NOT agent:bot AND NOT agent:rss
Et voilĂ ! Rien de bien complexeâŠ
Métrique: Fréquentation en nombre de hits des pages publiques
Sur mon serveur, j'ai tout un tas de services non publics. Si je veux savoir qui regarde mes pages publiques, il faut que j'enlÚve les pages privées.
Voici une syntaxe de filtre simple (quoiqu'un peu longue):
NOT (request:news OR request:posteweb OR request:admin OR request:*css* OR request:favicon.ico OR request:*index.rss* OR request:robots.txt OR request:\/tag\/* OR request:*calendar*)
Voici la recette Ă appliquer:
- Filtre: NOT (request:news OR request:posteweb OR request:admin OR request:css OR request:favicon.ico OR request:index.rss OR request:robots.txt OR request:\/tag\/* OR request:calendar)
- Visualize: Vertical Bar Chart.
- Buckets: X-Axis.
- Aggregation: Terms
- Field: request.raw
- Order:Top, Size: 10.
- Order by: metric: Count
MĂ©trique: Quelles sont les requĂȘtes qui aboutissent Ă des codes d'erreur ?
Ici aussi, nous allons utiliser des filtres. Tout ce qu'on veut, c'est rĂ©cupĂ©rer les requĂȘtes en erreur, dont le code HTTP n'est pas 200.
Voici la recette:
- Filtre: NOT response:200
- Visualize: Vertical Bar Chart.
- Buckets: X-Axis.
- Aggregation: Terms
- Field: request.raw
- Order:Top, Size: 15.
- Order by: metric: Count
- Split bars
- Aggregation: Significant Terms
- Field: response.raw
- Size: 10.