6030 links
  • GuiGui's Show

  • Home
  • Login
  • RSS Feed
  • Tag cloud
  • Picture wall
  • Daily
Links per page: 20 50 100
page 1 / 1
  • Retour sur l'organisation de mes journaux informatiques et améliorations

    Cela fait environ dix ans que j'ai fait des choix de rangement de mes journaux informatiques (logs) sur mes serveurs personnels. Il est grand temps de faire un bilan et d'améliorer ce qui doit l'être.


    Ce qui fonctionne

    Un journal par logiciel, un dossier par service

    Sur un système GNU/Linux, les journaux sont éclatés en fichiers par catégorie (facility) et par priorité. Un même message peut être consigné dans plusieurs fichiers.

    Je pense que cette organisation en gros pâtés n'est pas intuitive ni optimale.

    Ce que je fais :

    • Un fichier journal par logiciel, pas de duplication ;

    • Quand plusieurs logiciels concourent à un même service (mail = postfix, dovecot, etc.), je crée un dossier qui contient un journal par logiciel ;

    Ça se fait facilement en rajoutant des fragments de configuration dans /etc/rsyslog.d. Ainsi, pour CRON, je crée un fichier /etc/rsyslog.d/cron.conf contenant cron.* -/var/log/cron.log.


    Séparer les archives dans une arborescence dédiée

    De base, les journaux archivés (« .log.1 », « .log.2.gz », etc.) sont stockés à côté du journal courant. Du coup, /var/log devient rapidement un fouillis dans lequel je peine à m'y retrouver. C'est encore plus prononcé quand on a des obligations de conservation sur un temps long.

    Ce que je fais : une arborescence séparée /var/log/archives/<nom_logiciel_ou_service>.

    Cela suppose de surcharger tous les fragments de configuration déposés dans /etc/logrotate.d/ par des paquets logiciels. Pour éviter d'incessantes questions lors des mises à jour desdits paquets, j'utilise dpkg-divert. Exemple : dpkg-divert --add --rename --divert /etc/logrotate.d/apache2.dpkg-dist /etc/logrotate.d/apache2 qui ordonne de ranger le fichier /etc/logrotate.d/apache2 livré par un paquet dans /etc/logrotate.d/apache2.dpkg-dist. logrotate ignore les fichiers dont le nom termine par « .dpkg-dist ».


    Ce que j'ai amélioré

    Ordre des directives dans mes configurations logrotate

    Tantôt des fichiers de configurations dans /etc/logrotate.d commençaient par la périodicité, tantôt par la durée de conservation, tantôt par… De plus, je n'utilisais pas les mêmes directives partout sans justification (ifempty, par ex.).

    C'est galère pour chercher visuellement ou automatiquement des informations.

    J'ai tout harmonisé au format : paramètres généraux puis paramètres du journal courant puis paramètres des archives puis scripts. Donc : périodicité, rotate, missingok, ifempty, create, olddir, compress, delaycompress, sharedcripts, postrotate.


    Harmonisation des paramètres de logrotate entre mes serveurs

    Il y avait des divergences mineures entre mes serveurs persos pour un même journal. C'est corrigé.


    Prise en compte des "nouveautés"

    Debian est passé de mysql à mariadb, donc les noms des journaux n'étaient plus parlants (ceci dit, la configuration demeure toujours dans /etc/mysql donc bon…), des directives rsyslog étaient devenues inutiles (elles ne capturaient et donc ne redirigeaient plus rien) et, c'est désormais rsyslog qui journalise tout (cf. /etc/mysql/mariadb.conf.d/50-server.cnf), donc le script postrotate dans la configuration de logrotate semble insuffisant, j'y ai rajouté /usr/lib/rsyslog/rsyslog-rotate.

    Lors de l'activation de HTTP/2 sur ce site, je suis passé de php sous forme de module pour Apache httpd à php-fpm. Désormais, il y a un journal pour le gestionnaire (php-fpm) qui était pris en charge par logrotate via une configuration livrée avec le paquet, mais qui, du coup, n'archivait pas dans mon arborescence séparée ni pour une durée pour laquelle j'avais consentie.

    L'ennui, c'est que le nom du journal, « php7.4-fpm.log », changera à la prochaine mise à jour majeure de Debian, car la version de php changera (7.3 -> 7.4 -> 8.2). Le fragment de configuration pour logrotate sera livré dans le paquet, mais ne contiendra pas mes directives. Configurer PHP-FPM pour envoyer son journal à rsyslog ne résout pas ce problème : l'arborescence de configuration dépend aussi de la version de PHP (/etc/php/<VERSION>/fpm/php-fpm.conf), donc elle sera effacée par la mise à jour. Vu le cycle de vie très rapide de php-fpm, l'objectif de ce nommage dépendant de la version est de pouvoir installer simultanément plusieurs versions de PHP sur un même système.

    J'ai créé un fichier /etc/logrotate.d/php :

    /var/log/php*-fpm.log {
        […]
        postrotate
            # Demander à la "bonne" version des php-fpm de réouvrir son journal
            binname=$(find /usr/lib/php/ -iname 'php*-fpm-reopenlogs')
            if test -x "$binname"
            then
                $binname;
            fi
        endscript
    }

    Hé oui, là encore, le binaire qui permet de dire à php-fpm de changer de journal dépend du numéro de version…

    Mais, ça ne suffit pas : logrotate.conf inclut les fragments de conf' présents dans /etc/logrotate.d, dont php et php-X.Y-fpm. Logrotate consignera que deux fragments de conf' portent sur un même journal et terminera en erreur, pour ces deux fragments, donc le journal de php ne sera pas archivé et le script pas exécuté. Je crée donc un fichier /etc/cron.daily/php :

        #!/bin/bash
    
        # dpkg-divert une éventuelle nouvelle config suite màj majeure Debian
        if test -f /etc/logrotate.d/php*-fpm
        then
                confpath=$(find /etc/logrotate.d/ -type f -name 'php*-fpm')
                dpkg-divert --quiet --add --rename --divert "${confpath}.dpkg-dist" "$confpath"
        fi


    Factorisation de mes journaux web

    J'ai déjà expliqué ce point : je créais un journal par hôte virtuel, donc deux journaux par site web, un pour HTTP, l'autre pour HTTPS.

    Ce distinguo ne m'a rien apporté en dix ans. Notamment car, par défaut, Apache httpd ne journalise pas les erreurs TLS. Je ne vais pas activer cela vu que j'en n'ai pas eu besoin et que ça risque de souvent biper à tort vu mon certificat x509 signé par une autorité de certification maison.


    Correction de configurations afin d'éliminer les messages inutiles

    Shaarli consigne une erreur à chaque robot qui accède, sans définir le referer (la case n'existe alors pas dans le tableau $_SERVER), aux liens qui permettent de changer le nombre de liens par page. Et comme shaarli redéfinit error_reporting… Bref, dans index.php de shaarli, remplacer le error_reporting existant par : error_reporting(E_ALL^E_WARNING^E_NOTICE);.

    La bibliothèque de fonctions de RSS-Bridge qui fait l'interface avec l'API de Twitter, TwitterClient, consigne des événements en mode debug ("je réutilise le token mise en cache", "je demande un nouveau token", etc.) qui atterrissent dans le journal des erreurs de l'hôte virtuel. Aucune condition autour de ces directives, donc pas d'autre moyen de les faire taire que de les commenter…

    Un plugin de mon Wordpress, mon thème perso Wordpress et Shaarli généraient des erreurs pour des fonctions dépréciées, pour une syntaxe erronée (function_exists() attend le nom d'une fonction avec ses parenthèses ou entourée de guillements), etc. J'ai corrigé tout ça.

    ejabberd consignait en permanence des erreurs TLS liées à mon certificat x509 autosigné. C'est inutile et ça conservait des données personnelles (l'essentiel de mes contacts ont un serveur Jabber perso donc le nom de domaine est une donnée personnelle). J'ai réduit la verbosité d'ejabberd (loglevel: 2 dans /etc/ejabberd/ejabberd.yml). Le reste était des messages au démarrage "tu charges tel module sans charger tel autre, c'est inhabituel". J'ai corrigé ça.


    Rétention trop longue

    Lors de la définition de ma politique de journalisation, je me suis fait embarquer par la légende urbaine qui voudrait qu'on doit conserver le journal des accès à un serveur web pendant un an, idem pour un serveur emails ou de messagerie. La réalité est plus nuancée et dépend de l'usage et du contexte. J'ai tout détaillé dans un autre article. Au final, je ne suis pas concerné par les obligations légales de conservation de données de connexion.

    De plus, par paresse intellectuelle et par peur du manque, je consignais des journaux techniques sur la même durée (un an) alors que ça n'a aucun intérêt. Consigner les journaux d'OpenDNSSEC durant un an quand la durée de vie de mes signatures est d'une semaine, ça n'a aucun intérêt (s'il y a une erreur, mes services seront inaccessibles dans la semaine). Consigner les transferts de zones DNS sur la même durée est tout aussi inutile, là encore en relation avec la durée d'expiration de mes zones sur mes serveurs DNS secondaires. Idem le journal de ntpd qui consigne rien après son démarrage… Etc.

    Je me rassurais : la plupart de ces journaux ne comportent aucune donnée personnelle. Pour ceux qui en comportent, comme celui de mon serveur emails, ce n'était pas bien grave car, à part les spammeurs et les scanneurs de ports / TLS / etc., ça consignait ma correspondance… que je conserve de toute façon dans mon logiciel de messagerie.

    Parfois, cette durée de conservation m'a été utile. Exemple ? Vérifier les paramètres pris en charge par les gros fournisseurs emails avant de renforcer mes configurations TLS. L'ennui, c'est que tout peut toujours être utile. Avoir un micro en permanence sur toi quand t'es chez ton employeur permettra de prouver ta bonne foi lors d'un contentieux… ou que t'es en tort. Surveiller ton/ta partenaire de cul permettra de détecter sa tromperie… ou la tienne. Fliquer toute la population augmente la probabilité de retrouver l'auteur d'une infraction… ou un dissident politique. C'est précisément le sens des arrêts de la CJUE sur la rétention des données de connexion : l'équilibre entre la préservation de la vie privée et les autres intérêts. Dans le cas d'usage que je cite, je pouvais très bien faire mon étude au fil de l'eau, avant de modifier mes paramètres TLS, en conservant uniquement mes résultats (tel fournisseur emails prend en charge uniquement TLS 1.0, par ex.). C'est un boulot régulier (avant chaque effacement du journal), c'est la seule contrainte.

    Désormais, j'ai défini deux catégories de journaux :

    • Les journaux inutiles (j'y ai jamais rien trouvé d'intéressant) ou utiles à très court terme (lors d'un changement de configuration) : apparmor, bind / nsd (transferts de zones DNS, il faut agir rapidement, cf. ci-dessus pour l'explication), cron (il envoie des emails pour les tâches qui foirent de toute façon), ejabberd (erreurs lors du démarrage, j'y ai jamais rien trouvé en dehors), mariadb (j'y ai jamais rien trouvé, et ça consigne qu'au démarrage), ntpd (idem), opendnssec (il faut agir rapidement, cf. ci-dessus pour l'explication), php-fpm (uniquement les messages du gestionnaire lui-même, pas les erreurs dans le code qui tombent dans le journal des erreurs du serveur web, donc ça consigne uniquement au démarrage et en cas de dépassement des limites configurées), Tiny Tiny RSS (composant qui actualise les flux en tâche de fond et qui ne consigne rien d'utile, les erreurs sont affichées dans l'interface web), btpm / wtmp / lastlog / btmp (je les utilise jamais). Je les conserve 2 semaines (weekly + rotate 1) ;

    • Les journaux utiles à moyen terme :
      • Serveurs web (pour générer des stats d'audience mensuelle et pour diagnostiquer un problème genre compromission par une faille de sécurité que l'on peut mettre du temps à détecter). Je les conserve 6 semaines (weekly + rotate 5) afin d'être sûr qu'ils englobent un mois entier, de son 1er jour à son dernier (explication) ;

      • Serveur emails (diagnostiquer un problème), auth.log / syslog / daemon.log / messages / kern.log (diagnostiquer un problème, y compris une compromission). Je les conserve 5 semaines (weekly + rotate 4), soit un mois glissant ;

      • Gestionnaires de paquets (alternatives.log / dossier apt / dpkg.log). Pas de données personnelles + ça m'a déjà été utile plusieurs mois après + Debian les conserve 13 mois par défaut, donc je les conserve 6 mois (monthly + rotate 5).


    Penser à journald

    Quand on veut réduire la durée de conservation de ses journaux, il faut penser à systemd-journald.

    Sur un système Debian, journald s'interpose entre les logiciels et rsyslog, même quand c'est rsyslog qui ouvre la socket UNIX pour un logiciel (postfix, par ex.). Il reçoit donc tous les messages syslog. Or, par défaut, il implémente une politique basée sur l'occupation de l'espace disque (détails).

    On peut réduire l'occupation disque maximale des journaux, mais je ne suis pas fan : en cas de pic d'activité (ou d'attaque), les journaux pertinents peuvent être écrasés par ceux du pic…

    On peut désactiver partiellement journald (détails, dernière ligne), mais ce n'est pas à l'épreuve du temps vu que journald a des usages pertinents et qu'il est appelé à prendre une place centrale.

    On peut faire un mix entre une politique d'occupation disque et une politique basée sur le temps (détails). En ce qui me concerne, j'ai configuré MaxRetentionSec=5week. Il s'agit, chez moi (cf. section précédente), de la durée maximale désirée des journaux qui transitent par message syslog (apache httpd et nginx écrivent eux-mêmes leurs journaux, idem pour apt et dpkg).


    Penser à logrotate

    La plupart des fragments de configuration par défaut pour logrotate contiennent la directive notifempty, qui lui ordonnent de ne pas archiver les journaux, ni de supprimer les plus vieux, si le journal est vide.

    On peut donc se retrouver avec des journaux, y compris contenant des données à caractère personnel, vieux de plusieurs années.

    Solution : dpkg-divert + corriger la configuration de logrotate.


    Format de date

    Dans la plupart des journaux, la date et l'heure d'un message sont exprimées dans un format difficilement exploitable comme « Jul 1 01:23:45 » ou « 01/Jul/2023:01:23:45 ».

    Cela complique l'affichage simultané de plusieurs journaux dans l'ordre chronologique. On peut faire zcat $(ls -rv), mais bon, paye ta simplicité lors d'une urgence, et ça suppose que tous les journaux soient dans le même dossier. Surtout si les journaux englobent plusieurs mois : sort classera « Jul » (juillet) avant « Jun » (juin), par exemple, et selon le type de tri, il classera 10,11,12, etc. après 01 et avant 02… Oui, il est possible de préciser à sort de longs paramètres pour trier comme il faut, mais, là encore, paye ta simplicité…

    De même, cela complique une recherche dans les journaux : « Jul 1 » (deux espaces) mais « Jul 10 » (une espace). Rien de grave, mais c'est pénible.

    Le pompon, c'est la date+heure au milieu d'une ligne, notamment dans les journaux des serveurs web. Le journal est chronologique, donc la date est la clé du tri implicite, donc elle devrait être en début de ligne, en lieu et place de l'adresse IP qui est l'une des infos du message, pas une caractéristique de ce dernier comme l'est la date. Là encore, ça complique un tri avec sort.

    Tout cela constitue l'une des raisons qui m'a fait reculer à chaque fois que j'ai réfléchi à la réduction de la durée de conservation de mes journaux : une durée de conservation de quelques semaines, un mois au max, est souvent la plus apropriée, mais en monthly + rotate 0, logrotate supprime le journal des derniers jours d'un mois dès le 1er jour du mois suivant (voir), donc on part sur rotate 1 donc 2 mois de conservation… De plus, descendre en dessous d'un mois, c'est choisir une périodicité quotidienne ou hebdomadaire, et donc devoir agréger plusieurs journaux pour obtenir une vision sur « quelques semaines - 1 mois », et donc se retrouver face au problème de tri énoncé ci-dessus…

    Il existe deux ensembles de formats de date+heure faciles à manipuler : ISO 8601 et RFC 3339. Les arguments sont ici. Les deux normes ne sont pas équivalentes, certains formats communs aux deux normes, d'autres non, voir.

    Comment les utiliser ?


    rsyslog

    D'après sa documentation, rsyslog permet d'utiliser le format « %Y-%M-%D %h:%m:%.6s%Z:%z », dérivé de « %Y-%M-%D %h:%m:%.3s%Z:%z » (seul le nombre de chiffres après la seconde change), qui est un format commun au RFC 3339 et à ISO 8601.

    Pour l'utiliser dans tous les journaux dont rsyslog à la charge :

    # echo '$ActionFileDefaultTemplate RSYSLOG_FileFormat' > /etc/rsyslog.d/0-dateformat.conf
    # systemctl restart rsyslog

    (Préfixer le nom du fichier par « 0 » permet qu'il soit le premier inclus dans /etc/rsyslog.conf. Sans cela, le format de date+heure des logiciels dont le fragment de config' rsyslog serait inclus avant resterait inchangé.)


    Logiciels n'utilisant pas syslog

    Inventaire :

    • btmp, wtmp, lastlog, faillog : fichiers binaires qui se consultent avec des commandes dédiées, donc osef ;

    • apt/, dpkg.log, alternatives.log : ils utilisent le format « %Y-%M-%D %h:%m:%s », qui est proche de ceux des normes sus-citées et qui en présente les mêmes avantages sans en faire partie ;

    • ejabberd/ : ils utilisent le format « %Y-%M-%D %h:%m:%.6s%Z:%z », dérivé de « %Y-%M-%D %h:%m:%.3s%Z:%z » (seul change le nombre de chiffres après la seconde) normalisé dans le RFC 3339. De toute façon, d'après sa doc, ejabberd ne permet pas de changer le format de ses journaux ni d'envoyer ses messages à syslog ;

    • php-fpm : utilise un format merdique. Aucun paramètre pour changer de format. On peut envoyer les messages dans syslog, mais il y a un fichier de configuration par version de PHP, donc, à chaque mise à jour majeure de Debian, il faudra refaire le travail. Tout ça pour quelques messages indiquant que des limites ont été dépassées à cause de robots, ça ne m'intéresse pas ;

    • apache httpd : utilise un format merdique, pour les journaux des accès (globaux ou par hôte virtuel) et des erreurs (idem). On peut le changer ou tout envoyer dans syslog, mais, si l'envoi des journaux d'erreur est propre, celui des journaux d'accès repose sur logger (voir). À chaque requête, un processus logger est lancé. Ça me paraît bien gourmand pour rien en comparaison d'une écriture dans une socket UNIX syslog… De plus, comme déjà dit ci-dessus, journald "interceptera" les messages syslog et procédera à une double journalisation, qui consommera de l'espace disque, et désactiver journald, même partiellement, est à contre-courant de l'histoire.

    • nginx :
      • D'après sa doc', et contrairement à Apache httpd, les journaux des accès et ceux des erreurs (globaux ou par hôte virtuel) peuvent être envoyés proprement à syslog via une socket. Par harmonie avec mes serveurs Apache httpd et pour éviter une duplication des journaux par journald, je ne vais pas utiliser cette fonctionnalité ;

      • D'après sa doc', le format des journaux des erreurs (globaux ou par hôte virtuel) ne peut pas être changé. mais il utilise le format « %Y/%M/%D %h:%m:%s » qui est proche de ceux des normes sus-citées et en présente les mêmes avantages sans en faire partie (à cause des slashes…) ;

      • Le format des journaux des accès (globaux ou par hôte virtuel) peut être changé.


    Changer le format des journaux d'Apache httpd

    Le format combined des journaux des accès, qui répond à mes autres besoins, est défini dans /etc/apache2/apache2.conf, il n'y a plus qu'à l'adapter à ce qu'on veut en se reposant sur la doc' ou en utilisant un exemple tout prêt.

    Le format des journaux des erreurs et les formats possibles sont définis dans la doc'. On notera que le format des journaux des accès (ISO 8601) ne peut pas être repris dans le journal des erreurs. Le format qui s'en rapproche le plus est « %Y-%M-%D %h:%m:%.6s » qui, comme dit plus haut, ne fait partie ni d'ISO 8601, ni du RFC 3339, mais en est très proche et en présente les mêmes avantages.

    Pour appliquer le format :

    # Journaux des accès
    echo 'LogFormat "%{%Y-%m-%dT%T}t %h %l %u \"%r\" %>s %O \"%{Referer}i\" \"%{User-agent}i\"" combined-with-iso8601' > /etc/apache2/conf-available/iso8601-log-format.conf
    echo 'LogFormat "%{%Y-%m-%dT%T}t %v:%p %h %l %u \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined-with-iso8601' >> /etc/apache2/conf-available/iso8601-log-format.conf
    a2enconf iso8601-log-format.conf
    # Dans chaque hôte virtuel
    CustomLog ${APACHE_LOG_DIR}/<vhost>/access.log combined-with-iso8601
    # Pour other_vhosts_access.log
    dpkg-divert --add --rename --divert /etc/apache2/conf-available/other-vhosts-access-log.conf.dpkg-dist /etc/apache2/conf-available/other-vhosts-access-log.conf
    echo 'CustomLog ${APACHE_LOG_DIR}/other_vhosts_access.log vhost_combined-with-iso8601' > /etc/apache2/conf-available/other-vhosts-access-log.conf
    
    # Journaux des erreurs
    echo 'ErrorLogFormat "%{cu}t [%-m:%l] [pid %P:tid %T] %7F: %E: [remote\ %a] %M% ,\ referer:\ %{Referer}i"' >> /etc/apache2/conf-available/iso8601-log-format.conf
    # Dans chaque hôte virtuel (oui, pas besoin de préciser le format, il est unique, sauf si on le surcharge dans un hôte virtuel)
    ErrorLog ${APACHE_LOG_DIR}/<vhost>/error.log
    # /var/log/apache2/error.log est défini dans /etc/apache2/apache2.conf, il n'y a rien à faire


    Changer le format des journaux des accès de nginx httpd

    (Je rappelle que le format des journaux des erreurs, globaux ou par hôte virtuel, ne peut pas être modifié, cf. ci-dessus.)

    Le format combined des journaux des accès, qui correspond à mes autres besoins, est défini dans la doc', il n'y a plus qu'à l'adapter. Toutes les variables utilisables (TLS, communication avec le backend, etc.) ne sont pas mentionnées, il faut creuser ailleurs dans la doc' :(. En tout cas, on a la variable « $time_iso8601 » qui fait ce qu'on veut.

    Mise en pratique :

    Le journal global des accès est défini dans nginx.conf avant l'inclusion des confs situées dans conf.d/… donc il faut modifier nginx.conf, soit pour inverser cet ordre, soit pour ajouter nos directives directement dedans, ce que j'ai fait…

    dpkg-divert --add --no-rename --divert /etc/nginx/nginx.conf.dpkg-dist /etc/nginx/nginx.conf
    
    # Dans nginx.conf
    log_format combined-with-iso8601 '$time_iso8601 $remote_addr - $remote_user "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"';
    access_log /var/log/nginx/access.log combined-with-iso8601;
    
    # Dans chaque hôte virtuel
    access_log /var/log/nginx/<vhost>/access.log combined-with-iso8601;
    16/07/2023 18:31:30 - permalink -
    - http://shaarli.guiguishow.info/?aY4WgQ
Links per page: 20 50 100
page 1 / 1
Mentions légales identiques à celles de mon blog | CC BY-SA 3.0

Shaarli - The personal, minimalist, super-fast, database free, bookmarking service by the Shaarli community