Récemment, je me suis un peu fritté avec Postfix et ElasticSearch et j’ai fini par me dire que ça pourrait en intéresser certains. Le challenge est assez simple en réalité : être capable de repérer très rapidement qui a parlé à qui et à quel moment.

Et en fait, c’est pas aussi simple que ça.

Au début, il y avait syslog

La base du problème n’est finalement pas si compliqué que ça : quand on a un ou plusieurs serveurs SMTP, on peut commencer par balancer l’ensemble des logs dans syslog. Ça permet de les centraliser et ça permet également de faire des recherches croiser quand on en a plusieurs.

Jusque là, rien de bien sorcier. Je vous passe également le fait de balancer ces logs dans Logstash. Je ne dis pas que c’est à la portée du premier con venu, mais ça dépasse quelque peu le cadre de cet article (je te rassure on va quand même en faire un peu, mais je ne détaillerai pas plus que ça).

Bref, je pars du principe que vous avez un syslog qui concentre tout et qui repasse le bébé à un Logstash.

Ensuite, il fallu faire un peu le tri

Malheureusement, comme souvent avec ELK, il va être nécessaire de faire le tri en amont pour ne pas se retrouver avec des grosses conneries un peu partout. Dans Logstash, il va falloir faire un pipeline et y appliquer des filtres. Donc, on va insérer ça dans le fichier /etc/logstash/pipelines.yml :

- pipeline.id: syslog
   path.config: "/etc/logstash/syslog/conf.d/*.conf"

Ça permet de créer une configuration complètement à part pour syslog avec des entrées, des sorties et tout ce qui va bien, tout en faisant un truc à peu près propre. À partir de ce moment-là, on peut créer, dans /etc/logstash/syslog/conf.d/, deux fichiers 10_input.conf et 90_output.conf :

10_input.conf :

input {
    udp {
        port => 1234
        type => "syslog"
    }
}

90_output.conf :

output {
  elasticsearch {
    index => "syslog-%{+YYYY.MM.dd}"
    template_name => "syslog-*"
  }
}

Si tu te poses la question, il faut que les fichiers soient lus dans le bon ordre pour faire les choses correctement, donc oui, je mets des chiffres en début de fichiers, c’est moche mais au moins, je suis certain que la conf est lue dans le bon sens.

Ça permet de créer une entrée (type syslog) et une sortie dans un template Elasticsearch. Jusque là, on n’a pas vraiment réinventé la mayonnaise, on prend juste des entrées et on les balance dans des sorties (et figurez-vous que ça fonctionne, c’est un truc de gue-din). Bon, maintenant, on a certes des données, mais elles ne sont absolument pas triées.

En gros, on a une date et un message, mais c’est à peu près tout. Et ça ne nous avance pas beaucoup. Donc, on va faire un premier filtre qui va permettre de récupérer les données syslog et de commencer à faire un premier tri. Tu peux faire un fichier intermédiaire (qu’on appellera 20_filter_10_begin.conf) :

filter {
  if [type] == "syslog" {
    grok {
      match => { "message" => "%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{PROG:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:syslog_message}" }
      add_field => [ "received_at", "%{@timestamp}" ]
      add_field => [ "received_from", "%{host}" ]
    }
    date {
      match => [ "syslog_timestamp", "MMM  d HH:mm:ss", "MMM dd HH:mm:ss" ]
    }
  }
}

Voilà, maintenant, tu relances ton Logstash et tu devrais commencer à récupérer quelques données. Elles sont relativement brutes : tu as le serveur, la date et l’heure, le nom du programme et c’est à peu près tout. Ça peut déjà pas mal servir, mais on va quand même essayer d’affiner un peu…

Et après, on laisse affiner pendant quelques mois, comme pour le Beaufort

Maintenant, tu vas aller télécharger ces quelques fichiers et les coller dans les bons répertoires (/etc/logstash/syslog/patterns/ pour le fichier .grok et dans conf.d pour le reste, en faisant bien attention à le renommer pour qu’il soit lu après le reste, mais avant le output).

Une fois que tu as fait ça, tu peux de nouveau relancer Logstash. Ces modèles sont très très bien faits et permettent de vraiment trier complètement les différentes étapes de traitement de chaque message : mise en queue, traitement, livraison, nettoyage, etc…

Tu peux déjà t’amuser dans l’interface de Kibana pour essayer de retrouver certains messages, des destinataires, des expéditeurs et ainsi de suite :

img

Et rien qu’avec ça, tu peux déjà faire pas mal de merdes ! Tu peux faire des rapports permettant de voir le nombre de messages échangés (suffit de compter les queueid uniques), tu peux voir le volume moyen des messages échangés, etc…

