Pyruse 1.0 : pour remplacer Fail2ban et autres « scruteurs » de journaux sur un GNU/Linux moderne

Posté par (page perso) . Édité par Davy Defaud, ZeroHeure et palm123. Modéré par ZeroHeure. Licence CC by-sa
92
12
fév.
2018
Supervision

Après plus de dix ans de présence discrète sur LinuxFr.org, tout juste marquée de quelques commentaires, je me décide enfin à proposer une dépêche.
Je souhaite vous présenter la version 1.0 de Pyruse, que j’ai développé sous licence GPL v3 pour mon propre usage en auto‐hébergement, car je sais que se promènent ici d’autres auto‐hébergeurs qui pourront être intéressés.

Sommaire

Origine du projet

Il faut remonter à l’année dernière, avec mon ancien serveur qui arrivait au bout de ses possibilités (utilisation constante de la partition d’échange — swap —, lenteurs…). J’ai alors décidé de le remplacer et d’en profiter pour l’améliorer au niveau du logiciel.

Comme toute occasion est bonne pour apprendre, j’ai décidé de me concentrer sur l’automatisation (avec Ansible) et la sécurisation, le tout avec Arch Linux (j’en reparlerai plus tard…). Quand est venu le moment d’implémenter la supervision, je n’ai pas voulu recommencer à utiliser Epylog, ni Fail2ban :

  • ces deux outils parcourent les journaux système chacun de son côté, c’est du gaspillage de ressources ;
  • aucun des deux outils n’est assez souple pour me permettre la finesse de configuration que je souhaitais obtenir.

De là est venue la décision de créer mon propre logiciel, avec Python (un apprentissage de plus…).

Que fait Pyruse ?

Pyruse est en quelque sorte abonné au journal système de systemd et se contente de pousser chaque entrée lue du journal dans un workflow constitué de filtres et d’actions. J’ai pris mon inspiration non seulement dans les deux logiciels sus‐mentionnés, mais aussi dans Netfilter : la configuration décrit des chaînes d’exécution liées les unes aux autres.

Le cœur de Pyruse ne fait rien. Tout repose sur les filtres et les actions. De base, les filtres proposés sont :

  • des comparaisons : =, ≤, ≥, ∈ ;
  • les expressions rationnelles compatibles Perl (pcre), avec sauvegarde des valeurs capturées ((…), (?P<nom>…)) ;
  • le test d’existence d’un utilisateur.

Au niveau des actions, pour l’instant, j’ai juste implémenté ce dont j’ai besoin, à savoir :

  • l’augmentation et la réinitialisation de compteurs (nombre d’accès SSH en échec par exemple) ;
  • l’envoi instantané de courriels, essentiellement pour des alertes ;
  • un compte‐rendu quotidien des événements des dernières 24 heures ;
  • sans oublier « noop », l’action qui ne fait rien… (si, si, ça sert !).

Ces filtres et actions ont accès à une « entry », une entrée de journal systemd, qui est dans les faits un dictionnaire Python (une Map, pour les adeptes de Java). Chaque filtre ou action peut agir sur cette entrée de journal, et aussi la compléter (extraction d’un nom d’utilisateur, d’une adresse IP, valeur courante d’un compteur…).

Il est trivial d’ajouter un module. À titre d’exemple, voici le code du filtre de test d’égalité :

from pyruse import base

class Filter(base.Filter):
    def __init__(self, args):
        super().__init__()
        self.field = args["field"]
        self.value = args["value"]

    def filter(self, entry):
        return entry[self.field] == self.value if self.field in entry else False

Et voici celui de l’action d’envoi d’un courriel :

import string
from pyruse import base, email

class Action(base.Action):
    def __init__(self, args):
        super().__init__()
        self.subject = args.get("subject", "Pyruse Notification")
        self.template = args["message"]
        values = {}
        for (_void, name, _void, _void) in string.Formatter().parse(self.template):
            if name:
                values[name] = None
        self.values = values

    def act(self, entry):
        for (name, _void) in self.values.items():
            self.values[name] = entry.get(name, None)
        msg = self.template.format_map(self.values)
        email.Mail(msg).setSubject(self.subject).send()

Comme Fail2ban : bannir une adresse IP qui abuse du port SSH

Voici un extrait de configuration qui réalise cela :

