NOPE LinkedIn

Catégories:
Security
Crowdsec

Crowdsec - Introduction et création d'un parser

1. Introduction

1.1 Parser (Analyseur)

Un analyseur est un fichier de configuration YAML qui décrit comment une chaîne doit être analysée. Cette chaîne peut être une ligne de journal ou un champ extrait d’un analyseur précédent.

Alors que de nombreux analyseurs s’appuient sur l’ approche GROK (alias expression régulière nommée groupes de capture), les analyseurs peuvent également utiliser des expressions pour effectuer une analyse sur des données spécifiques (c’est-à-dire json), se référer à des méthodes externes pour l’enrichissement ou même effectuer une White Liste .

L’ événement entre dans l’analyseur et peut se terminer avec succès ou non :

Parser Diagram

1.2 Stages (Etapes)

Les analyseurs sont organisés en Stages(étapes) pour permettre les pipelines et les branchements dans l’analyse. Un événement peut passer à l’étape suivante si au moins un analyseur de l’étape donnée l’a analysé avec succès tout en ayant onsuccess défini sur next_stage. Sinon, l’événement est considéré comme non analysé et quittera le pipeline (et sera ignoré):

Parser Diagram

Chaque analyseur peut ajouter, modifier ou même supprimer des données de l’événement.

L’approche actuelle est la suivante :

  • s00-raw: prend en charge la structure globale du journal (c’est-à-dire extraire les lignes de journal du blob ‘JSON’, analyser les informations de protocole syslog)
  • s01-parse: analyse la ligne de journal réelle ( ‘ssh’ , ’nginx’ etc.)
  • s02-enrich: effectue un post-traitement, tel que l’enrichissement ‘géoip’ ou la post-analyse des événements ‘http’ pour fournir plus de contexte Une fois qu’un événement a quitté avec succès le pipeline d’analyse, il est prêt à être mis en correspondance avec des ‘scénarios’. Comme vous pouvez vous y attendre, chaque analyseur s’appuie sur les informations analysées au cours des étapes précédentes.

Une fois qu’un scénario déborde, l’événement résultant va être traité par un ensemble distinct d’analyseurs, appelés “postoverflows”.

Ces analyseurs sont situés dans /etc/crowdsec/postoverflows/ et contiennent généralement des listes blanches supplémentaires, un exemple courant consiste à mettre en liste blanche les décisions provenant de certains FQDN spécifiques .

Habituellement, ces parseurs doivent être réservés aux parseurs “coûteux” qui pourraient s’appuyer sur des services externes.

Voir le Hub pour explorer les analyseurs, ou voir ci-dessous quelques exemples :

Les analyseurs résident généralement dans /etc/crowdsec/parsers/<STAGE>/.

2. Création d’un Parser (Analyseur)

Cette partie suppose que vous essayez de créer un analyseur pour crowdsec dans le but de le soumettre au hub, et donc de créer les tests fonctionnels associés. La création de ces tests fonctionnels guidera notre démarche et la facilitera.

Nous allons créer un parseur pour le service imaginaire “myservice” qui produit trois types de logs via syslog:

Dec  8 06:28:43 mymachine myservice[2806]: bad password for user 'toto' from '1.2.3.4'
Dec  8 06:28:43 mymachine myservice[2806]: unknown user 'toto' from '1.2.3.4'
Dec  8 06:28:43 mymachine myservice[2806]: accepted connection for user 'toto' from '1.2.3.4'

Comme nous allons analyser ces journaux pour détecter davantage les attaques par force brute et par énumération d’utilisateurs, nous allons simplement “supprimer” le dernier type de journal

2.1 Schéma

Un schema yaml est disponible pour l’analyseur et lié à SchemaStore pour une disponibilité publique générale dans les éditeurs les plus courants. Vous pourrez voir si l’analyseur se conforme au schéma directement dans votre éditeur, et vous aurez une sorte de coloration syntaxique et des suggestions. La seule exigence pour cela est d’écrire votre analyseur en utilisant la structure de répertoires du hub pour que l’éditeur détecte que le fichier doit se conformer au schéma yaml.

2.1.1 Installation dans visual studiocode:

L’extension pour visual studiocode peut être installée directement à partir du market place ou via GitHub Dans Visual Studio Code, il faut ouvrir la pallete de commande en utilisant la combinaison de touches Ctrl+P, et y coller la commande suivante:

