zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Elkeid 规则引擎——数据向后传递是亮点,支持单事件规则和统计类规则;如果向后传递支持的话,理论上AB先后事件的关联分析可以做;自定义plugin类似udf

事件统计规则引擎数据 分析 自定义 支持
2023-09-14 09:11:46 时间

参考:https://elkeid.bytedance.com/docs/hub/handbook.html#6-elkeid-hub-ruleset

 

检测规则在云端 hub里:如下图

 

 

6 Elkeid HUB RuleSet

RuleSet是HUB实现核心检测/响应动作的部分,需要根据具体业务需求来实现,下图是RuleSet在HUB中的简单工作流程:==》允许数据继续向后传递这个是亮点!!!

4

6.1 RuleSet

HUB RuleSet是通过XML来描述的规则集

RuleSet存在两种类型,rule 和 whitelist,如下:

 
<root ruleset_id="test1" ruleset_name="test1" type="whitelist">
... ....
</root>
<root ruleset_id="test2" ruleset_name="test2" type="rule" undetected_discard="true">
... ...
</root>

其中RuleSet的是固定的模式,不可随意变更,其他属性:

字段说明
ruleset_id 不可重复,描述一个ruleset,只能由小写英文或"_/-"或数字组成
ruleset_name 描述该ruleset的name,可以重复,可以使用中文
type 为rule或者whitelist,其中rule代表着的检测到继续向后传递,whitelist则为检测到不向后传递向后传递的概念可以简单的理解为该ruleset的检出事件
undetected_discard 仅在type为rule时有意义,意为未检测到是否丢弃,若为true,则未被该ruleset检测到则丢弃,若为false,则为未被该rulset检测到也继续向后传递

6.2 Rule

接下来我们来了解rule的具体语法,通常ruleset是由一个或多个rule组成的,需要注意的是多个rule之间的关系是'或'的关系,即如果一条数据可以命中其中的一条或多条rule。

 
<root ruleset_id="test2" ruleset_name="test2" type="rule" undetected_discard="true">
    <rule rule_id="rule_xxx_1" author="xxx" type="Detection">
        ... ...
    </rule>
    <rule rule_id="rule_xxx_2" author="xxx" type="Frequency">
        ... ...
    </rule>
</root>

一个rule的基本属性有:

字段说明
rule_id 在同一个ruleset中不可重复,标识一个rule
author 标识rule的作者
type rule有两种类型,一种是Detection,是无状态的检测类型规则;另一种是Frequency,是在Detection的基础上对数据流进行频率的相关检测

我们先来看两种不同类型规则的简单示例。

我们先假设从Input传入到Ruleset的数据样例为:

 
{
    "data_type":"59",
    "exe":"/usr/bin/nmap",
    "uid":"0"
}
6.2.1 Detection 简单例子
 
<rule rule_id="detection_test_1" author="EBwill" type="Detection">
   <rule_name>detection_test_1</rule_name>
   <alert_data>True</alert_data>
   <harm_level>high</harm_level>
   <desc affected_target="test">这是一个Detection的测试</desc>
   <filter part="data_type">59</filter>
   <check_list>
       <check_node type="INCL" part="exe">nmap</check_node>
   </check_list>
</rule>

其中detection_test_1这个规则的意义是:当有数据的data_type为59,且exe中存在nmap的时候,数据继续向后传递。

6.2.2 Frequency 简单例子
 
<rule rule_id="frequency_test_1" author="EBwill" type="Frequency">
   <rule_name> frequency_test_1 </rule_name>
   <alert_data>True</alert_data>
   <harm_level>high</harm_level>
   <desc affected_target="test">这是一个Frequency的测试</desc>
   <filter part="data_type">59</filter>
   <check_list>
       <check_node type="INCL" part="exe">nmap</check_node>
   </check_list>
   <node_designate>
       <node part="uid"/>
   </node_designate>
   <threshold range="30" local_cache="true">10</threshold>
</rule>

其中frequency_test_1这个规则的意义是:当有数据的data_type为59,且exe中存在nmap的时候,进入到频率检测:当同一个uid,在30秒内出现了 >= 10以上行为则告警,且在这过程中使用当前HUB实例自身缓存。