"Détecter les accès SSH en échec": [
  { "filter": "filter_equals",
    "args": { "field": "_SYSTEMD_UNIT", "value": "sshd.service" }
  },
  { "filter": "filter_pcreAny",
    "args": { "field": "MESSAGE", "re": [
      "^Failed password for (?P<utilisateur>.*) from (?P<adresseIP>[^ ]*) port",
      "^Invalid user (?P<utilisateur>.*) from (?P<adresseIP>[^ ]*) port",
      "^User (?P<utilisateur>.*) from (?P<adresseIP>[^ ]*) not allowed because not listed in AllowUsers$"
    ] }
  },
  { "action": "action_counterRaise",
    "args": { "counter": "sshd", "for": "adresseIP", "keepSeconds": 300, "save": "nbÉchecs" }
  },
  { "filter": "filter_greaterOrEquals",
    "args": { "field": "nbÉchecs", "value": 4 },
    "else": "… Ça ira pour cette fois, circulez !"
  },
  { "action": "action_nftBan",
    "args": {
      "IP": "adresseIP", "banSeconds": 86400,
      "nftSetIPv4": "Inet4 sshd_ban", "nftSetIPv6": "Inet6 sshd_ban"
    }
  }
],
"… Ça ira pour cette fois, circulez !": [
  { "action": "action_noop" }
]
  1. d’abord, on regarde si l’entrée du journal vient bien de SSH, sinon on passe à la chaîne d’exécution suivante (non écrite dans cet exemple) ;
  2. ensuite, on essaie de repérer une authentification en échec : si c’est bien ça, on passe à l’étape suivante, sinon on passe à la chaîne d’exécution suivante (non écrite dans l’exemple) ;
  3. puisque l’authentification est en échec, on augmente le compteur pour cette adresse IP, chaque détection étant mémorisée cinq minutes ; la valeur du compteur est enregistrée dans nbÉchecs ;
  4. on regarde ce compteur : à partir de 4, on passe à l’étape suivante ; en dessous, on « appelle » la sous‐chaîne d’exécution « … Ça ira pour cette fois, circulez ! » (qui ne fait rien) ;
  5. puisque l’adresse IP à l’origine de la ligne de journal a abusé du service, elle sera bannie pour 24 heures.

Chaque chaîne d’exécution est introduite par une étiquette libre et se déroule jusqu’à ce qu’un filtre soit non passant, auquel cas on passe à la chaîne d’exécution suivante (sauf si un « else » est explicitement fourni). Quand on arrive à la fin de la chaîne d’exécution, si c’est une action, ça s’arrête là ; si c’est un filtre, on continue avec la chaîne d’exécution suivante (les « sous‐chaînes » d’exécution, appelées depuis les « then » et « else », sont ignorées).

Comme Epylog : obtenir un rapport quotidien

Je complète un peu mon exemple précédent :

"Détecter les accès SSH en échec": [
  { "filter": "filter_equals", 
  },
  { "filter": "filter_pcreAny",
    "args": { "field": "MESSAGE", "re": [  ] },
    "else": "… Détecter les accès SSH réussis"
  },
  { "action": "action_counterRaise", 
  },
  { "filter": "filter_greaterOrEquals", 
  },
  { "action": "action_nftBan", 
  },
  { "action": "action_dailyReport",
    "args": { "level": "INFO", "message": "Bannissement de {adresseIP} en accès SSH" }
  }
],
"… Ça ira pour cette fois, circulez !": [
  { "action": "action_noop" }
],
"… Détecter les accès SSH réussis": [
  { "filter": "filter_pcre",
    "args": {
      "field": "MESSAGE",
      "re": "^Accepted (?:password|publickey) for (.*) from ([^ ]*) port ", "save": [ "utilisateur", "adresseIP" ]
    }
  },
  { "action": "action_counterReset",
    "args": { "counter": "sshd", "for": "adresseIP", "graceSeconds": 432000 }
  },
  { "action": "action_dailyReport",
    "args": { "level": "INFO", "message": "Login en tant que {utilisateur}@{_HOSTNAME} par SSH" }
  }
],
"Entrée de log inconnue": [
  { "action": "action_dailyReport",
    "args": {
      "level": "OTHER",
      "message": "[{PRIORITY}/{SYSLOG_IDENTIFIER}] {_UID}:{_GID}@{_HOSTNAME}:{_CMDLINE} ({_SYSTEMD_UNIT})\n    {MESSAGE}"
    }
  }
]

Cette fois‐ci, si une entrée du journal n’émane pas de SSH, elle passe directement à « Entrée de log inconnue », où elle est ajoutée au (futur) rapport quotidien (section « Autre »).
Du côté de SSH :

  • ça commence pareil, mais si le message ne correspond pas à un échec d’authentification, on tente de détecter une authentification réussie ; de plus, après un bannissement, on ajoute une information au (futur) rapport quotidien ;
  • une nouvelle sous‐chaîne d’exécution traite les accès SSH réussis : dans ce cas, on met l’adresse IP concernée en liste blanche pendant cinq jours et on ajoute un message au (futur) rapport quotidien.