ext install remcohaszing.schemastore

Cela signifie que vous devrez écrire l’analyseur dans un sous-répertoire de:

  • parsers/s00-raw
  • parsers/s01-parse
  • parsers/s02-enrich
  • postoverflows/s00-enrich
  • postoverflows/s01-whitelist

Ce sous-répertoire porte votre nom ou du nom de votre organisation. À titre d’exemple parsers/s01-parse/crowdsecurity/sshd-logs.yaml correspond à cette structure de répertoire. Notez que l’extension de l’analyseur doit .yaml.

2.2 Pré-requis

2.2.1 Créer un environnement de test

Vous avez besoin d’un environnement de test pour plusieurs raisons :

  • Création de nouveaux analyseurs ou scénarios
  • Tester de nouvelles fonctionnalités ou en général
  • Présentez un bug ou un cas particulier

Créer un répertoire pour héberger notre environnement de test.

# mkdir crowdsec && cd crowdsec

Cela peut être fait directement avec l’archive tar de la dernière release :

VER=1.5.2 # Please check https://github.com/crowdsecurity/crowdsec/releases/latest for latest version
wget https://github.com/crowdsecurity/crowdsec/releases/download/v$VER/crowdsec-release.tgz
tar xvzf crowdsec-release.tgz
cd crowdsec-v$VER
./test_env.sh

On obtient la sortie suivante:

[07/27/2023:11:26:13 PM][INFO] Creating test arboresence in /root/crowdsec/crowdsec-v1.5.2/tests
[07/27/2023:11:26:13 PM][INFO] Arboresence created
[07/27/2023:11:26:13 PM][INFO] Copying needed files for tests environment
[07/27/2023:11:26:13 PM][INFO] Files copied
[07/27/2023:11:26:13 PM][INFO] Setting up configurations
WARN[27-07-2023 23:26:13] can't load CAPI credentials from './config/online_api_credentials.yaml' (missing field)
INFO[27-07-2023 23:26:13] push and pull to Central API disabled
WARN[27-07-2023 23:26:13] You are using sqlite without WAL, this can have a performance impact. If you do not store the database in a network share, set db_config.use_wal to true. Set explicitly to false to disable this warning.
WARN[27-07-2023 23:26:13] can't load CAPI credentials from './config/online_api_credentials.yaml' (missing field)
INFO[27-07-2023 23:26:13] push and pull to Central API disabled
INFO[27-07-2023 23:26:13] Machine 'test' successfully added to the local API
INFO[27-07-2023 23:26:13] API credentials dumped to '/root/crowdsec/crowdsec-v1.5.2/tests/config/local_api_credentials.yaml'
INFO[27-07-2023 23:26:14] Wrote new 794587 bytes index to /root/crowdsec/crowdsec-v1.5.2/tests/config/hub/.index.json
INFO[27-07-2023 23:26:14] crowdsecurity/syslog-logs : OK
INFO[27-07-2023 23:26:14] Enabled parsers : crowdsecurity/syslog-logs
INFO[27-07-2023 23:26:14] crowdsecurity/geoip-enrich : OK
INFO[27-07-2023 23:26:14] downloading data 'https://crowdsec-statics-assets.s3-eu-west-1.amazonaws.com/GeoLite2-City.mmdb' in '/root/crowdsec/crowdsec-v1.5.2/tests/data/GeoLite2-City.mmdb'
INFO[27-07-2023 23:26:16] downloading data 'https://crowdsec-statics-assets.s3-eu-west-1.amazonaws.com/GeoLite2-ASN.mmdb' in '/root/crowdsec/crowdsec-v1.5.2/tests/data/GeoLite2-ASN.mmdb'
INFO[27-07-2023 23:26:16] Enabled parsers : crowdsecurity/geoip-enrich
INFO[27-07-2023 23:26:16] crowdsecurity/dateparse-enrich : OK
INFO[27-07-2023 23:26:16] Enabled parsers : crowdsecurity/dateparse-enrich
INFO[27-07-2023 23:26:16] crowdsecurity/sshd-logs : OK
INFO[27-07-2023 23:26:16] Enabled parsers : crowdsecurity/sshd-logs
INFO[27-07-2023 23:26:16] crowdsecurity/ssh-bf : OK
INFO[27-07-2023 23:26:16] Enabled scenarios : crowdsecurity/ssh-bf
INFO[27-07-2023 23:26:16] crowdsecurity/ssh-slow-bf : OK
INFO[27-07-2023 23:26:16] Enabled scenarios : crowdsecurity/ssh-slow-bf
INFO[27-07-2023 23:26:16] crowdsecurity/sshd : OK
WARN[27-07-2023 23:26:16] crowdsecurity/sshd : overwrite
INFO[27-07-2023 23:26:16] /root/crowdsec/crowdsec-v1.5.2/tests/config/collections doesn't exist, create
INFO[27-07-2023 23:26:16] Enabled collections : crowdsecurity/sshd
INFO[27-07-2023 23:26:16] crowdsecurity/linux : OK
INFO[27-07-2023 23:26:16] /root/crowdsec/crowdsec-v1.5.2/tests/config/collections/sshd.yaml already exists.
INFO[27-07-2023 23:26:16] Enabled collections : crowdsecurity/linux
INFO[27-07-2023 23:26:16] Enabled crowdsecurity/linux
INFO[27-07-2023 23:26:16] Run 'sudo systemctl reload crowdsec' for the new configuration to be effective.
[07/27/2023:11:26:16 PM][INFO] Environment is ready in /root/crowdsec/crowdsec-v1.5.2/tests

Une arborescence de répertoire va être créé:

# tree -AC -d -L 3
.
├── cmd
│   ├── crowdsec
│   └── crowdsec-cli
├── config
│   └── patterns
├── plugins
│   └── notifications
│       ├── email
│       ├── http
│       ├── slack
│       └── splunk
└── tests
    ├── config
    │   ├── collections
    │   ├── crowdsec-cli
    │   ├── hub
    │   ├── notifications
    │   ├── parsers
    │   ├── patterns
    │   ├── postoverflows
    │   └── scenarios
    ├── data
    ├── hub
    │   ├── assets
    │   ├── blockers
    │   ├── collections
    │   ├── hub-tests
    │   ├── parsers
    │   ├── postoverflows
    │   └── scenarios
    ├── logs
    └── plugins

L’environnement de test est disponible dans le dossier tests et fournit un environnement CrowdSec fonctionnel :

cd tests
./crowdsec -c dev.yaml

cscli doit aussi être fonctionnel :

cd tests
./cscli -c dev.yaml hub list
# ./cscli -c dev.yaml hub list
INFO[27-07-2023 23:29:20] Loaded 98 collecs, 106 parsers, 183 scenarios, 7 post-overflow parsers

COLLECTIONS
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Name                  📦 Status   Version   Local Path
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 crowdsecurity/linux   ✔️ enabled   0.2       /root/crowdsec/crowdsec-v1.5.2/tests/config/collections/linux.yaml
 crowdsecurity/sshd    ✔️ enabled   0.2       /root/crowdsec/crowdsec-v1.5.2/tests/config/collections/sshd.yaml
────────────────────────────────────────────────────────────────────────────────────────────────────────────────

PARSERS
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Name                             📦 Status   Version   Local Path
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 crowdsecurity/dateparse-enrich   ✔️ enabled   0.2       /root/crowdsec/crowdsec-v1.5.2/tests/config/parsers/s02-enrich/dateparse-enrich.yaml
 crowdsecurity/geoip-enrich       ✔️ enabled   0.2       /root/crowdsec/crowdsec-v1.5.2/tests/config/parsers/s02-enrich/geoip-enrich.yaml
 crowdsecurity/sshd-logs          ✔️ enabled   2.2       /root/crowdsec/crowdsec-v1.5.2/tests/config/parsers/s01-parse/sshd-logs.yaml
 crowdsecurity/syslog-logs        ✔️ enabled   0.8       /root/crowdsec/crowdsec-v1.5.2/tests/config/parsers/s00-raw/syslog-logs.yaml
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

SCENARIOS
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Name                        📦 Status   Version   Local Path
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 crowdsecurity/ssh-bf        ✔️ enabled   0.1       /root/crowdsec/crowdsec-v1.5.2/tests/config/scenarios/ssh-bf.yaml
 crowdsecurity/ssh-slow-bf   ✔️ enabled   0.2       /root/crowdsec/crowdsec-v1.5.2/tests/config/scenarios/ssh-slow-bf.yaml
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

POSTOVERFLOWS
─────────────────────────────────────────
 Name   📦 Status   Version   Local Path
─────────────────────────────────────────

Dans l’environnement de test, les configurations se trouvent dans le dossier config/.

Ajouter un référentiel hub à l’envireonnement

Comme indiqué dans la documentation, cette étape est nécessaire seulement si on veut développer des analyseurs et des scénarios. Ce qui est notre cas.

# cd tests # if you are already in tests no need to cd
~/crowdsec/crowdsec-v1.5.2/tests # git clone https://github.com/crowdsecurity/hub.git
~/crowdsec/crowdsec-v1.5.2/tests # cd hub

Nous venons de cloner le référentiel hub, nous pouvons maintenant exécuter cscli, par exemple :

~/crowdsec/crowdsec-v1.5.2/tests/hub # ../cscli -c ../dev.yaml hubtest run --all

Cette commande va exécuter tous les tests du référentiel contenu dans le hub. Le ../ devant cscli et dev.yaml c’est tout simplement car ils se trouvent dans le dossier au-dessus de notre répertoire de travail actuel.

Une astuce utile pour vous éviter de taper toute la commande à chaque fois est de définir un alias dans votre shell :

alias csdev="$(dirname $PWD)/cscli -c $(dirname $PWD)/dev.yaml"

Ensuite, vous pouvez exécuter la commande en tant que csdev

csdev hubtest run --all

Cependant, il s’agit d’un alias temporaire défini dans votre session. Si vous vous déconnecté et reconnécté, il aura disparu. Il faut donc l’ajouter à votre .bashrc ou à votre équivalent shell.

2.3 Créer notre environnement de tests

Depuis la racine du référentiel hub :

▶ cscli hubtest create myservice-logs --type syslog

  Test name                   :  myservice-logs
  Test path                   :  /home/dev/github/hub/.tests/myservice-logs
  Log file                    :  /home/dev/github/hub/.tests/myservice-logs/myservice-logs.log (please fill it with logs)
  Parser assertion file       :  /home/dev/github/hub/.tests/myservice-logs/parser.assert (please fill it with assertion)
  Scenario assertion file     :  /home/dev/github/hub/.tests/myservice-logs/scenario.assert (please fill it with assertion)
  Configuration File          :  /home/dev/github/hub/.tests/myservice-logs/config.yaml (please fill it with parsers, scenarios...)

2.4 Configurez notre environnement de tests

Ajoutons notre analyseur à la configuration de test (.tests/myservice-logs/config.yaml). Il précise que nous avons besoin de l’analyseur syslog-logs (car les journaux myservice sont expédiés via syslog), puis de notre analyseur personnalisé. La ligne surlignée peut être supprimée pour notre exemple.

parsers:
- crowdsecurity/syslog-logs
- crowdsecurity/dateparse-enrich
- ./parsers/s01-parse/crowdsecurity/myservice-logs.yaml
scenarios: 
- ""
postoverflows:
- ""
log_file: myservice-logs.log
log_type: syslog
labels: {}
ignore_parsers: false
override_statics: []

Note:: comme notre analyseur personnalisé ne fait pas encore partie du hub, nous spécifions son chemin par rapport à la racine du répertoire du hub. Donc le fichier myservice-logs.yaml devra être créé dans le répertoire /crowdsec/crowdsec-v1.5.2/tests/hub/parsers/s01-parse/crowdsecurity

2.5 Création de notre parser (Analyseur) Squelette

Pour les besoins du tutoriel, créons un analyseur très simple:

filter: 1 == 1
debug: true
onsuccess: next_stage
name: crowdsecurity/myservice-logs
description: "Parse myservice logs"
grok:
#our grok pattern : capture .*
  pattern: ^%{DATA:some_data}$
#the field to which we apply the grok pattern : the log message itself
  apply_on: message
statics:
  - parsed: is_my_service
    value: yes
  • filter : si l’expression est true, l’événement entrera dans l’analyseur, sinon, il ne le sera pas.
  • onsuccess: définit ce qui se passe lorsque l’événement a été analysé avec succès : allons-nous continuer ? allons-nous passer à l’étape suivante ? etc.
  • name: un nom
  • *description: Une description du parser
  • debug: un drapeau qui permet d’activer les informations de débogage local
  • grok: un modèle pour capturer certaines données dans les journaux