我们可以看到,实际上Frequency只是比Detection多了node_designate以及threshold字段,也就是说,无论什么规则,都会需有最基础的一些字段,我们接下来就先来了解这些通用的基础字段。

6.2.3 通用字段描述
字段说明
rule_name 代表rule的名字,与rule_id不同的是可以使用中文或其他方式来更好的表达rule的含义,可以重复
alert_data 为True或False,如果为True,则会将该rule的基础信息增加到当前的数据中向后传递;若为False,则不会将该rule的信息增加到当前的数据中
harm_level 表达该rule的危险等级,可以为 info/low/medium/high/critical
desc 用于提供这个rule本身的描述,其中affected_target是该rule针对的组件信息,用户自行填写,并无强制规定限制
filter 对数据的第一层过滤,part表示对数据中的哪个字段进行过滤,具体内容为检测内容,义为part中是否存在 检测数据,如果RuleSet的类型为rule存在则继续向下执行rule的逻辑,如果不存在则不向下检测;RuleSet类型为whitelist时则相反,即存在则跳过检测,不存在则继续。 filter只能存在一个,且默认也仅支持“存在”的逻辑检测
check_list check_list内可以存在0或多个check_node,一个rule只能存在一个check_list,其中check_node的逻辑为'且'即为'and',如果RuleSet的类型为rule则需要其中check_node全部通过才可以继续向下,如果为whitelist则相反,即其中check_node全部不通过才可以继续向下
check_node check_node是一个具体的检测项
6.2.4 alert_data

我们依然以上面的Decetion的例子为例:

待检测数据:

 
{
    "data_type":"59",
    "exe":"/usr/bin/nmap",
    "uid":"0"
}

RuleSet:

 
<root ruleset_id="test2" ruleset_name="test2" type="rule" undetected_discard="true">
<rule rule_id="detection_test_1" author="EBwill" type="Detection">
   <rule_name>detection_test_1</rule_name>
   <alert_data>True</alert_data>
   <harm_level>high</harm_level>
   <desc affected_target="test">这是一个Detection的测试</desc>
   <filter part="data_type">59</filter>
   <check_list>
       <check_node type="INCL" part="exe">nmap</check_node>
   </check_list>
</rule>
</root>

如果其alert_data为True,则该RuleSet会向后传递以下数据,会增加SMITH_ALERT_DATA字段,其中包括HIT_DATA用来描述命中规则的详情,以及RULE_INFO即规则本身的基本信息:==》这样是不是就可以做时序先后的关联了?

 
{
    "SMITH_ALERT_DATA":{
        "HIT_DATA":[
            "test2 exe:[INCL]: nmap"
        ],
        "RULE_INFO":{
            "AffectedTarget":"all",
            "Author":"EBwill",
            "Desc":"这是一个Detection的测试",
            "DesignateNode":null,
            "FreqCountField":"",
            "FreqCountType":"",
            "FreqHitReset":false,
            "FreqRange":0,
            "HarmLevel":"high",
            "RuleID":"test2",
            "RuleName":"detection_test_1",
            "RuleType":"Detection",
            "Threshold":""
        }
    },
    "data_type":"59",
    "exe":"/usr/bin/nmap",
    "uid":"0"
}

alert_data为False,则会向后传递以下数据,即原始数据:

 
{
    "data_type":"59",
    "exe":"/usr/bin/nmap",
    "uid":"0"
}
6.2.5 check_node

check_node的基本结构如下:

 
<check_node type="检测类型" part="待检测路径">
   检测内容
</check_node>
6.2.5.1 检测类型

目前支持以下几种检测类型:

类型说明
END 待检测路径中的内容 以 检测内容 结尾
START 待检测路径中的内容 以 检测内容 开头
NEND 待检测路径中的内容 不以 检测内容 结尾
NSTART 待检测路径中的内容 不以 检测内容 开头
INCL 待检测路径中的内容 存在 检测内容
NI 待检测路径中的内容 不存在 检测内容
MT 待检测路径中的内容 大于 检测内容
LT 待检测路径中的内容 小于 检测内容
REGEX 对 待检测路径中的内容 进行 检测内容 的正则匹配
ISNULL 待检测路径中的内容 为空
NOTNULL 待检测路径中的内容 不为空
EQU 待检测路径中的内容 等于 检测内容
NEQ 待检测路径中的内容 不等于 检测内容
CUSTOM 针对 待检测路径中的内容 进行 检测内容 指定的 自定义插件检测
CUSTOM_ALLDATA 针对 待检测数据 进行 检测内容 指定的 自定义插件检测;该方式下part可以为空,因为不依赖该字段,是将整个数据传递到插件进行检测