Passé minuit, je recevrai un courriel qui pourra ressembler à ça (en texte, mais il y a aussi du HTML) :

= Pyruse Report

== WARNING Messages

|===============================================================================
|Count|Message                                    |Date+time for each occurrence
|===============================================================================

== Information Messages

|===============================================================================
|Count|Message                                    |Date+time for each occurrence

|  1  |Banissement de {115.193.108.70} en accès SSH
      |2018-02-09 04:28:28.247342

|  2  |Login en tant que yves@dmz par SSH
      |2018-02-09 08:45:35.049244 +
       2018-02-09 09:36:14.220262

|===============================================================================

== Other log events

----------
2018-02-09 07:55:25.159374: [7/ldapwhoami] 994:17@dmz:None (None)
    DIGEST-MD5 common mech free
2018-02-09 08:05:00.738969: [3/php] 994:994@backend:/usr/bin/php -f /srv/nextcloud/cron.php (nc-cron.service)
    {PHP} PHP Startup: Unable to load dynamic library '/usr/lib/php/modules/imagick.so'
----------

Et davantage !

Bien sûr, le but n’était pas de copier l’existant. Voici ce qu’apporte Pyruse.

Du contexte

Pour commencer, les logiciels de ce type sont généralement conçus pour appliquer un ensemble de règles à chaque ligne de journal, règle après règle, sans tenir compte de ce qui a déjà été vu.

Par contraste, notez dans l’exemple ci‐dessus qu’après avoir constaté qu’une entrée de journal issue de SSH n’est pas un échec d’authentification, on ne vérifie pas à nouveau que l’entrée de journal est bien issue de SSH : cela a déjà été vérifié ! On regarde donc directement si le message correspond à une authentification réussie.

Optimisation de l’exécution grâce au contexte

Le fichier de configuration est prévu pour traiter les entrées du journal de l’ensemble des services fonctionnant sur le serveur. Une fois déterminé qu’une entrée de journal n’est pas issue de sshd.service (ce qui n’est au passage qu’un test d’égalité, bien plus efficace que d’appliquer une expression rationnelle), on passe directement à la chaîne d’exécution suivante, évitant ainsi l’ensemble des expressions rationnelles relatives à SSH.

Optimisation de l’exécution grâce à systemd

Comme expliqué ci‐dessus, un simple test sur la valeur de _SYSTEMD_UNIT dans l’entrée de journal permet d’éviter d’un coup plusieurs tests non pertinents.
De plus, une simple comparaison numérique avec PRIORITY peut suffire à écarter d’énormes quantités de journaux :

"Détecter les erreurs HTTP vers Nextcloud": [
  { "filter": "filter_equals",
    "args": { "field": "_SYSTEMD_UNIT", "value": "uwsgi@nextcloud.service" }
  },
  { "filter": "filter_pcre",
    "args": {
      "field": "MESSAGE",
      "re": "^\[[^]]+\] ([^ ]+) .*\] ([A-Z]+ /[^?]*)(?:\?.*)? => .*\(HTTP/1.1 5..\)",
      "save": [ "adresseIP", "requêteHTTP" ] },
    "else": "… Ignorer les erreurs de codage de Nextcloud"
  },
  { "action": "action_dailyReport",
    "args": {
      "level": "INFO",
      "message": "Nextcloud : échec de « {requêteHTTP} » par {adresseIP}"
    }
  }
],
"… Ignorer les erreurs de codage de Nextcloud": [
  {
    "filter": "filter_in",
    "args": { "field": "PRIORITY", "values": [ 2, 3 ] },
    "else": "… Ignorer les erreurs de binding Nextcloud–LDAP"
  },
  { "action": "action_noop" }
],
etc.

Ici, constatant que Nextcloud génère en priorité 2 et 3 des erreurs qui se rapportent au codage, j’ignore toutes les entrées de journal de ces niveaux de priorité, après avoir récupéré les quelques messages de ces niveaux qui m’intéressent.

Souplesse de configuration

Il est possible de combiner les possibilités pour effectuer des actions proportionnées ; par exemple :

  • si l’événement constaté nécessite un intervention au plus vite, envoi d’un courriel immédiat ;
  • si tel n’est pas le cas mais qu’une étude de la situation est nécessaire, ajout d’un avertissement dans le rapport quotidien ;
  • si l’événement n’est pas si important mais mérite quand même d’être mentionné, ajout d’une information dans le rapport quotidien.

