服务器端功能设计

Django配置

安全审计功能作为 OMServer 的一个功能扩展,需要 Web 服务器端开发框架(Django)同样做些变更来支持新增的功能。由于该功能作为项目的一个 App,因此,第一步需要创建一个 App,操作如下:

# cd /data/www/OMserverweb
# python manage.py startapp omaudit 在创建的omaudit目录中修改urls.py,添加App的URL映射规则,内容如下:
from django.conf.urls.defaults import *
urlpatterns = patterns('omaudit.views',
                       (r'^$', 'index'),
                       (r'omaudit_pull/$', 'omaudit_pull'),  # 映射到omaudit_pull方法,实现客户端数据接收
                       (r'omaudit_run/$', 'omaudit_run'),  # 映射到omaudit_run方法,实现前端实时查询
                       )

修改 App 的 models.py,实现与数据库的关系映射,内容如下:

from django.db import models

# Create your models here.
class ServerHistory(models.Model):
    id = models.IntegerField(primary_key=True, db_column='ID')  # Field name made lowercase.
    history_id = models.IntegerField()
    history_ip = models.CharField(max_length=45)
    history_user = models.CharField(max_length=45)
    history_datetime = models.DateTimeField()
    db_datetime = models.DateTimeField()
    history_command = models.CharField(max_length=765)

    class Meta:
        db_table = u'server_history'

如果数据库结构已经存在,可以通过 python manage.py inspectdb 命令来生成 models 代码。最后修改项目 settings.py,注册该 App 名称,内容如下:

INSTALLED_APPS = (
… …
    # 'django.contrib.admindocs',
    'public',
    'autoadmin',
    'omaudit',    #添加此行,注册该App
)

功能实现方法

服务器端提供了两个关键视图方法,分别实现前端实时展示(omaudit_run)及数据接收(omaudit_pull),下面针对两个方法进行说明。

(1) 前端实时展示(omaudit_run)方法

关于前端数据实时展示的实现原理,通过前端 JavaScript 的 setInterval() 方法实现定时函数调用,首次请求默认返回 ID 倒序最新5条记录,并记录下 LastID(最新记录ID),后面的定时调用将传递 LastID 参数,数据库查询条件是 “ID>LastID”,从而达到实时获取最新记录的目的,同时也支持选择主机来作为过滤条件,功能实现流程图见图14-5。

image 2023 12 09 17 49 41 948
Figure 1. 图14-5 前端数据展示流程图

omaudit_run() 方法实现源码如下:

"""
=事件任务前端展示方法
"""
def omaudit_run(request):
    if not 'LastID' in request.GET:  # 获取上次查询到的最新记录ID
        LastID = ""
    else:
        LastID = request.GET['LastID']

    if not 'hosts' in request.GET:  # 获取选择的主机地址信息
        Hosts = ""
    else:
        Hosts = request.GET['hosts']

    ServerHistory_string = ""
    host_array = target_host(Hosts, "IP").split(';')  # 调用target_host方法过滤出IP地址

    if LastID == "0":  # 符合第一次提交条件,查询不加“id>LastID”条件,反之
        if Hosts == "":  # 符合没有选择主机条件,查询不加“history_ip in host_array”
            # 条件,反之
            ServerHistoryObj = ServerHistory.objects \
                                   .order_by('-id')[:5]
        else:
            ServerHistoryObj = ServerHistory.objects \
                                   .filter(history_ip__in=host_array).order_by('-id')[:5]
    else:
        if Hosts == "":
            ServerHistoryObj = ServerHistory.objects \
                .filter(id__gt=LastID).order_by('-id')
        else:
            ServerHistoryObj = ServerHistory.objects \
                .filter(id__gt=LastID, history_ip__in=host_array).order_by('-id')
    lastid = ""
    i = 0
    for e in ServerHistoryObj:  # 遍历查询结果,返回给前端
        if i == 0:
            lastid = e.id
        ServerHistory_string += "<font color=#cccccc>" + e.history_ip + \
                                "</font>&nbsp;&nbsp;\t" + e.history_user + "&nbsp;&nbsp;\t" + \
                                str(e.db_datetime) + "\t # <font color=#ffffff>" + e.history_command + "</font>*"
        i += 1
    ServerHistory_string += "@@" + str(lastid)  # 通过“@@”字符分隔事件记录与lastid,
    # 前端拆分
    return HttpResponse(ServerHistory_string)

(2) 数据接收(omaudit_pull)方法

数据接收方法相对比较简单,即将接收到的信息直接入库,实现的源码如下:

"""
=事件任务pull方法
"""
def omaudit_pull(request):
    if request.method == 'GET':  # 校验HTTP(GET)请求参数合法性
        if not request.GET.get('history_id', ''):
            return HttpResponse("history_id null")
        if not request.GET.get('history_ip', ''):
            return HttpResponse("history_ip null")
        if not request.GET.get('history_user', ''):
            return HttpResponse("history_user null")
        if not request.GET.get('history_datetime', ''):
            return HttpResponse("history_datetime null")
        if not request.GET.get('history_command', ''):
            return HttpResponse("history_command null")

        history_id = request.GET['history_id']  # 获取HTTP请求参数值
        history_ip = request.GET['history_ip']
        history_user = request.GET['history_user']
        history_datetime = request.GET['history_datetime']
        history_command = request.GET['history_command']

        historyobj = ServerHistory(history_id=history_id, \  # 数据入库(insert)
        history_ip = history_ip, \
            history_user = history_user, \
            history_datetime = history_datetime, \
            history_command = history_command)
        try:
            historyobj.save()
        except Exception as e:
            return HttpResponse("入库失败,请与管理员联系!" + str(e))

        Response_result = "OK"  # 输出“OK”字符作为成功标志
        return HttpResponse(Response_result)
    else:
        return HttpResponse("非法提交!")

当然,如接入的集群过于庞大,服务器端数据库会逐步形成瓶颈,主机审计信息入库会出现一定延时,影响 Linux 用户操作体验,一个可行的方案是采用信息异步入库,即先将信息写入本地,再通过上报程序后台提交信息,用户将无感知。