我们接下来说明下part的使用方法,该方法与filter的part使用方法一致。

6.2.5.2 part

假设待检测数据为:

 
{
    "data":{
        "name":"EBwill",
        "number":100,
        "list":[
            "a1",
            "a2"
        ]
    },
    "class.id":"E-19"
}

对应的part描述方式如下:

 
data               =       "{\"name\":\"EBwill\",\"number\":100,\"list\":[\"a1\",\"a2\"]
data.name          =       "EBwill"
data.number        =       "100"
data.list.#_0      =       "a1"
data.list.#_1      =       "a2"
class\.id          =       "E-19"

需要注意的是如果待检测key中存在"."需要用""转义

6.2.5.3 高级用法之check_data_type

假设待检测数据为

 
{
    "stdin":"/dev/pts/1",
    "stdout":"/dev/pts/1"
}

假设我们需要检测 stdin 等于 stdout,即我们的检测内容来源于待检测数据,那么我们需要使用check_data_type="from_ori_data" 来重新定义检测内容的来源是来自于待检测数据而不是填写的内容,如下:

 
<check_node type="EQU" part="stdin" check_data_type="from_ori_data">stdout</check_node>
6.2.5.4 高级用法之logic_type

假设待检测数据为

 
{
    "data":"test1 test2 test3",
    "size": 96
}

当我们需要检测data中是否存在 test1 或 test2 的时候,我们可以写正则来实现,也可以通过定义logic_type来实现check_node支持"AND"或者"OR"逻辑,如下:

 
<!-- data中存在test1 或 test2 -->
<check_node type="INCL" part="data" logic_type="or" separator="|">
    <![CDATA[test1|test2]]>
</check_node>
<!-- data中存在test1 和 test2 -->
<check_node type="INCL" part="data" logic_type="and" separator="|">
    <![CDATA[test1|test2]]>
</check_node>

其中logic_type用来描述逻辑类型,支持"and"和"or",separator用于自定义标识切割检测数据的方式

6.2.5.5 高级用法之foreach

当我们需要对数组有较复杂的检测时可能可以通过foreach来解决。

假设待检测数据为

 
{
    "data_type":"12",
    "data":[
        {
            "name":"a",
            "id":"14"
        },
        {
            "name":"b",
            "id":"98"
        },
        {
            "name":"c",
            "id":"176"
        },
        {
            "name":"d",
            "id":"172"
        }
    ]
}

我们想将id > 100且顶层的data_type等于12的数据的obj筛选出来那么可以先通过foreach进行遍历,然后再对遍历后的数据进行判断,如下:

 
<check_list foreach="d in data"> ==》这不就是python web模板的写法嘛
    <check_node type="MT" part="d.id">100</check_node>
    <check_node type="EQU" part="data_type">12</check_node>
</check_list>

则会向后传递多条数据:

 
{
    "data_type":"12",
    "data":[
        {
            "name":"c",
            "id":"176"
        }
    ]
}

以及

{
    "data_type":"12",
    "data":[
        {
            "name":"d",
            "id":"172"
        }
    ]
}

我们通过下图来更好的理解foreach这个高级用法

5

假设待检测数据为

 
{
    "data_type":"12",
    "data":[
        1,
        2,
        3,
        4,
        5,
        6,
        7
    ]
}

我们想筛选出data中小于5的数据,那么需要这样编写:

 
<check_list foreach="d in data">
    <check_node type="LT" part="d">5</check_node>
</check_list>
6.2.6 Frequency 字段

Frequency的逻辑在check_list之后,但数据通过了filter和check_list之后,如果当前rule的类型为Frequency则会进入到Frequency的特殊检测逻辑。Frequency存在两个字段,node_designatethreshold,如下:

 
<node_designate>
    <node part="uid"/>
    <node part="pid"/>
</node_designate>
<threshold range="30">5</threshold>
6.2.6.1 node_designate

其中node_designate是代表group_by,上方样例的含义是对 uid 和 pid 这两个字段进行group_by。

6.2.6.2 threshold

threshold是描述频率检测的具体检测内容:多长时间内(range)出现多少次(threshold)。如上样例中即表达:同一uid与pid在30秒内出现5次即为检出,其中range的单位是秒。

6

如上图,由于仅仅在10s内就出现了5次,那么在剩下的20s内出现的全部pid=10且uid=1的数据都会告警,如下:

7

但是这个问题可能会导致告警数据过多,因此支持一个叫做:hit_reset的参数,使用方式如下:

 
<node_designate>
    <node part="uid"/>
    <node part="pid"/>
</node_designate>
<threshold range="30" hit_reset="true" >5</threshold>

当hit_reset为true时,每次满足threshold策略后,时间窗口将会重制,如下:

8

6.2.6.3 高级用法之count_type

有些情况下我们计算频率不是想计算“出现了多少次”,而会有一些其他的需求,如出现了多少类,**出现的字段内数据和是多少。**我们先来看第一个需求,出现了多少类。

假设待检测数据为

 
{
    "sip":"10.1.1.1",
    "sport":"6637",
    "dip":"10.2.2.2",
    "dport":"22"
}

当我们想写一个检测扫描器的规则时,我们其实往往不关心某一个IP访问了多少次其他资产,而是访问了多少不同的其他资产,当这个数据较大时,可能存在网络扫描探测的可能性,假设我们规定,3600秒内,同一IP访问的不同IP数超过100种就记录下来,那么他的频率部分规则应该这样编写:

 
<node_designate>
    <node part="sip"/>
</node_designate>
<threshold range="3600" count_type="classify" count_field="dip">100</threshold>

这时候count_type需要为classifycount_field则为类型计算依赖的字段,即dip。

第二个场景假设待检测数据为

 
{
    "sip":"10.1.1.1",
    "qps":1
}

假设我们需要筛选出3600s内qps总和大于1000的数据,那么我们可以这样编写:

 
<node_designate>
    <node part="sip"/>
</node_designate>
<threshold range="3600" count_type="sum" count_field="qps">1000</threshold>

count_type为空时默认计算次数,当为classify时计算的是类型,为sum时计算的是求和

6.2.7 append

当我们想对数据进行一些增加信息的操作时,可以使用append来进行添加数据的操作,append的语法如下:

 
<append type="append类型" rely_part="依赖字段" append_field_name="增加字段名称">增加内容</append>
6.2.7.1 append之STATIC

假设待检测数据为

 
{
    "alert_data":"data"
}

假设该数据已经通过了filter/check_list/频率检测(若有),这时候我们想增加一些固定的数据到该数据中,如:data_type:10,那么我们可以通过以下方式增加:

 
<append type="static" append_field_name="data_type">10</append>

我们将会得到以下数据:

 
{
    "alert_data":"data",
    "data_type":"10"
}
6.2.7.2 append之FIELD

假设待检测数据为

 
{
    "alert_data":"data",
    "data_type":"10"
}

如果我们想对该数据增加一个字段:data_type_copy:10(来源于数据中的data_type字段),那么我们可以按以下方式编写:

 
<append type="field" rely_part="data_type" append_field_name="data_type_copy"></append>
6.2.7.3 append之CUSTOM

假设待检测数据为

 
{
    "sip":"10.1.1.1",
    "sport":"6637",
    "dip":"10.2.2.2",
    "dport":"22"
}

如果我们想通过外部API查询sip的CMDB信息,那我们在这种场景下无法通过简单的规则来实现,需要借助Plugin来实现,Plugin的具体编写方式将在下文进行说明,在这里我们先介绍如果在RuleSet中调用自定义Plugin,如下:

 
<append type="CUSTOM" rely_part="sip" append_field_name="cmdb_info">AddCMDBInfo</append>

在这里我们将会把rely_part中字段的数据传递到AddCMDBInfo插件进行数据查询,并将插件返回数据append到cmdb_info数据中,如下:

 
{
    "sip":"10.1.1.1",
    "sport":"6637",
    "dip":"10.2.2.2",
    "dport":"22",
    "cmdb_info": AddCMDBInfo(sip) --> cmdb_info中的数据为插件AddCMDBInfo(sip)的返回数据
}
6.2.7.4 append之CUSTOM_ALLORI