Tout ça, c’est très bien, mais à un moment, quelqu’un viendra forcément te poser la question : et comment je fais pour savoir qui a écrit à qui ?

Ça a l’air simple comme ça, mais en fait non…

Dit comme ça, ça a effectivement l’air tout con et dans l’absolu, tu te dis probablement que tu as la donnée, donc que ça ne devrait pas être si compliqué que ça à extraire. En fait, si. Le souci, c’est que tu as effectivement la donnée : si tu tries en fonction du queueid de Postfix, tu peux effectivement savoir qui a écrit à qui. Mais d’un point de vue données, tu n’as pas tout sur une seule ligne !

Et Elasticsearch a ce côté un peu chiant : si tu n’as pas toutes les infos dont tu as besoin sur la même ligne, dans le même document, dans le même enregistrement, alors faire des recoupements peut devenir extrêmement pénible. Alors évidemment, pour de la recherche manuelle, ce n’est pas bien compliqué : tu cherches le destinataire, tu trouves le queueid, tu retries par queueid et tu finiras bien par tomber dessus.

Mais à partir du moment où tu dois le confier à quelqu’un d’autre ou simplement si tu veux faire des stats ou des rapports un peu plus poussés, bah ça va être très compliqué, voire impossible.

Bah alors, on fait quoi du coup ? À part avoir l’air con…

J’arrive mes petits bisous, j’arrive. En fait, j’ai tourné en rond sur ce problème pendant un bon moment. J’ai d’abord cherché à voir si je pouvais faire des corrélations un peu complexes dans ELK lui-même. Ça n’a pas vraiment donné les résultats que j’attendais (probablement pas impossible à faire, mais bien trop compliqué à mon goût). Il paraît que la version payant d’ELK sait faire pas mal de choses de ce point de vue là, mais je suis trop pauvre pour ça (pour un besoin pro, ça pourrait se justifier, pour un besoin perso, c’est quand même plus compliqué).

J’ai ensuite cherché comment faire du multiligne. C’est possible, et ça existe, il y a même des plugins spéciaux pour ça. Il y a même des gens qui l’ont déjà fait.

Le souci, c’est que tout cela date un peu (beaucoup de fonctions ont changé) et que je ne suis jamais parvenu à le faire fonctionner correctement. Avec un peu de temps et d’efforts, c’est peut-être possible de le faire fonctionner, mais sincèrement bon courage !

Bref, après avoir bien galéré sur le sujet pendant un moment, je me suis dit qu’il était peut-être possible d’enrichir les logs de Postfix avec d’autres données. Si on n’arrive pas à traiter en aval, il vaut mieux revenir sur les données en amont pour essayer de pousser plus de choses ou de rendre les choses un peu différemment.

Première chose à faire donc, dire à Postfix de journaliser d’autres informations sur les messages reçus. Par exemple, leur sujet : tous les messages ont un sujet, cela doit donc être possible de le journaliser. Et bien, oui et c’est même relativement simple. Dans la configuration de Postfix (main.cf), il suffit d’ajouter cette ligne :

header_checks = regexp:/etc/postfix/header_checks

Puis de créer le fichier de regexp correspondant :

/^Subject:/     WARN

Là, on dit à Postfix de journaliser tout message contenant l’entête Suject (donc tous les messages en gros) et de lui attribuer le niveau WARNING. On relance Postfix et là, voilà ce qu’on obtient :

Jun 25 16:32:57 smtp-1 postfix/cleanup[32674]: C9D27601B2: warning: header Subject: RE: I iz a subject from mail1.example.com[203.0.113.1]; from=<bidule@example.org> to=<truc@example.net> proto=ESMTP helo=<mail1.example.com>

\o/

Non seulement Postfix a journalisé le sujet du message (ce qui est déjà très bien, ça nous permet de visualiser cela en une seule ligne), mais, coup de bol, il récupère aussi les autres informations du message en bonus. Et figure-toi que les filtres que tu as utilisé avant permettent de récupérer toutes ces informations directement dans ELK.

Dans Kibana, il suffit alors de mettre en place un petit filtre :

  • syslog_program: postfix

  • postfix_message_level: warning

  • postfix_to exists

  • postfix_from exists

Et tu vas récupérer exactement la liste de l’ensemble des messages qui sont passés par ton SMTP, avec le destinataire, l’expéditeur et le sujet du message :

img

Conclusage

Voilà, maintenant, tu as tout le nécessaire pour aller récupérer des données hyper précises sur tes SMTP et ça ne va pas être très compliqué de récupérer tous les messages dont machin est le destinataire ou tous les messages volumineux à destination de truc ou même faire des gros graphiques de ouf.