Introduction

Cette partie a pour objet de trouver le moyen d'obtenir le plus d'informations possible des logs issus d'Exim4. Ce MTA rend compte des emails qu'il a envoyé et des tentatives de connexions. Faisons quelques stats avec ça !

Contenu des logs

Avant de commencer, étudions un peu ce qu'on peut récupérer dans un log Exim4. Par défaut, Exim4 propose deux fichiers de log différents. Le premier nommé mainlog est le log principal qui trace toutes les activités de réception, de connexion au serveur et d'envoi de courriel. Le second fichier nommé rejectlog permet de tracer toutes les connexions qui ont échouées.

Mainlog

Voici un exemple de ligne de ce fichier de log:

[[!format Erreur: Format de page non reconnu log]]

On peut d'abord lire une tentative d'envoi de mail vers sympa@linuxfr.org. On voit ensuite que toutes les lignes qui indiquent un début ou la fin d'un traitement de file de message (Start queue run) ne nous intéressent pas. On peut donc les écarter. Ensuite, on voit un mail qui arrive: celui qui vient de qgis-developer-bounces@lists.osgeo.org. C'est cette information qui nous intéresse.

Filtre logstash

Logstash vient sans filtre tout prêt. Comme d'habitude, Exim4 a un format de date bien particulier. Il faudra le capturer en plus de l'adresse IP. Voici le format de date à ajouter dans votre fichier de patterns (patterns/extra):

EXIMT %{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{TIME}

Si on analyse rapidement le fichier de log précédent, on voit d'abord qu'on peut supprimer tous les messages Start queue run et End queue run. Logstash dispose d'un filtre pour cela qui s'appelle drop. Il n'a pas d'argument.

Ensuite, nous voulons récupérer les courriels entrants. Ces derniers ont un motif du genre:

Exim-Queue-id adresse_email H=machine_d_envoi (domaine) [adresse IP]...

Parfois ce motif change, notamment lors de la connexion depuis localhost ou avec un compte identifié. Mais dans la majorité des cas, ce motif permet de récupérer les tentatives d'envoi de mail vers le domaine local et c'est ce qui nous intéresse.

Pour les courriels sortants, le motif pourrait être le suivant:

Exim-Queue-id adresse_email R=dnslookup T=remote H=machine_contactée [adresse IP]...

L'ensemble de ma tentative de filtrage ressemble à ce qui suit (j'ai essayé de gérer les cas assez nombreux chez moi où l'envoi ou la réception se fait depuis un compte local de la machine (localhost)):

  file {
      path => "/home/medspx/projects/ELK/test/datasource/exim4/mainlog.log"
      start_position => beginning
      type => "exim"
  }

}