假设待检测数据为

 
{
    "sip":"10.1.1.1",
    "sport":"6637",
    "dip":"10.2.2.2",
    "dport":"22"
}

如果我们想通过内部权限系统的API查询sip与dip的权限关系,那此时也是需要通过插件来实现这一查询,但是我们的该插件的入参不唯一,我们需要将待检测数据完整的传入该插件,编写方式如下:

 
<append type="CUSTOM_ALLORI" append_field_name="CONNECT_INFO">AddConnectInfo</append>

我们可以得到:

 
{
    "sip":"10.1.1.1",
    "sport":"6637",
    "dip":"10.2.2.2",
    "dport":"22",
    "CONNECT_INFO": AddConnectInfo({"sip":"10.1.1.1","sport":"6637","dip":"10.2.2.2","dport":"22"}) --> CONNECT_INFO中的数据为插件AddConnectInfo的返回数据
}
6.2.7.5 其他

append可以在一条rule中存在多个,如下:

 
<rule rule_id="rule_1" type="Detection" author="EBwill">
    ...
    <append type="CUSTOM_ALLORI" append_field_name="CONNECT_INFO">AddConnectInfo</append>
    <append type="field" rely_part="data_type"></append>
    <append type="static" append_field_name="data_type">10</append>
    ...
</rule>
6.2.8 del

当我们需要对数据进行一些裁剪的时候,可以使用del字段进行操作。

假设待检测数据为

 
{
    "sip":"10.1.1.1",
    "sport":"6637",
    "dip":"10.2.2.2",
    "dport":"22",
    "CONNECT_INFO": "false"
}

假设我们需要将字段CONNECT_INFO移除,那我按如下方式编写即可:

 
<del>CONNECT_INFO</del>

可以得到如下数据:

 
{
    "sip":"10.1.1.1",
    "sport":"6637",
    "dip":"10.2.2.2",
    "dport":"22"
}

del可以编写多个,需要用";"隔开,如下:

 
<del>CONNECT_INFO;sport;dport</del>

即可得到如下数据:

 
{
    "sip":"10.1.1.1",
    "dip":"10.2.2.2"
}
6.2.9 modify

当我们需要对数据进行复杂处理时,通过append和del无法满足需求,如对数据进行拍平操作,对数据的key进行变化等,这时候可以使用modify来进行操作,需注意modify仅支持插件,使用方式如下:

 
<modify>插件名称</modify>

流程如下图:

9

6.2.10 Action

当我们需要做一些特殊操作,如联动其他系统,发送告警到钉钉/Lark/邮件,联动WAF封禁IP等操作的时候,我们可以通过Action来实现相关的操作,需注意仅支持插件,使用方式如下:

 
<action>emailtosec</action>

插件emailtosec的入参会是当前的数据,其他的操作可以按需求编写。

action也是支持多个插件,使用方式如下:

 
<action>emailtosec1</action>
<action>emailtosec2</action>

在上面的例子中emailtosec1与emailtosec2都会被触发运行。

action插件也支持自定义入参,具体使用方法请参考7.2.1

6.3 检测/执行顺序

10

需要注意的是数据在通过Rule的过程中是动态的,即如果通过了append那么接下来如果是del那么del接收到的数据是append生效后的数据。

6.3.1 Rule之间的关系

同一RuleSet中的Rule为"OR"的关系,假设RuleSet如下:

 
<root ruleset_id="test2" ruleset_name="test2" type="rule" undetected_discard="true">
<rule rule_id="detection_test_1" author="EBwill" type="Detection">
   <rule_name>detection_test_1</rule_name>
   <alert_data>True</alert_data>
   <harm_level>high</harm_level>
   <desc affected_target="test">这是一个Detection的测试1</desc>
   <filter part="data_type">59</filter>
   <check_list>
       <check_node type="INCL" part="exe">redis</check_node>
   </check_list>
</rule>
<rule rule_id="detection_test_2" author="EBwill" type="Detection">
   <rule_name>detection_test_2</rule_name>
   <alert_data>True</alert_data>
   <harm_level>high</harm_level>
   <desc affected_target="test">这是一个Detection的测试2</desc>
   <filter part="data_type">59</filter>
   <check_list>
       <check_node type="INCL" part="exe">mysql</check_node>
   </check_list>
</rule>
</root>

假设数据的exe字段为 mysql-redis,那么会detection_test_1于detection_test_2都会被触发且会产生两条数据向后传递,分别隶属这两条规则

6.4 更多的例子

 
<rule rule_id="critical_reverse_shell_rlang_black" author="lez" type="Detection">
    <rule_name>critical_reverse_shell_rlang_black</rule_name>
    <alert_data>True</alert_data>
    <harm_level>high</harm_level>
    <desc kill_chain_id="critical" affected_target="host_process">可能存在创建 R 反弹shell的行为</desc>
    <filter part="data_type">42</filter>
    <check_list>
        <check_node type="INCL" part="exe">
            <![CDATA[exec/R]]>
        </check_node>
        <check_node type="REGEX" part="argv">
            <![CDATA[(?:\bsystem\b|\bshell\b|readLines.*pipe.*readLines|readLines.*writeLines)]]>
        </check_node>
    </check_list>
    <node_designate>
    </node_designate>
    <del />
    <action />
    <append append_field_name="" rely_part="" type="none" />
</rule>
<rule rule_id="init_attack_network_tools_freq_black" author="lez" type="Frequency">
    <rule_name>init_attack_network_tools_freq_black</rule_name>
    <freq_black_data>True</freq_black_data>
    <harm_level>medium</harm_level>
    <desc kill_chain_id="init_attack" affected_target="service">存在多次使用网络攻击工具的行为,可能存在中间人/网络欺骗</desc>
    <filter part="SMITH_ALERT_DATA.RULE_INFO.RuleID">init_attack_network</filter>
    <check_list>
    </check_list>
    <node_designate>
        <node part="agent_id" />
        <node part="pgid" />
    </node_designate>
    <threshold range="30" local_cache="true" count_type="classify" count_field="argv">3</threshold>
    <del />
    <action />
    <append append_field_name="" rely_part="" type="none" />
</rule>
<rule rule_id="tip_add_info_01" type="Detection" author="yaopengbo">
    <rule_name>tip_add_info_01</rule_name>
    <harm_level>info</harm_level>
    <threshold/>
    <node_designate/>
    <filter part="data_type">601</filter>
    <check_list>
        <check_node part="query" type="CUSTOM">NotLocalDomain</check_node>
    </check_list>
    <alert_data>False</alert_data>
    <append type="FIELD" rely_part="query" append_field_name="tip_data"></append>
    <append type="static" append_field_name="tip_type">3</append>
    <append type="CUSTOM_ALLORI" append_field_name="tip_info">AddTipInfo</append>
    <del/>
    <action/>
    <desc affected_target="tip">dns新增域名tip检测信息</desc>
</rule>

6.5 规则编写建议

  • filter的良好运用可以大大降低性能压力,filter的编写目标应该是让尽可能少的数据进入CheckList
  • 尽可能少的使用正则

7 Elkeid HUB Plugin

Elkeid HUB Plugin用于解除部分Ruleset在编写过程的限制,提高HUB使用的灵活性。通过编写plugin,可以实现部分编写Ruleset无法完成的操作。同时,如果需要和当前尚不支持的第三方组件进行交互,只能通过编写plugin来实现。Elkeid HUB目前仅支持Python Plugin。

Python Plugin本质是在HUB运行过程中,通过另外一个进程执行Python 脚本,并将执行结果返回给Elkeid HUB。

Plugin总计有6种类型,均在Ruleset编写文档中介绍过,以下会结合上文的例子对每种plugin展开介绍。 Plugin的类型命名与在Ruleset中的标签名并不一一对应,实际使用中请严格以文档为准。

7.1 通用参数介绍

7.1.1 格式

每个Plugin都是一个Python Class,plugin加载时,HUB会实例化这个Class,并对该Class的name,type,log,redis四个变量进行赋值,每次plugin执行时,会调用该class的plugin_exec方法。

Class 如下:

 
class Plugin(object):
    def __init__(self):
        self.name = ''
        self.type = ''
        self.log = None
        self.redis = None
    
    def plugin_exec(self, arg, config):
        pass
7.1.2 init

__init__方法中包含以下四个变量:

  • name: Plugin Name
  • type: Plugin Type
  • log: logging
  • redis: redis client

如果有自己的init逻辑,可以加在后面

7.1.3 plugin_exec

plugin_exec方面有两个参数,arg和config。

  • arg就是该plugin执行时接受的参数。根据plugin类型的不同,arg是string或dict()。

针对Action,Modify,Origin,OriginAppend四种类型的plugin,arg是dict()。

针对Append,Custom两种类型的plugin,arg是string。

  • config是plugin可以接受的额外参数,目前只有Action和Modify支持,如果在Ruleset中有指定,会通过config参数传递给plugin。

例如:在ruleset中添加了extra标签,HUB会以dict的形式以config入参调用plugin_exec方法。

 
<action extra="mail:xxx@bytedance.com">PushMsgToMatao</action>
config = {"mail":"xxx@bytedance.com"}

7.2 example

7.2.1 Plugin之Action

在rule中的作用见6.2.10。

Action用于实现数据通过当前rule之后执行一些额外操作。

一个Action plugin接收的是整个数据流的拷贝。返回的是action是否执行成功。action是否执行成功不会影响数据流是否继续向下走,只会在HUB的日志中体现。

实现参考

 
class Plugin(object):
    def __init__(self):
        self.name = None
        self.type = None
        self.log = None
        self.redis = None
        
    def plugin_exec(self, arg, config):
        # 例:请求某个回调地址
        requests.post(xxx)
        result = dict()
        result["done"] = True
        return result
7.2.2 Plugin之Append

在rule中的作用见6.2.7.3

Append和OriginAppend类似,均是可以自定义Append操作,不同的是Append接受的数据流中确定的某个属性值,而OriginAppend接受的是整个数据流的拷贝。两者的返回值均会写入到数据流中指定属性中。

实现参考

 
class Plugin(object):
    def __init__(self):
        self.name = None
        self.type = None
        self.log = None
        self.redis = None

    def plugin_exec(self, arg, config):
        result = dict()
        result["flag"] = True
        # 在原arg后面加上__new__后缀
        result["msg"] = arg + "__new__"
        return result
7.2.3 Plugin之Custom

在rule中的作用见6.2.5.2中的Custom

CUSTOM用于实现自定义CheckNode。CheckNode中虽然预定义了10余种常见的判断方式,但在实际rule编写过程中,必然无法完全覆盖,所以开放了plugin拥有书写更灵活的判断逻辑。

该plugin接收的参数是数据流中指定的属性值,返回的是是否命中以及写入hit中部分。

实现参考

 
class Plugin(object):
    def __init__(self):
        self.name = None
        self.type = None
        self.log = None
        self.redis = None

    def plugin_exec(self, arg, config):
        result = dict()
        # 若arg长度为10
        if arg.length() == 10:
            result["flag"] = True
            result["msg"] = arg
        else:
            result["flag"] = True
            result["msg"] = arg
        return result
7.2.4 Plugin之Modify

在rule中的作用见6.2.9

Modify是所有plugin中灵活度最高的plugin,当编写ruleset或其他plugin无法满足需求时,可以使用modify plugin,获得对数据流的完全操作能力。

Modify插件的入参是当前数据流中的一条数据。返回分两种情况,可以返回单条数据,也可以返回多条数据。

返回单条数据时,Flag为true,数据在Msg中,返回多条数据时,MultipleDataFlag为true,数据在数组MultipleData中。若Flag和MultipleDataFlag同时为true,则无意义。

实现参考1:

 
class Plugin(object):
    def __init__(self):
        self.name = None
        self.type = None
        self.log = None
        self.redis = None

    def plugin_exec(self, arg, config):
        result = dict()
        # 随意修改数据,例如加个字段
        arg["x.y"] = ["y.z"]
        result["flag"] = True
        result["msg"] = arg
        return result

实现参考2:

 
class Plugin(object):
    def __init__(self):
        self.name = None
        self.type = None
        self.log = None
        self.redis = None

    def plugin_exec(self, arg, config):
        result = dict()
        # 将该条数据复制成5分
        args = []
        args.append(arg)
        args.append(arg)
        args.append(arg)
        args.append(arg)
        args.append(arg)
        result["multiple_data_flag"] = True
        result["multiple_data"] = args
        return result
