Analyse de logs Apache 🔗

🗓 In projects/ELK/

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:

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.

Métrique: 10 pages les plus visitées

Ce métrique est trÚs simple. Voici la recette pour le constituer:

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.

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.

Barres verticales:

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:

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:

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: