前言
本文假设你要为 CrowdSec 创建解析器,并计划提交到 Hub,因此也会创建相应的功能测试。功能测试将指导整个过程,让开发更容易。
这里为虚构服务 myservice 创建解析器。它通过 syslog 产生三种日志:
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
也支持鼠标悬停说明:

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

错误消息有助于理解解析器文件为何不符合 schema:
前提条件
-
创建本地测试环境。 Create a local test environment
-
克隆 Hub。
git clone https://github.com/crowdsecurity/hub.git
创建测试
在 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...)
配置测试
将解析器加入测试配置 .tests/myservice-logs/config.yaml。先指定需要 syslog-logs 解析器,因为 myservice 日志经 syslog 传输,再指定自定义解析器。
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 根目录的路径。
创建解析器:骨架
先为教程创建一个非常简单的解析器:
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 模式:从日志中捕获数据。
然后可以这样测试解析器:
▶ 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 可以进一步检查:
▶ 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:
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
再次运行测试:
▶ 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,可以进一步了解处理过程:
▶ 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











暂无评论内容