7.2.5 Plugin之Origin

在rule中的作用见6.2.5.2中的CUSTOM_ALLORI

Custom插件的进阶版,不再是对数据流中的某个字段进行check,而是对数据流中的整条数据进行check。入参由单个字段变成了整个数据流。

实现参考

 
class Plugin(object):
    def __init__(self):
        self.name = None
        self.type = None
        self.log = None
        self.redis = None

    def plugin_exec(self, arg, config):
        result = dict()
        # 若arg["a"]和arg["b"]长度长度都为10
        if arg["a"].length() == 10 and arg["b"].length() == 10:
            result["flag"] = True
            result["msg"] = arg
        else:
            result["flag"] = True
            result["msg"] = arg
        return result
7.2.6 Plugin之OriginAppend

在rule中的作用见6.2.7.4

Append插件的进阶版,不再是对数据流中的某个字段进行判断然后append,而是对数据流中的整条数据进行判断。入参由单个字段变成了整个数据流。

实现参考

 
class Plugin(object):
    def __init__(self):
        self.name = None
        self.type = None
        self.log = None
        self.redis = None
        
    def plugin_exec(self, arg, config):
        result = dict()
        result["flag"] = True
        # 合并两个字段
        result["msg"] = arg["a"] + "__" + arg["b"]
        return result

7.3 Plugin 开发流程

plugin的运行环境为pypy3.7-v7.3.5-linux64,如果希望python脚本正常运行,需要在此环境下进行测试。

HUB自身引入了部分基础依赖,但远无法覆盖python常用package,当有需要时,需要用户通过如下方式自行安装。

  1. venv位于py/pypy下,可以通过以下命令切换到venv中,执行pip install进行安装。
 
source py/pypy/bin/activate
  1. 在plugin的init方法中调用pip module,进行安装
7.3.1 创建Plugin

plugin存放在在运行配置(config目录)下的plugin文件夹中,一个plugin对应目录下的一个文件夹。我们提供了一些示例plugin,包含Action,Modify,Append操作,同时提供了一份空白模板。

创建插件只需要复制一份空白模板并以你想要的plugin名命名,同时修改elkeid.txt文件

 
[plugin]
name = SendToLarkGroup              #插件名
type = Action                       #插件类型
description = use bot send lark     #插件描述  
runtime = Python
author = lichanghao_orange          #插件作者
7.3.2 编写插件内容

可以根据前面提供的示例以及内置的插件例子进行编写,实现自定义功能

7.3.3 使用该插件

在规则中对应的地方填写该插件的插件名即可使用,如果出现报错会显示在HUB的log中

7.4 示例插件说明

在社区版中我们提供了几个示例插件用于教学和简单实用,目前包含推送消息到飞书群插件、推送消息到钉钉群插件、根据IP反查域名插件、自定义Check插件。这些插件经过简单修改后均可以直接使用,如下是使用说明。

7.4.1 推送消息到飞书群插件
  • 插件名:SendToLarkGroup
  • 使用方法:获取飞书机器人的app_id、app_secret修改对应位置即可(在注释中可以找到)。在调用时使用extra传参将群聊id传入即可使用。
 
  <action extra="id:qunid">SendToLark</action>
7.4.2 推送消息到钉钉群插件
  • 插件名:SendToDingding
  • 使用方法:在钉钉群中添加机器人并获取机器人的token以及两步认证的secret(在群聊的机器人设置中可以找到)。
7.4.3 推送消息到TG群插件
  • 插件名:SendToTelegram
  • 使用方法:在TG中创建好bot后,获取token,在bot设置中关闭Group Privacy。将bot添加到群中,将群id和token写进插件代码中即可。
7.4.4 推送消息到企业微信插件
  • 插件名:SendToWeCom
  • 使用方法:在企业微信中添加机器人并获取机器人的webhook链接,将该链接填写在插件内即可。
7.4.5 根据IP反查域名插件
  • 插件名:DNSptr
  • 使用方法:读取字段ip,如果可以找到对应的域名,会在新增的字段“ptr”中输出域名并在字段“dns”中输出dns服务器地址,如果没查到,会在新增的字段“ptr”中输出“null”。