Il est aussi possible d’élaborer des workflows complexes :

"Détecter les accès SSH en échec": [
  { "filter": "filter_equals", 
  },
  { "filter": "filter_pcreAny",
    "args": { "field": "MESSAGE", "re": [  ] },
    "else": "… Détecter les accès SSH réussis"
  },
  { "filter": "filter_userExists",
    "args": { "field": "utilisateur" },
    "else": "… Utilisateur SSH inexistant"
  },
  { "action": "action_email",
    "args": { "message": "ATTENTION: Tentative d’intrusion en tant que {utilisateur} !" }
  },
  { "action": "action_dailyReport",
    "args": { "level": "WARN", "message": "Authentification SSH en échec pour {utilisateur}" },
    "then": "… Détecter les échecs SSH répétés"
  }
],
"… Utilisateur SSH inexistant": [
  { "action": "action_dailyReport",
    "args": { "level": "INFO", "message": "Authentification SSH en échec pour {utilisateur}" },
    "then": "… Détecter les échecs SSH répétés"
  }
],
"… Détecter les accès SSH réussis": [
  { "filter": "filter_pcre", 
  },
  { "action": "action_counterReset", 
  },
  { "action": "action_dailyReport", 
  }
],
"… Détecter les échecs SSH répétés": [
  { "action": "action_counterRaise", 
  },
  { "filter": "filter_greaterOrEquals", 
  },
  { "action": "action_nftBan", 
  },
  { "action": "action_dailyReport", 
  }
],
"… Ça ira pour cette fois, circulez !": [
  { "action": "action_noop" }
],
etc.

Ici, la section complétée dans le rapport quotidien (WARN ou INFO) dépend de l’existence ou non de l’utilisateur sur le système. De plus, si l’utilisateur existe, un courriel d’alerte est immédiatement envoyé.

Le futur

Pour ma part, les fonctionnalités nécessaires sont présentes. Toutefois, si d’autres personnes se montrent intéressées par ce logiciel et qu’il leur manque des modules, il est facile d’en ajouter ; j’accepterai avec plaisir les contributions. ;-)

Il y a tout de même une chose que je voudrais faire évoluer à court terme : le rapport quotidien. Je prévois d’utiliser un système de modèles (templates). En effet, je conçois que tout le monde n’adhère pas forcément à mon souhait d’afficher pour chaque message la date et l’heure de chaque occurrence…

Ah, une dernière chose : c’est aussi la première fois que je communique publiquement autour d’un projet hébergé par Gitea sur mon serveur, et je n’ai pour l’instant qu’une vision interne. Si quelqu’un rencontre un quelconque problème d’interaction avec le serveur, il ne faut pas hésiter à me le signaler ! Merci. :-)

