创建 CrowdSec 解析器

前言

本文假设你要为 CrowdSec 创建解析器,并计划提交到 Hub,因此也会创建相应的功能测试。功能测试将指导整个过程,让开发更容易。

这里为虚构服务 myservice 创建解析器。它通过 syslog 产生三种日志:

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

我们将解析日志,以便后续检测暴力破解和用户枚举攻击,因此会直接忽略最后一种日志。

解析器有对应的 YAML schema,并通过 SchemaStore 提供给常用编辑器。编辑器能够检查解析器是否符合 schema,提供语法高亮与建议。为让编辑器正确识别,文件必须遵循 Hub 目录结构:位于 parsers/s00-raw、parsers/s01-parse、parsers/s02-enrich、postoverflows/s00-enrich 或 postoverflows/s01-whitelist 的某个子目录中,子目录以个人或组织名称命名。例如 parsers/s01-parse/crowdsecurity/sshd-logs.yaml。文件扩展名必须是 .yaml。 yaml schema
available
SchemaStore

也支持鼠标悬停说明:

创建 CrowdSec 解析器:原文配图 1

文件不符合 schema 时,会检测出错误:

创建 CrowdSec 解析器:原文配图 2

错误消息有助于理解解析器文件为何不符合 schema:

创建 CrowdSec 解析器:原文配图 3

前提条件

  1. 创建本地测试环境。 Create a local test environment

  2. 克隆 Hub。

SH
git clone https://github.com/crowdsecurity/hub.git

创建测试

在 Hub 仓库根目录中执行:

SH
▶ 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...)

配置测试

将解析器加入测试配置 .tests/myservice-logs/config.yaml。先指定需要 syslog-logs 解析器,因为 myservice 日志经 syslog 传输,再指定自定义解析器。

YAML
parsers:
    - crowdsecurity/syslog-logs
    - ./parsers/s01-parse/crowdsecurity/myservice-logs.yaml
scenarios:
postoverflows:
log_file: myservice-logs.log
log_type: syslog
ignore_parsers: false

注意:自定义解析器尚未加入 Hub,因此使用相对于 Hub 根目录的路径。

创建解析器:骨架

先为教程创建一个非常简单的解析器:

YAML
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:表达式为 true 时,事件进入解析器;否则不进入。 filter
  • onsuccess:定义成功解析事件后如何处理,例如继续解析,或进入下一阶段。 onsuccess
  • name 和 description:名称与描述。
  • statics:用于修改事件的静态字段。 statics
  • debug:启用本地调试信息。
  • grok 模式:从日志中捕获数据。

然后可以这样测试解析器:

SH
▶ cscli hubtest run myservice-logs
INFO[01-10-2021 12:41:21 PM] Running test 'myservice-logs'
WARN[01-10-2021 12:41:24 PM] Assert file '/home/dev/github/hub/.tests/myservice-logs/parser.assert' is empty, generating assertion:

len(results) == 2
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["program"] == "myservice"
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.Parsed["logsource"] == "syslog"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["message"] == "bad password for user 'toto' from '192.168.1.1'"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["some_data"] == "bad password for user 'toto' from '192.168.1.1'"
...


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

这里发生了什么?

  • 日志经过 syslog-logs 和自定义解析器处理。
  • 由于尚无断言,cscli hubtest 自动生成了一些断言。

这主要让我们确认日志确实经过了自己的解析器,虽然当前解析器还没有实际用途。使用 cscli hubtest explain 可以进一步检查:

SH
▶ cscli hubtest explain myservice-logs
line: Dec  8 06:28:43 mymachine myservice[2806]: bad password for user 'toto' from '192.168.1.1'
	├ s00-raw
	|	└ 🟢 crowdsecurity/syslog-logs
	└ s01-parse
		└ 🟢 crowdsecurity/myservice-logs

line: Dec  8 06:28:43 mymachine myservice[2806]: unknown user 'toto' from '192.168.1.1'
	├ s00-raw
	|	└ 🟢 crowdsecurity/syslog-logs
	└ s01-parse
		└ 🟢 crowdsecurity/myservice-logs

line: Dec  8 06:28:43 mymachine myservice[2806]: accepted connection for user 'toto' from '192.168.1.1'
	├ s00-raw
	|	└ 🟢 crowdsecurity/syslog-logs
	└ s01-parse
		└ 🟢 crowdsecurity/myservice-logs

可以看到,日志行成功经过了 syslog-logs 和 myservice-logs 两个解析器。

创建解析器:实际解析逻辑

修改 ./parsers/s01-parse/crowdsecurity/myservice-logs.yaml:

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"

这里进行了若干修改:

  • 创建了两个模式,用于捕获两类相关日志,借助在线 Grok 调试器或正则表达式调试器完成,见代码标注[2]。在线调试器只认识标准 Grok 模式,因此仍需像下文一样,用 CrowdSec 自身引擎的 cscli hubtest explain 核对结果。 online grok debugger online regex debugger
  • 记录 username 和 source_ip。必须同时设置 evt.Meta.source_ip 和 evt.Parsed.source_ip,见代码标注[1]。
  • 设置若干 statics 字段,对日志类型分类,见标注[3]。 statics

再次运行测试:

SH
▶ cscli hubtest run myservice-logs
INFO[01-10-2021 12:49:56 PM] Running test 'myservice-logs'
WARN[01-10-2021 12:49:59 PM] Assert file '/home/dev/github/hub/.tests/myservice-logs/parser.assert' is empty, generating assertion:

len(results) == 2
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["timestamp"] == "Dec  8 06:28:43"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["program"] == "myservice"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["source_ip"] == "192.168.1.1"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Parsed["user"] == "toto"
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["service"] == "myservice"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Meta["source_ip"] == "192.168.1.1"
results["s01-parse"]["crowdsecurity/myservice-logs"][0].Evt.Meta["username"] == "toto"
...
results["s01-parse"]["crowdsecurity/myservice-logs"][1].Evt.Meta["log_subtype"] == "myservice_bad_user"
results["s01-parse"]["crowdsecurity/myservice-logs"][2].Success == false


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

现在解析器捕获了所有相关信息,足以支持后续创建场景。

再次使用 cscli hubtest explain,可以进一步了解处理过程:

SH
▶ cscli hubtest explain myservice-logs
line: Dec  8 06:28:43 mymachine myservice[2806]: bad password for user 'toto' from '192.168.1.1'
	├ s00-raw
	|	└ 🟢 crowdsecurity/syslog-logs
	└ s01-parse
		└ 🟢 crowdsecurity/myservice-logs

line: Dec  8 06:28:43 mymachine myservice[2806]: unknown user 'toto' from '192.168.1.1'
	├ s00-raw
	|	└ 🟢 crowdsecurity/syslog-logs
	└ s01-parse
		└ 🟢 crowdsecurity/myservice-logs

line: Dec  8 06:28:43 mymachine myservice[2806]: accepted connection for user 'toto' from '192.168.1.1'
	├ s00-raw
	|	└ 🟢 crowdsecurity/syslog-logs
	└ s01-parse
		└ 🔴 crowdsecurity/myservice-logs

注意:accepted connection for user ‘toto’ from ‘192.168.1.1’ 这条日志没有被 crowdsecurity/myservice-logs 解析,因为没有为它定义模式。

结语

现在已经有一个能够处理 myservice 日志的完整解析器。可以将其部署到生产系统使用,也可以进一步贡献到 Hub。

有关指令与更多功能,参见解析器参考文档。 the parser reference documentation

也可以阅读相关博客文章。 this blog article


更多学习方式

创建 CrowdSec 解析器:原文配图 4

观看介绍如何创建解析器及场景的短视频系列。

Learn with CrowdSec Academy


© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容