# Les filtres
filter {
  # Filtre pour les messages entrants et sortants
  if [type] == "exim" {
    if [message] !~ /\<=/ and [message] !~ /=\>/ {
      drop { }
    }
    if [message] =~ /\<=/ {
      if [message] =~ /H=/ {
        grok {
          patterns_dir => "./patterns"
          match => { "message" => "%{EXIMT:timestamp} [a-zA-Z0-9\-]{16} \<= (?<from_sender>.+) H=(\(%{HOST:from_host}\)|%{HOST:from_host}).*\[%{IP:from_ip}\]" }
        }
      }
      else {
        grok {
          patterns_dir => "./patterns"
          match => { "message" => "%{EXIMT:timestamp} [a-zA-Z0-9\-]{16} \<= (?<from_sender>.+) U=%{HOST:from_local_user}" }
        }
      }
    }
    if [message] =~ /=\>/ {
      if [message] =~ /H=/ {
        grok {
          patterns_dir => "./patterns"
          match => { "message" => "%{EXIMT:timestamp} [a-zA-Z0-9\-]{16} => (?<to_email>.+) R=%{NOTSPACE:r_action} T=%{NOTSPACE:t_action} H=%{HOST:to_host} \[%{IP:to_ip}\]" }
        }
      }
      else {
        grok {
          patterns_dir => "./patterns"
          match => { "message" => "%{EXIMT:timestamp} [a-zA-Z0-9\-]{16} => (?<to_email>.+) R=%{NOTSPACE:r_action} T=%{NOTSPACE:t_action}" }
        }
      }
    }
    date {
      match => [ "timestamp", "YYYY-MM-dd HH:mm:ss" ]
    }
    geoip { source => "rejectip" }
  }
...

rejectlog

Voici un exemple de ligne de ce fichier de log:

[[!format Erreur: Format de page non reconnu log]]

On voit qu'ici, tout est plus complexe: l'élément commun est la date et l'heure de l'évènement mais en dehors de ça, les messages d'erreur ne suivent pas un formalisme particulier. Le seul motif qui se dégage est l'adresse IP du client qui est sous la forme [IP].

Nous allons donc faire au plus simple et capturer large.

Filtre Logstash

Ensuite, voic le filtre logstash:

file {
  path => "/home/medspx/projects/ELK/data/exim4/rejectlog*.log"
  start_position => beginning
  type => "exim_reject"
}

filter {
  # Filtre pour les logs de rejet Exim
  if [type] == "exim_reject" {
    # L'intégralité des données du log est récupérée via le motif étudié plus haut

# Les filtres
filter {
  if [type] == "exim_reject" {
    grok {
        patterns_dir => "./patterns"
        match => { "message" => "%{EXIMT:timestamp} [^\[\]]*\[%{IP:rejectip}\]" }
    }
    date {
      match => [ "timestamp", "YYYY-MM-dd HH:mm:ss" ]
    }
    geoip { source => "rejectip" }  
  }
}

Reporting

Introduction

Grâce aux filtres logstash précédents, nous disposons de quelques variables que nous allons exploiter. Voici ce que j'ai imaginé:

  • Nombre de mails entrants et sortants sur une période donnée avec distinction du local/externe.
  • Histogramme de mails entrants et sortant sur une période donnée.
  • Quels sont les 10 domaines externes qui sont les plus sollicités.
  • Quels sont les 10 domains externes qui envoient le plus de mails vers nos domaines.
  • Histogramme des rejets selon le temps.
  • Liste des IP rejettées.
  • Quels sont les grandes classes d'erreur avec leur répartition ?

Comme d'habitude, n'oubliez pas de rafraîchir vos champs (Settings -> Logstash-* -> bouton Refresh).

Métrique: Nombre de mails entrants et sortants sur une période donnée avec distinction du local/externe.

Cette fois, nous allons utiliser des barres verticales avec des filtres. Les informations de discrimination ne sont malheureusement pas compatibles entre elles. En effet, les champs ne sont pas identiques entre les mails envoyés et les mails reçus. Difficile de distinguer ce qui vient du local/externe au sein d'une même classe. Pour contourner le problème, nous allons créer une barre par traffic à identifier en regroupant l'envoi par rapport à la récéption.

  • Filtre: type:exim
  • Visualize: New Vertical Bar Chart.
  • X-Axis Buckets: split rows.
  • Aggregation: Filters
  • Filtre 1: from_sender:*
  • Filtre 2: from_sender:* AND from_local_user:*
  • Filtre 3: to_email:* AND NOT r_action:local_user

Métrique: Histogramme des mails entrants et sortants sur une période donnée

Ce diagramme est le même que précédemment mais il est fonction du temps. Nous allons utiliser un "Area Chart".

  • Filtre: type:exim
  • Visualize: Chart Area
  • Buckets: X-Axis
  • Aggregation: date histogram
  • Field: @timestamp
  • Split Area:
  • Aggreation: Filters
  • Filtre 1: from_sender:*
  • Filtre 2: from_sender:* AND from_local_user:*
  • Filtre 3: to_email:* AND NOT r_action:local_user

Métrique: quels sont les 10 domaines externes les plus sollicités

Une simple table fera l'affaire. Nous aurions pu ajouter un champ logstash pour récupérer le domaine d'envoi. En l'absence, nous utiliserons le serveur qui héberge ce domaine.

  • Filtre: type:exim AND NOT r_action:local_user AND NOT from_sender:*
  • Visualize: Data Table.
  • Buckets: split rows.
  • Aggregation: Terms
  • Field: to_host.raw
  • Order:Top, Size: 10.
  • Order by: metric: Count

Métrique: quels sont les 10 domaines externes qui nous envoient le plus de mails

Une simple table fera l'affaire. Nous aurions pu ajouter un champ logstash pour récupérer le domaine d'envoi. En l'absence, nous utiliserons le serveur qui héberge ce domaine.

  • Filtre: type:exim AND from_sender:*
  • Visualize: Data Table.
  • Buckets: split rows.
  • Aggregation: Terms
  • Field: from_host.raw
  • Order:Top, Size: 10.
  • Order by: metric: Count

Métrique: Histogramme des rejets selon le temps

[[!img Erreur: bad image filename]]

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

  • Filtre: type:exim_rject
  • Visualize: Aera chart.
  • Buckets: split slices.
  • X-Axis
  • Aggregation: Date Histogram
  • Field: @timestamp
  • Interval: Auto

Métrique: Liste des IP rejetées

[[!img Erreur: bad image filename]]

Autre métrique assez simple qui travaille sur le champ rejectip que nous avons défini dans le filtre Logstash.

  • Filtre: type:exim_reject
  • Visualize: Data Table.
  • Buckets: split rows.
  • Aggregation: Terms
  • Field: rejectip.raw
  • Order:Top, Size: 100.
  • Order by: metric: Count

Métrique: Les grandes classes de rejet

[[!img Erreur: bad image filename]]

Autre métrique assez simple qui travaille sur le champ message car c'est la seule source d'information que nous avons.

  • Filtre: type:exim_reject
  • Visualize: New Pie Chart.
  • Buckets: split slices.
  • Aggregation: Filter
  • Filter 1: message:relay not permitted
  • Filter 2: message: invalid argument
  • Filter 3: message:SMTP protocol

Conclusion

On peut bien entendu ajouter d'autres mesures même si j'ai essayé d'intégrer le plus d'informations d'intérêt. Par exemple, on pourrait également tracer les messages d'erreur du serveur ou les éléments de vérification de mail (DKIM/SPF par exemple).

Avec un peu de réflexion et de fabrication de motifs, on peut tout mettre dans Elasticsearch et faire de beaux diagrammes avec Kibana, comme d'habitude. Attention, les logs Exim sont souvent très volumineux. Ils occasionneront sans doute une grosse charge sur l'instance Logstash qui sera chargée de les traiter. Bien entendu, votre instance Elasticsearch consommera sans doute beaucoup d'espace disque. Mais les filtres Logstash que j'ai essayé de construire limitent la charge. En effet, un mail entrant ou sortant consommera une seule ligne. Si vous intégrez les messages d'erreur, ce sera sans doute plus consommateur de ressources. A vous de voir ce que vous voulez visualiser...