Y.

  • # Sympa!

    Posté par . Évalué à 6.

    Je trouve l'idée de parser les logs en mode 'flux' avec systemd plutôt pas mal. A tester!
    Merci!

  • # Message

    Posté par (page perso) . Évalué à 10.

    À mon avis, ce qu'il manque à fail2ban c'est de pouvoir envoyer une message sur un serveur central disant par exemple "attaque sur port 22 depuis IP X.X.X.X.X", à charge ensuite sur le serveur central de propager en broacast l'info si celle-ci dépasse une certaine valeur.

    Ainsi, 3 de tes PC se font attaquer -> tu protèges l'ensemble de ton parc.

    • [^] # Re: Message

      Posté par (page perso) . Évalué à 3.

      Je comprends. C’est en effet intéressant quand tu gères une ferme de serveurs.

      Avec Pyruse, il suffirait de créer un gestionnaire de compteurs alternatif adossé à une base de données (copier-coller de l’existant, avec juste un backend différent), puis d’implémenter les actions triviales +/−/reset.
      La base de données pourrait être un PostgreSQL/MySQL… pour une grosse installation, ou peut-être un fichier SQLite sur partage réseau (quid des accès concurrents ?)…

      Une autre solution, peut-être plus proche de ton idée, serait d’avoir un petit dæmon qui écoute en IP-Multicast : lorsqu’il reçoit une notification d’attaque, il la logue dans systemd. La conf. peut alors donner à cette « attaque » autant de poids qu’une véritable attaque locale. Et bien sûr, il y aurait une action à développer qui diffuse en IP-Multicast les attaques locales.

      Bref… C’est possible :-)

      • [^] # Re: Message

        Posté par . Évalué à 2.

        Tu décris à peu près ce que fournit un Redis (voire un Memcached).

    • [^] # Re: Message

      Posté par (page perso) . Évalué à 4.

      C'est a priori déjà possible, il suffit de faire tourner fail2ban sur un collecteur rsyslog.
      Enfin, failt2ban ou tout autre système (graylog permet de le faire, par exemple)

      • [^] # Re: Message

        Posté par (page perso) . Évalué à 2.

        C’est vrai que je n’avais pas pensé à ça !

        D’ailleurs, Le journal systemd a cette même capacité de centralisation. Je surveille moi-même 2 hôtes avec Pyruse. Le champ _HOSTNAME me fournit l’hôte concerné.

    • [^] # Re: Message

      Posté par . Évalué à 3.

      bah perso je le fais…
      J'ai crée une action perso qui envoie une requête http sur un serveur central, qui va générer une blackliste qui est ensuite propagée aux autres serveurs qui viennent la récupérer 2x / heure…

      Assez simple à faire en fait, fail2ban est très souple à ce niveau là.

      Si on veut quelque chose en temps réel il faut prévoir un mécanisme pour "pousser" l'info vers les autres serveurs, surement via ssh par exemple. Mais la flemme de faire ça, de toute façon ce n'est pas nécessaire.

      Et au passage je blackliste de façon permanente toute IP bloquée + de 20x…

  • # ça me rappelle un truc ...

    Posté par (page perso) . Évalué à 4. Dernière modification le 12/02/18 à 23:11.

    Un article de blog qui m'avait bien plu à l'époque:

    Get rid of syslog (or a journald log filter in ~100 lines of Python)

    pyruse m'a l'air d'une solution bien plus flexible pour ce genre d'usage.

    Merci de partager ton projet en tout cas :)

  • # Internationalization

    Posté par . Évalué à 0.

    Super projet, je m'en vais de ce pas y jeter un oeil. Juste une remarque :je sais bien que Python 3 supporte Unicode jusque dans les noms de variables, mais amont humble avis CNET une très mauvaise idée les caractères accentués dans noms de variables !

    Tu devrais aussi penser à l'internationalisation dès maintenant,les messages en français ça n'aide pas pour la diffusion…

    • [^] # Re: Internationalization

      Posté par (page perso) . Évalué à 10.

      Ah ah :-) C’est amusant : En lisant le titre de ton commentaire, je m’attendais exactement à l’inverse de ce que tu as finalement écrit.

      En fait, si tu regardes, tout le code ainsi que la documentation sont en anglais ; rien en français. Tout le français que tu vois dans la dépêche est situé dans des clefs de dictionnaires Python. Ce ne sont que de quelconques chaînes de caractères.
      D’ailleurs, tu noteras que le rapport quotidien est en anglais ; c’est une autre raison pour laquelle je souhaite mettre en place des « patrons » de génération à la place d’un rendu fixe.

      Il y aurait donc bien un petit effort d’internationalisation à faire, pour rendre peut-être le logiciel plus accessible aux personnes non anglophones ;-)

      • [^] # Re: Internationalization

        Posté par . Évalué à 2.

        Effectivement, j'ai lu les bouts de code de ta dépêche en diagonale via mon agrégateur de flux RSS, qui me fait sauter la coloration syntaxique, et le "nbÉchecs" m'a fait saigner les yeux, mais je n'ai pas encore cloné ton projet.

        Donc mea culpa

  • # YAML

    Posté par . Évalué à 2.

    Super projet. Merci.

    La possibilité d'utiliser du YAML pour la configuration serait encore mieux - parce que JSON ça pique les yeux (même si la coloration syntaxique de linuxfr en rouge n'aide pas :)

    • [^] # Re: YAML

      Posté par . Évalué à 4.

      Question de goût.
      Perso, quand j'écris des playbooks ansible, c'est toujours la misère, parce qu'il y a une espace en trop (ou en moins), et ce n'est plus une liste, c'est un dictionnaire, ou l'inverse.
      C'est sûrement un problème qui vient de moi, mais perso, JSON, je trouve que c'est plus « sympa ».

      Sans troller, y a-t-il des meilleurs arguments ?

      • [^] # Re: YAML

        Posté par . Évalué à 10.

        Sans troller, y a-t-il des meilleurs arguments ?

        Les vrais gros plus du yaml c'est la possibilité d'avoir des commentaires et de faire des chaînes multi lignes.

        • [^] # Re: YAML

          Posté par (page perso) . Évalué à 3.

          Je ne peux pas généraliser à tout fichier JSON, mais dans le cas particulier de Pyruse, il faut savoir que les entrées de dictionnaire non utilisées sont ignorées, ce qui permet ceci par exemple :

          "Détecter les accès SSH en échec": [
            { "INFO": "COMMENCER PAR VÉRIFIER QUE ÇA VIENT DE SSH",
              "filter": "filter_equals",
              "args": { "field": "_SYSTEMD_UNIT", "value": "sshd.service" }
            },
            { "INFO": "PUIS QU’ON A BIEN UN MESSAGE D’ÉCHEC DE CONNEXION",
              "filter": "filter_pcreAny",
              "args": { "field": "MESSAGE", "re": [
                "^Failed password for (?P<utilisateur>.*) from (?P<adresseIP>[^ ]*) port",
                "^Invalid user (?P<utilisateur>.*) from (?P<adresseIP>[^ ]*) port",
                "^User (?P<utilisateur>.*) from (?P<adresseIP>[^ ]*) not allowed because not listed in AllowUsers$"
              ] }
            },
            { "INFO": "SI C’EST BIEN LE CAS, ON AUGMENTE LE COMPTEUR",
              "action": "action_counterRaise",
              "args": { "counter": "sshd", "for": "adresseIP", "keepSeconds": 300, "save": "nbÉchecs" }
            },
            { "INFO": "ET S’IL EST SUPÉRIEUR OU ÉGAL À 4",
              "filter": "filter_greaterOrEquals",
              "args": { "field": "nbÉchecs", "value": 4 },
              "else": "… Ça ira pour cette fois, circulez !"
            },
            { "INFO": "ALORS ON BANNIT L’ADRESSE IP POUR 24H",
              "action": "action_nftBan",
              "args": {
                "IP": "adresseIP", "banSeconds": 86400,
                "nftSetIPv4": "Inet4 sshd_ban", "nftSetIPv6": "Inet6 sshd_ban"
              }
            }
          ],
        • [^] # Re: YAML

          Posté par . Évalué à 1.

          Si c’est le avantage, je trouve TOML nettement plus intéressant !

      • [^] # Re: YAML

        Posté par . Évalué à 5.

        du json valide est du yaml valide ;)

        • [^] # Re: YAML

          Posté par . Évalué à 3.

          du json valide est du yaml 1.2 valide ;)

      • [^] # Re: YAML

        Posté par . Évalué à 3. Dernière modification le 15/02/18 à 10:47.

        python => yaml
        javascript => json

        Donc si tu code en python, les espaces du yaml ne sont pas un problème de plus.

        • [^] # Re: YAML

          Posté par . Évalué à 4.

          Je ne comprends pas les implications. Enfin, JavaScript vers JSON, oui, mais Python vers YAML, non.

          Python a un module json dans sa bibliothèque standard. Mais pas de YAML. Donc bon, c'est pas évident comme lien.

          • [^] # Re: YAML

            Posté par . Évalué à 5.

            Il faut lire "=>" comme "a inspiré".
            Mais bien sûr Python sait faire les deux puisque Python sait tout faire ;)

            Il faut aussi noter que Yaml a été fait pour être un peu plus "human readable" et donc un peu moins "machine proof" que Json. Donc à voir ce qui doit être privilégié dans le logiciel.

            • [^] # Re: YAML

              Posté par . Évalué à 3.

              Je trouve également plus lisible le yaml que le json. Et en pratique j'ai lu (mais pas assez pratiqué les deux) que json et yaml c plus ou moins a même chose, il ne serait probablement pas très compliqué de faire un truc qui gère les deux (mais je peux me tromper). Mais je comprends le choix initial, j'ai fait le même il y a 1 an et demi au taf : aujourd'hui je choisirais yaml.

              Maintenant, entre json et xml, ya pas photo, je préfère le json.

  • # Ban de 24h ?

    Posté par (page perso) . Évalué à 6.

    Vu ton choix de bannir pour 24h, je pense que tu ne t'es jamais fais bannir d'un de tes serveurs :)

    • [^] # Re: Ban de 24h ?

      Posté par . Évalué à 4.

      J'ai fini par faire ça aussi.
      Et un accès de secours avec Shell in a box. Sur une URL en HTTPS, avec un sous-chemin non indexé.
      Il est possible aussi de faire écouter le serveur SSH sur un port secondaire.

    • [^] # Re: Ban de 24h ?

      Posté par (page perso) . Évalué à 6.

      Non, en réalité, je n’utilise pas 24h sur mon serveur, mais davantage (de l’ordre de la semaine). Je me suis déjà fait bannir, il y a longtemps… Tout comme Glandos, j’avais un accès shell-in-a-box pour rétablir l’accès. Désormais, je ne m’embête plus avec ça : j’utilise Termux sur mon Fairphone => IP différente ;-) Je peux aussi rebondir par un accès SSH tiers…

      Au passage, oubliez l’idée de se protéger en changeant le port SSH : les scanners de ports trouvent le nouveau port sans difficulté, et je peux confirmer que ça n’empêche en rien les « attaques ». Bien sûr, sur un serveur bien configuré, ces attaques ont peu de chances de mener à quoi que ce soit…

    • [^] # Re: Ban de 24h ?

      Posté par . Évalué à 3.

      En même temps en utilisant des clés c'est compliqué de se bannir soit-même.

  • # Archlinux?

    Posté par . Évalué à 3.

    Je suis déçu tu n'as pas reparlé d'Archlinux ! Je suis moi même un fervent défenseur d'Archlinux, mais je n'irais pas l'installer sur un serveur de production d'auto-hébergement, rien que d'avoir à faire les mises à jour régulièrement à force c'est un peu la plaie…

    Sinon c'est un super projet et je vais me pencher dessus ayant l'envie de déployer un système équivalent sur mon serveur!

    • [^] # Re: Archlinux?

      Posté par (page perso) . Évalué à 8.

      Ça va venir :-) Il faut d’abord que je finalise certains points de ma configuration.

      En fait, le(s) serveur(s) est entièrement configuré avec Ansible. Du coup, régulièrement je repasse le « playbook » et au passage ça fait les mises à jour.

      En pratique, Archlinux sur un serveur, ça se passe plutôt bien. Avant cela, j’utilisais Debian.
      Mon expérience (sur serveur) est celle-ci :

      • Archlinux :
          − Il y a presque toujours un truc à gérer lors d’une mise à jour => hors de question de faire ça en automatique.
          + En contrepartie, les solutions aux problèmes qui se posent sont toujours simples et je sais que j’y arriverai toujours.
          + De plus, les logiciels sont toujours à jour et ça facilite beaucoup l’exploration de nouveaux usages.
          + Enfin, il est trivial d’empaqueter un nouveau logiciel, comme je l’ai fait d’ailleurs avec Pyruse ; donc pas d’étapes ./configure&&make install dans mon playbook Ansible.

      • Debian :
          + Les mises-à-jour se passent généralement sans histoire.
          − Par contre, quand quand ça dérape, il faut réussir à s’imprégner des particularismes Debian, ce qui n’est pas toujours facile…
          + Les mises à jour de sécurité sont faites de manière sérieuse, ce qui compense l’ancienneté des paquets.
          − Mais plus le temps passe, plus il devient compliqué, voire impossible dans certains cas, de tester certains logiciels.

      Tout est dans l’équilibre personnel recherché. Je suis parfaitement à l’aise avec la ligne de commande, et Archlinux convient mieux à mes objectifs. Mais il ne faut jamais être dogmatique : d’autres distributions peuvent mieux convenir à d’autres situations.

  • # Comment on fait pour l'installer ?

    Posté par . Évalué à 4.

    J'aimerais bien tester le logiciel mais je n'ai pas vu la documentation pour l'installer.

  • # Support des filtres & actions au "format fail2ban"

    Posté par . Évalué à 2. Dernière modification le 16/02/18 à 04:52.

    …pour offrir de l'ampleur au projet et permettre une bascule et du test aisés par les (nombreux) utilisateurs de fail2ban.

    • [^] # Re: Support des filtres & actions au "format fail2ban"

      Posté par (page perso) . Évalué à 4.

      Le format Fail2ban n’est pas vraiment adapté à la philosophie de mon programme.
      Cependant, ton idée a du mérite : elle enlèverait certainement un frein à l’utilisation de Pyruse…

      Il est probablement possible de faire un convertisseur, qui lit la configuration de fail2ban et la transforme en configuration pour Pyruse.
      Ce qui est dommage, c’est qu’un tel convertisseur ne serait sans doute pas capable de détecter les redondances et ainsi optimiser le fichier de configuration ; autrement dit : quel gain pour l’utilisateur ?

      Je prévois par ailleurs quelques difficultés, notamment le fait qu’avec un peu de volonté, n’importe quoi peut être fait dans la configuration de fail2ban (cf. l’article que j’ai mis en lien dans le corps de la dépêche) : il faut donc récupérer chaque extrait de code (shell) et l’appeler par un module « wrapper » fait pour appeler du shell.

      Bref, on va se retrouver avec un fichier de configuration non optimisé, qui n’utilise que des filtres et actions ad-hoc (aucun module standard), pour finalement exécuter du shell, et tout ça dans un mode opératoire nettement moins testé que la même configuration dans Fail2ban. Je ne suis pas optimiste…

  • # Botfreak

    Posté par (page perso) . Évalué à 1.

    Hello à tous,

    C'est marrant, je n'étais pas venu depuis des années sur linuxfr, et boom, direct sur la première page je tombe sur cette news qui m'interpelle.

    J'ai moi-même développé un truc très similaire. Je n'ai pas encore fait de release parce que je n'imagine pas que ça intéresse grand monde, c'est un modèle un peu plus compliqué.

    Les sources (mercurial, pas git !) : https://sources.freehackers.org/botfreak/

    Je résume, les points communs :
    * sert à la même chose
    * système générique de filtres de logs, regex, etc.
    * en python

    Les grosses différences :
    * ne se base pas sur systemd (je ne l'utilise pas, mais il serait facile d'adapter)
    * fonctionne en client-serveur, avec un serveur central (web), des "reporters" qui remonte les problèmes, et des "blockers" qui utilise ces informations pour bloquer (avec iptables). La communication se fait par API.
    * il y a aussi une interface web (Django) pour reporter 'à la main' des mauvais hosts/ips.

    Il y a aussi des concepts de 'whitelists' (je ne sais pas si pyruse a ça).

    Depuis très longtemps j'avais des scripts "maison" (fail2ban ne m'a jamais convaincu) sur tous mes serveurs, mais ce qui me gênait c'est qu'il n'y avait pas de partage d'information entre eux.

    Tel que c'est fait aujourd'hui, chaque utilisateur a un pool de serveur, et les informations sont partagées entre tous les serveurs d'un même utilisateur. Au démarrage d'un blocker, il télécharge l'historique jusqu'à un certain point dans le temps (configurable), et les tables iptables sont gardées en cache pour la performance.

  • # Nouveautés

    Posté par (page perso) . Évalué à 4.

    Salut à tous,

    Un grand merci à LinuxFR et aux Éditions ENI pour le livre que m’a apporté cet article.
    Pourquoi ce cadeau ? Parce que vous avez été nombreux à être intéressés par Pyruse (quelqu’un a même créé un ticket pour suggérer le support d’iptables, qui pourrait donc finalement faire son entrée ultérieurement).

    Du coup, vous serez sans doute intéressés de savoir qu’il y a des évolutions :

    • une procédure d’installation (dites-moi si vous rencontrez des problèmes avec) ;
    • le choix du niveau de détail (concernant les dates+heures) associé à chaque message du rapport quotidien ;
    • le filtre d’appartenance à des plages d’IP, ce qui permet de faire des « whitelist » et « blacklist » ;
    • l’action de log dans le journal systemd, qui permet la détection de récidive, avec autant de niveaux que souhaité.

    Et en ce moment, je travaille à l’ajout d’actions de gestion du DNAT (ou autre mécanisme de proxy). Vous avez sans doute déjà rencontré ce problème : vous avez des attaques (SSH, HTTP…) et elles proviennent toutes de « 127.0.0.1 » ou « 10.0.0.1 » (votre proxy)…

    La solution que je vais mettre en place fonctionne en 2 temps :

    1. Une action travaille sur les logs du pare-feu, ou de haproxy, pour associer 3 adresses+port : le client, le proxy, la destination.
    2. Une autre action permet de substituer des valeurs de variables en fonction de la correspondance établie à la première étape.

    Exemple :

    1. Le client 12.34.56.78:23456 se connecte à votre serveur 87.65.43.21:443 (haproxy), qui utilise la socket locale 10.0.0.1:12345 pour relayer l’appel en mode TCP à OpenSSH sur 10.0.0.2:22.
    2. Une chaîne d’exécution va :
      • extraire des logs haproxy les infos 12.34.56.78:23456, 10.0.0.1:12345, 10.0.0.2:22,
      • établir une correspondance en mémoire, d’une durée de vie limitée, entre ces données.
    3. Quelques entrées de log plus tard, une autre chaîne d’exécution va :
      • détecter une attaque SSH depuis 10.0.0.1:12345 vers 10.0.0.2:22,
      • activer le remplacement (si correspondance trouvée) de la source, qui va remplacer 10.0.0.1:12345 par 12.34.56.78:23456
      • agir sur la vraie adresse IP de l’attaquant.

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.