Il faut s’assurer de ne pas avoir oublié de saisir les lignes de log dans le fichier ~/crowdsec/crowdsec-v1.5.2/tests/hub/.tests/myservice-logs/myservice-logs.log:

Dec  8 06:28:43 mymachine myservice[2806]: bad password for user 'toto' from '1.2.3.4'
Dec  8 06:28:43 mymachine myservice[2806]: unknown user 'toto' from '1.2.3.4'
Dec  8 06:28:43 mymachine myservice[2806]: accepted connection for user 'toto' from '1.2.3.4'

On peut alors “tester” notre analyseur:

cd ../../
▶ ~/crowdsec/crowdsec-v1.5.2/tests/hub #cscli hubtest run myservice-logs
INFO[28-07-2023 11:44:21] Running test 'myservice-logs'
WARN[28-07-2023 11:44:25] Assert file '/root/crowdsec/crowdsec-v1.5.2/tests/hub/.tests/myservice-logs/parser.assert' is empty, generating assertion:

len(results) == 4
len(results["s00-raw"]["crowdsecurity/syslog-logs"]) == 3
results["s00-raw"]["crowdsecurity/syslog-logs"][0].Success == true
...
len(results["s01-parse"]["crowdsecurity/myservice-logs"]) == 3
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Success == true
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["logsource"] == "syslog"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["message"] == "bad password for user 'toto' from '1.2.3.4'"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["pid"] == "2806"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["program"] == "myservice"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["some_data"] == "bad password for user 'toto' from '1.2.3.4'"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["timestamp"] == "Dec  8 06:28:43"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["is_my_service"] == "yes"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Meta["machine"] == "mymachine"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Meta["datasource_path"] == "myservice-logs.log"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Meta["datasource_type"] == "file"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Success == true
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Parsed["is_my_service"] == "yes"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Parsed["logsource"] == "syslog"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Parsed["timestamp"] == "Dec  8 06:28:43"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Parsed["message"] == "unknown user 'toto' from '1.2.3.4'"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Parsed["pid"] == "2806"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Parsed["program"] == "myservice"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Parsed["some_data"] == "unknown user 'toto' from '1.2.3.4'"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Meta["datasource_path"] == "myservice-logs.log"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Meta["datasource_type"] == "file"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Meta["machine"] == "mymachine"
results["s01-parse"]["crowdsecurity/myservice-logs"][2].Success == true
results["s01-parse"]["crowdsecurity/myservice-logs"][2].Evt.Parsed["timestamp"] == "Dec  8 06:28:43"
results["s01-parse"]["crowdsecurity/myservice-logs"][2].Evt.Parsed["logsource"] == "syslog"
results["s01-parse"]["crowdsecurity/myservice-logs"][2].Evt.Parsed["message"] == "accepted connection for user 'toto' from '1.2.3.4'"
results["s01-parse"]["crowdsecurity/myservice-logs"][2].Evt.Parsed["pid"] == "2806"
results["s01-parse"]["crowdsecurity/myservice-logs"][2].Evt.Parsed["some_data"] == "accepted connection for user 'toto' from '1.2.3.4'"
results["s01-parse"]["crowdsecurity/myservice-logs"][2].Evt.Parsed["is_my_service"] == "yes"
results["s01-parse"]["crowdsecurity/myservice-logs"][2].Evt.Parsed["program"] == "myservice"
results["s01-parse"]["crowdsecurity/myservice-logs"][2].Evt.Meta["datasource_path"] == "myservice-logs.log"
results["s01-parse"]["crowdsecurity/myservice-logs"][2].Evt.Meta["datasource_type"] == "file"
results["s01-parse"]["crowdsecurity/myservice-logs"][2].Evt.Meta["machine"] == "mymachine"
len(results["s02-enrich"]["crowdsecurity/dateparse-enrich"]) == 3
...
results["s02-enrich"]["crowdsecurity/dateparse-enrich"][2].Evt.Meta["machine"] == "mymachine"
results["s02-enrich"]["crowdsecurity/dateparse-enrich"][2].Evt.Enriched["MarshaledTime"] == "2023-12-08T06:28:43Z"
len(results["success"][""]) == 0


Please fill your assert file(s) for test 'myservice-logs', exiting

Que s’est-il passé ici ?

  • Nos journaux ont été traités par l’analyseur syslog-logs et notre analyseur personnalisé
  • Comme nous n’avons pas d’assertion(s) existante(s), cscli hubtest va en générer pour nous

Cela nous permet surtout de nous assurer que nos logs ont bien été traités par notre analyseur, même s’il est inutile dans son état actuel. Une inspection plus approfondie peut être vue avec cscli hubtest explain:

~/crowdsec/crowdsec-v1.5.2/tests/hub # cscli hubtest explain myservice-logs
line: Dec  8 06:28:43 mymachine myservice[2806]: bad password for user 'toto' from '1.2.3.4'
        ├ s00-raw
        |       └ 🟢 crowdsecurity/syslog-logs (+12 ~9)
        ├ s01-parse
        |       └ 🟢 crowdsecurity/myservice-logs (+2 ~1)
        ├ s02-enrich
        |       └ 🟢 crowdsecurity/dateparse-enrich (+2 ~2)
        ├-------- parser success 🟢
        ├ Scenarios

line: Dec  8 06:28:43 mymachine myservice[2806]: unknown user 'toto' from '1.2.3.4'
        ├ s00-raw
        |       └ 🟢 crowdsecurity/syslog-logs (+12 ~9)
        ├ s01-parse
        |       └ 🟢 crowdsecurity/myservice-logs (+2 ~1)
        ├ s02-enrich
        |       └ 🟢 crowdsecurity/dateparse-enrich (+2 ~2)
        ├-------- parser success 🟢
        ├ Scenarios

line: Dec  8 06:28:43 mymachine myservice[2806]: accepted connection for user 'toto' from '1.2.3.4'
        ├ s00-raw
        |       └ 🟢 crowdsecurity/syslog-logs (+12 ~9)
        ├ s01-parse
        |       └ 🟢 crowdsecurity/myservice-logs (+2 ~1)
        ├ s02-enrich
        |       └ 🟢 crowdsecurity/dateparse-enrich (+2 ~2)
        ├-------- parser success 🟢
        ├ Scenarios

Nous pouvons voir que nos lignes de journal ont été analysées avec succès par les analyseurs syslog-logs et myservice-logs.

2.6 Création de l’analyseur:

Modifions notre analyseur ./parsers/s01-parse/crowdsecurity/myservice-logs.yaml:

onsuccess: next_stage
filter: "evt.Parsed.program == 'myservice'"
name: crowdsecurity/myservice-logs
description: "Parse myservice logs"
#for clarity, we create our pattern syntax beforehand
pattern_syntax:
  MYSERVICE_BADPASSWORD: bad password for user '%{USERNAME:user}' from '%{IP:source_ip}' #[1]
  MYSERVICE_BADUSER: unknown user '%{USERNAME:user}' from '%{IP:source_ip}' #[1]
nodes:
#and we use them to parse our two type of logs
  - grok:
      name: "MYSERVICE_BADPASSWORD" #[2]
      apply_on: message
      statics:
        - meta: log_type #[3]
          value: myservice_failed_auth
        - meta: log_subtype
          value: myservice_bad_password
  - grok:
      name: "MYSERVICE_BADUSER" #[2]
      apply_on: message
      statics:
        - meta: log_type #[3]
          value: myservice_failed_auth
        - meta: log_subtype
          value: myservice_bad_user
statics:
    - meta: service #[3]
      value: myservice
    - meta: username
      expression: evt.Parsed.user
    - meta: source_ip #[1]
      expression: "evt.Parsed.source_ip"

Diverses modifications ont été apportées ici :

  • Nous avons créé des modèles pour capturer les deux types de lignes de journal pertinents, à l’aide d’un débogueur grok en ligne ou d’un débogueur regex en ligne [2] )
  • Nous gardons une trace du nom d’utilisateur et de la source_ip (Veuillez noter que la définition de la source_ip dans evt.Meta.source_ip et evt.Parsed.source_ip est importante [1] )
  • Nous configurons diverses informations statiques pour classer le type de journal [3] Recommençons les tests :
INFO[28-07-2023 11:50:40] Running test 'myservice-logs'
WARN[28-07-2023 11:50:44] Assert file '/root/crowdsec/crowdsec-v1.5.2/tests/hub/.tests/myservice-logs/parser.assert' is empty, generating assertion:

len(results) == 4
len(results["s00-raw"]["crowdsecurity/syslog-logs"]) == 3
results["s00-raw"]["crowdsecurity/syslog-logs"][0].Success == true
results["s00-raw"]["crowdsecurity/syslog-logs"][0].Evt.Parsed["message"] == "bad password for user 'toto' from '1.2.3.4'"
...
results["s00-raw"]["crowdsecurity/syslog-logs"][2].Evt.Meta["machine"] == "mymachine"
results["s00-raw"]["crowdsecurity/syslog-logs"][2].Evt.Meta["datasource_path"] == "myservice-logs.log"
len(results["s01-parse"]["crowdsecurity/myservice-logs"]) == 3
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Success == true
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["program"] == "myservice"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["source_ip"] == "1.2.3.4"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["logsource"] == "syslog"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["message"] == "bad password for user 'toto' from '1.2.3.4'"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["pid"] == "2806"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["timestamp"] == "Dec  8 06:28:43"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["user"] == "toto"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Meta["service"] == "myservice"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Meta["source_ip"] == "1.2.3.4"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Meta["username"] == "toto"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Meta["datasource_path"] == "myservice-logs.log"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Meta["datasource_type"] == "file"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Meta["log_subtype"] == "myservice_bad_password"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Meta["log_type"] == "myservice_failed_auth"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Meta["machine"] == "mymachine"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Success == true
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Parsed["logsource"] == "syslog"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Parsed["source_ip"] == "1.2.3.4"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Parsed["message"] == "unknown user 'toto' from '1.2.3.4'"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Parsed["pid"] == "2806"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Parsed["program"] == "myservice"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Parsed["timestamp"] == "Dec  8 06:28:43"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Parsed["user"] == "toto"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Meta["service"] == "myservice"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Meta["source_ip"] == "1.2.3.4"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Meta["username"] == "toto"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Meta["datasource_path"] == "myservice-logs.log"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Meta["datasource_type"] == "file"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Meta["log_subtype"] == "myservice_bad_user"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Meta["log_type"] == "myservice_failed_auth"
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Meta["machine"] == "mymachine"
results["s01-parse"]["crowdsecurity/myservice-logs"][2].Success == false
len(results["s02-enrich"]["crowdsecurity/dateparse-enrich"]) == 2
results["s02-enrich"]["crowdsecurity/dateparse-enrich"][0].Success == true
results["s02-enrich"]["crowdsecurity/dateparse-enrich"][0].Evt.Parsed["pid"] == "2806"
...
results["s02-enrich"]["crowdsecurity/dateparse-enrich"][1].Evt.Meta["timestamp"] == "2023-12-08T06:28:43Z"
results["s02-enrich"]["crowdsecurity/dateparse-enrich"][1].Evt.Enriched["MarshaledTime"] == "2023-12-08T06:28:43Z"
len(results["success"][""]) == 0

Please fill your assert file(s) for test 'myservice-logs', exiting

Nous pouvons voir que notre analyseur a capturé toutes les informations pertinentes, et cela devrait être suffisant pour créer des scénarios plus tard. Encore une fois, une inspection plus approfondie avec cscli hubtest explain nous en dira plus sur ce qui s’est passé :

▶ ~/crowdsec/crowdsec-v1.5.2/tests/hub # cscli hubtest explain myservice-logs
line: Dec  8 06:28:43 mymachine myservice[2806]: bad password for user 'toto' from '1.2.3.4'
        ├ s00-raw
        |       └ 🟢 crowdsecurity/syslog-logs (+12 ~9)
        ├ s01-parse
        |       └ 🟢 crowdsecurity/myservice-logs (+7 ~1)
        ├ s02-enrich
        |       └ 🟢 crowdsecurity/dateparse-enrich (+2 ~2)
        ├-------- parser success 🟢
        ├ Scenarios

line: Dec  8 06:28:43 mymachine myservice[2806]: unknown user 'toto' from '1.2.3.4'
        ├ s00-raw
        |       └ 🟢 crowdsecurity/syslog-logs (+12 ~9)
        ├ s01-parse
        |       └ 🟢 crowdsecurity/myservice-logs (+7 ~1)
        ├ s02-enrich
        |       └ 🟢 crowdsecurity/dateparse-enrich (+2 ~2)
        ├-------- parser success 🟢
        ├ Scenarios

