scirius
scirius介绍
scirius为suricata提供了一个web,用来管理规则以及处理告警,我们分析学习一下他的源代码。
先分析主目录的views.py
定义了重定向到kinaba、evebox、moloch的路由,没什么关键函数。我们主要关心的是hunt和manage。
rules
看一下rules目录下的views.py
访问manage页面,主要实现了两个功能
- suricata状态监控,配置
- rule管理
规则的管理,先大概了解一下功能
- /rules/source可以看到规则源
- /rules/source/add_public可以增加公共规则源
- /rules/source/add可以编辑自定义规则源
- /rules/ruleset/add可以新增规则集
- /rules/ruleset/n/update可以更新规则集
- /rules/ruleset/n/copy可以复制规则集
- /rules/ruleset/n/delete可以删除规则集
- /rules/ruleset/n/edit可以编辑规则集
- /rules/ruleset/1/edit?mode=sources可以启用/禁用source
- /rules/ruleset/1/edit?mode=categories可以启用/禁用categories
- /rules/ruleset/1/addsupprule可以禁用某条rule
- /rules/ruleset/1/edit?mode=rules可以恢复某条被禁用的rule
- /rules/category/n/可以查看category
- /rules/category/n/enable可以启用category
- /rules/category/n/disable可以禁用category
- /rules/category/n/transform
- /rules/rule/pk/n/可以查看rule的定义
- /rules/rule/pk/n/disable可以禁用rule
- /rules/rule/pk/n/enable可以启用rule
- /rules/rule/n/edit
- /rules/rule/n/disable
- /rules/rule/n/enable
- /rules/rule/n/threshold?action=threshold为规则增加触发的阀值
- /rules/rule/n/threshold?action=suppress过滤某ip触发改规则
页面如下
index
从数据库获取Ruleset和Source的list,但是在index.html没有用到这两个变量
search
将客户端请求中的search分别取Rule,Category,Ruleset中作查询,并将查询结果转换为tables对象,最后在search.html中利用load和render_tables将数据展示到前段
sources
获取所有的source并展示在前段页面
source
列出某一个source中所有的category
categories
调用utils.py中的scirius_listing((request, Category, assocfn)),而该函数不仅仅会返回Category还会返回assocfn,不知道这是用来干嘛的,先往下看
category
rules是public_source中启用的规则,commented_rules是public_source中默认注释掉的规则,rulesets_status则是category的action,lateral,target属性
elasticsearch
/rules/source/add_public增加公共规则源
/rules/source/add_public页面代码及分析如下
1 | def add_public_source(request): |
add_public_source.html页面代码及分析如下
1 | <!-- 获取source.yaml里面的source和参数 --> |
import_and_add_source.html1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67<script>
$( 'document' ).ready(function() {
<!-- 如果update为true则调用update_activate_source -->
{% if update %}
{% if not rulesets %}
update_activate_source({{ source.pk }}, null);
{% else %}
update_activate_source({{ source.pk }}, [ {{ rulesets|join:"," }} ], [ {{ ruleset_list|safeseq|join:"," }} ])
{% endif %}
window.addEventListener("beforeunload", function (e) {
if (!warn_on_exit) {
return;
}
var confirmationMessage = "Warning, leaving page will interrupt source addition mechanism.";
e.returnValue = confirmationMessage; // Gecko, Trident, Chrome 34+
return confirmationMessage; // Gecko, WebKit, Chrome <34
});
{% endif %}
});
function update_activate_source(src_pk, rulesets, ruleset_list)
{
var tgturl = "/rules/source/" + src_pk + "/update";
$('#source_progress').text("Updating source.");
<!-- 通过ajax访问"/rules/source/" + src_pk + "/update" -->
$.ajax({
type:"POST",
url: tgturl,
<!-- 如果后端返回的data['status']为true -->
success: function(data) {
if (data['status'] == true) {
$("#init_details").append('<p class="text-success"> <span class="glyphicon glyphicon-ok"></span></span> Source updated</p>');
$('#source_progress').width("70%");
<!-- 如果成功获取到规则文件,会调用test_source去判断是否可以正常加载且和其他规则匹配 -->
test_source(src_pk, rulesets, ruleset_list);
<!-- 否则定义error_action -->
} else {
$('#source_progress').addClass("progress-bar-danger");
$('#source_progress').text("Could not test source.");
<!-- 把data['error']放到text-danger标签里 -->
$("#init_details").append('<p class="text-danger"> <span class="glyphicon glyphicon-remove"></span> Error during source update: ' + data['errors'] + '</p>');
var error_actions = '<a href="{{ source.get_absolute_url }}delete"><button class="btn btn-primary" type="submit"><span class="glyphicon glyphicon-trash"> Delete source</span></button></a>'
error_actions += ' <a href="{{ source.get_absolute_url }}"><button class="btn btn-warning" id="continue" type="submit"><span class="glyphicon glyphicon-ok"> Ignore errors and continue</span></button></a>';
$("#init_details").append('<div id="error_actions">' + error_actions + '<div>');
warn_on_exit = false;
}
},
error: function(data) {
$('#source_progress').addClass("progress-bar-danger");
$('#source_progress').text("Unable to update source.");
var err_str = 'Error during source update';
if (data.statusText && data.statusText != 'error') {
err_str += ' (' + data.statusText + ')';
}
$("#init_details").append('<p class="text-danger"> <span class="glyphicon glyphicon-remove"> ' + err_str + '</span> </p>');
var error_actions = '<a href="{{ source.get_absolute_url }}delete"><button class="btn btn-primary" type="submit"><span class="glyphicon glyphicon-trash"> Delete source</span></button></a>'
error_actions += ' <a href="{{ source.get_absolute_url }}"><button class="btn btn-warning" id="continue" type="submit"><span class="glyphicon glyphicon-ok"> Ignore errors and continue</span></button></a>';
$("#init_details").append('<div id="error_actions">' + error_actions + '<div>');
warn_on_exit = false;
},
timeout: 240 * 1000
});
}
</script>
/rules/ruleset 列出ruleset规则集
页面如下
/rules/source 列出规则源
/rules/source页面代码如下
1 | def sources(request): |
最后效果是获取了Source.objects.all(),但是不太明白绕这么多绕?
页面如下
info
通过psutils获取系统状态1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71PROBE = __import__(settings.RULESET_MIDDLEWARE)
def info(request):
data = {'status': 'green'}
if request.GET.__contains__('query'):
info = PROBE.common.Info()
query = request.GET.get('query', 'status')
if query == 'status':
data = {'running': info.status()}
elif query == 'disk':
data = info.disk()
elif query == 'memory':
data = info.memory()
elif query == 'used_memory':
data = info.used_memory()
elif query == 'cpu':
data = info.cpu()
return JsonResponse(data, safe=False)
#在settings.py里可以看到RULESET_MIDDLEWARE='suricata',所以PROBE相当于__import__(settings.suricata),动态加载了suricata。而suricata/common.py如下
import psutil
from rest_framework import serializers
from django.conf import settings
if settings.SURICATA_UNIX_SOCKET:
try:
import suricatasc
except:
settings.SURICATA_UNIX_SOCKET = None
class Info():
def status(self):
suri_running = 'danger'
# 判断suricata是否运行则有两种方式,如果配置了suricata_unix_socket则调用suricatasc并发送uptime来判断。
if settings.SURICATA_UNIX_SOCKET:
sc = suricatasc.SuricataSC(settings.SURICATA_UNIX_SOCKET)
try:
sc.connect()
except:
return 'danger'
res = sc.send_command('uptime', None)
if res['return'] == 'OK':
suri_running = 'success'
sc.close()
# 如果没有配置SURICATA_UNIX_SOCKET则调用psutil.process_iter()来判断是否存在Suricata-Main来实现
else:
for proc in psutil.process_iter():
try:
pinfo = proc.as_dict(attrs=['name'])
except psutil.NoSuchProcess:
pass
else:
if pinfo['name'] == 'Suricata-Main':
suri_running = 'success'
break
return suri_running
#可以看到该函数调用psutil获取cpu/disk/mem状态
def disk(self):
return psutil.disk_usage('/')
def memory(self):
return psutil.virtual_memory()
def used_memory(self):
mem = psutil.virtual_memory()
return round(mem.used * 100. / mem.total, 1)
def cpu(self):
return psutil.cpu_percent(interval=0.2)
suricatasc可以实时和suricata进程进行交互