line: Dec  8 06:28:43 mymachine myservice[2806]: accepted connection for user 'toto' from '1.2.3.4'
        ├ s00-raw
        |       └ 🟢 crowdsecurity/syslog-logs (+12 ~9)
        ├ s01-parse
        └-------- parser failure 🔴

note: nous pouvons voir que notre ligne de journal accepted connection for user ’toto’ from ‘1.2.3.4’ n’a pas été analysée car nous n’avons pas de modèle pour cela Une modification possible pour analyser cette ligne serait de changer le fichier parsers/s01-parse/crowdsecurity/myservice-logs.yaml en y ajoutant les lignes surlignées:

onsuccess: next_stage
filter: "evt.Parsed.program == 'myservice'"
name: crowdsecurity/myservice-logs
description: "Parse myservice logs"
#for clarity, we create our pattern syntax beforehand
pattern_syntax:
  MYSERVICE_BADPASSWORD: bad password for user '%{USERNAME:user}' from '%{IP:source_ip}' #[1]
  MYSERVICE_BADUSER: unknown user '%{USERNAME:user}' from '%{IP:source_ip}' #[1]
  MYSERVICE_GOODUSER: accepted connection for user '%{USERNAME:user}' from '%{IP:source_ip}' #[1]
nodes:
#and we use them to parse our two type of logs
  - grok:
      name: "MYSERVICE_BADPASSWORD" #[2]
      apply_on: message
      statics:
        - meta: log_type #[3]
          value: myservice_failed_auth
        - meta: log_subtype
          value: myservice_bad_password
  - grok:
      name: "MYSERVICE_BADUSER" #[2]
      apply_on: message
      statics:
        - meta: log_type #[3]
          value: myservice_failed_auth
        - meta: log_subtype
          value: myservice_bad_user
  - grok:
      name: "MYSERVICE_GOODUSER" #[2]
      apply_on: message
      statics:
        - meta: log_type #[3]
          value: myservice_success_auth
        - meta: log_subtype
          value: myservice_good_user
statics:
    - meta: service #[3]
      value: myservice
    - meta: username
      expression: evt.Parsed.user
    - meta: source_ip #[1]
      expression: "evt.Parsed.source_ip"

On obtient alors le résultat suivant:

~/crowdsec/crowdsec-v1.5.2/tests/hub # cscli hubtest explain myservice-logs
line: Dec  8 06:28:43 mymachine myservice[2806]: accepted connection for user 'toto' from '1.2.3.4'
        ├ s00-raw
        |       └ 🟢 crowdsecurity/syslog-logs (+12 ~9)
        ├ s01-parse
        |       └ 🟢 crowdsecurity/myservice-logs (+7 ~1)
        ├ s02-enrich
        |       └ 🟢 crowdsecurity/dateparse-enrich (+2 ~2)
        ├-------- parser success 🟢
        ├ Scenarios

line: Dec  8 06:28:43 mymachine myservice[2806]: bad password for user 'toto' from '1.2.3.4'
        ├ s00-raw
        |       └ 🟢 crowdsecurity/syslog-logs (+12 ~9)
        ├ s01-parse
        |       └ 🟢 crowdsecurity/myservice-logs (+7 ~1)
        ├ s02-enrich
        |       └ 🟢 crowdsecurity/dateparse-enrich (+2 ~2)
        ├-------- parser success 🟢
        ├ Scenarios

line: Dec  8 06:28:43 mymachine myservice[2806]: unknown user 'toto' from '1.2.3.4'
        ├ s00-raw
        |       └ 🟢 crowdsecurity/syslog-logs (+12 ~9)
        ├ s01-parse
        |       └ 🟢 crowdsecurity/myservice-logs (+7 ~1)
        ├ s02-enrich
        |       └ 🟢 crowdsecurity/dateparse-enrich (+2 ~2)
        ├-------- parser success 🟢
        ├ Scenarios

3 Conclusion

Nous avons maintenant un analyseur entièrement fonctionnel pour les journaux de myservice ! Nous pouvons soit le déployer sur nos systèmes de production pour faire des choses, soit mieux encore, contribuer au hub !

Références