热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

基于golang+lua虚拟机的日志分析系统实现

FBI-AnalyzerFBI-Analyzer是一个灵活的日志分析系统,基于golang和lua,插件风格类似ngx-lua。使用者只需要编写简单的lua逻辑就可以实现golan

FBI-Analyzer

FBI-Analyzer是一个灵活的日志分析系统,基于golang和lua,插件风格类似ngx-lua。

使用者只需要编写简单的lua逻辑就可以实现golang能实现的所有需求,点击跳转实现原理。现实中可作为WAF的辅助系统进行安全分析,点击跳转实例。

可快速迁移waf中行为分析插件(非实时拦截需求,需要缓存计算数据的逻辑)至本系统,避免插件在处理请求时发起过多对数据缓存(redis等)的请求而导致WAF性能下降,帮助waf减负。

实现这个项目的目的其实也是加深下对lua虚拟机的认识,以及其他语言通过插件的方式调用lua脚本的工作原理,本项目因为只是单纯的lua虚拟机,不是luaJIT,所以不能使用ffi也不能引用三方so的方法。

当然使用lua插件化的性能最佳的语言肯定是C,但是因为太菜了,所以只能以golang来实现,但是就目前观察看下来,处理性能还是可以的。

跳过介绍,使用说明点击跳转。

特点

插件编写灵活

简单的需求在配置文件中完成其实挺不错的,但是在一些较为复杂的需求面前,配置文件写出来的可能比较抽象,或者说为了简化配置就要为某个单独的需求专门在主项目里写一段专门用来处理的逻辑,可以是可以,但没必要。

在使用openresty一段时间后,发现灵活的插件真的会减轻不少的工作量。接下来基于一个相对复杂的小需求来进行插件编写,点击跳转插件示例。

需求:对5分钟内的访问状态码40x的ip进行针对统计,5分钟内超过100次的打上标签锁定10分钟,供WAF进行拦截。

这种肯定也可以在waf中写插件,但是当类似需求多了,那么一条请求处理就可能会产生多次请求,影响waf性能。

这样的话只让waf发起一条请求读取下分析结果就可以直接进行拦截,将工作量转移给旁路系统,不影响线上服务。

插件秒级生效

在线上环境运行示例风控插件,能涉及到的业务总QPS高峰大概有十万。(虽然是背着领导偷偷跑的,但是因为完全旁路于业务,所以问题不大。

插件目前使用主动监测的方式进行更新(说白了,for循环),但是其实可以使用inotify通过修改事件来驱动插件更新,我这里没写是因为我还没写完服务端更新的操作,vim编辑保存文件会删除旧文件创建新文件导致文件监控失败,有点憨批所以没搞。LogFarmer中实时传日志的方式就是使用事件驱动,实现比较简单。

插件更新时会自动编译缓存,供协程调用,避免每次都会要编译脚本运行。

动图中演示注释和运行打印日志方法来检测插件生效的速度。

基于golang+lua虚拟机的日志分析系统实现

灵活自定义的函数库

以打印日志为例

func logging(L *lua.LState) int {

    buf := new(bytes.Buffer)
    n := L.GetTop()

    for i := 2; i 

丰富的三方依赖支撑

golang能够使用的所有方法都可以被lua使用,通过如上的定义方式,添加进lua虚拟机供lua使用。

例如样例lua策略脚本中,使用的redis模块和方法实际是使用的golang内的redis三方库。

// 注册给lua虚拟机的golang函数
var rdsFns = map[string]lua.LGFunction{
        "incr":   incr,
        "hmget":  hmget,
        "hmset":  hmset,
        "expire": expire,
        "delete": delete,
    }
// redis的递增函数
func incr(L *lua.LState) int {

    var err error
    var result int64
    result, err = db.RedSess.HIncrBy(L.CheckString(1), L.CheckString(2), 1).Result()
    L.Push(lua.LNumber(result))
    pushErr(L, err)
    return 2
}

已内置的lua函数库和变量

-- 对于方法的调用,都是通过`.` 而不是`:`
-- 原因在与通过`:`调用方法会默认带和self的table作为第一个参数,所以避免每次调用函数都判断栈顶数据是不是这个table,就只用`.`好了。
-- example
local redis = require("redis")
local ok, err = redis.incr("key", "field")

内置全局变量

fbi

-- 项目变量
-- 下面包含var变量,类似openresty
local var = fbi.var
print(var.status) 
-- 项目回显打印 200

-- log方法和log等级参数
local log = fbi.log
local ERROR = fbi.ERROR
local DEBUG = fbi.DEBUG
local INFO = fbi.INFO
log(ERROR, "s1", "s2", "s3", ..., "sn") 
-- 项目日志中打印 [error] s1s2s3...sn
-- 写成lua的table是这样
fbi = {
    var = {
        __metatable = {
            __index = getVarFunctin
        }
    },
    log = logFunction,
    ERROR = level_error,
}

内置UserData变量

用于在单个lua协程中传递变量

access

类型是自定义的access日志GoStruct

pipeline

类型是redis.pipeliner

内置模块

redis

-- 类型都是lua中的类型。ok是bool类型,err是nil或者string类型,result是string或number类型,str是string类型

-- redis单条请求方法
local redis = require("redis")
-- 方法名都和redis方法类似
local result, err = redis.hmget(key, field)
local ok, err = redis.hmset(key, field, value)
local result, err = redis.incr(key, field)
local ok, err = redis.expire(key, second)
local ok, err = redis.delete(key)

-- redis批量请求方法
local redis = require("redis")
local pipeline = redis.pipeline
-- 新建一个pipeline
pipeline.new()
local result, err = pipeline.hmget(key, field)
local ok, err = pipeline.hmset(key, field, value)
local result, err = pipeline.incr(key, field)
local ok, err = pipeline.expire(key, second)
local ok, err = pipeline.delete(key)
local err = pipeline.exec()
pipeline.close()

re

-- 类型都是lua中的类型。ok是bool类型,err是nil或者string类型,str是string类型
-- 项目在定义给lua用的golang正则方法时,缓存了每个待匹配模式,比如"^ab",提升速度和性能
local re = require("re")
local ok, err = re.match("abcabcd", "^ab")
local str, err = re.find("abcabcd", "^ab")

time

local time = require("time")
local tu = time.unix() -- 时间戳
local tf = time.format() -- 格式化时间 2020-05-31 00:15
local zero = time.zero -- 1590829200, 基准时间,用于跟当前时间做差取余算时间段

项目运行流程和手册

说明

目前只写了kafka的数据输入,且日志格式为json,后期看情况加。

如需对接自家日志,需要在rule/struct.go中定义下日志格式,可以网上找json2gostrcut的转换;再在lua/http.go对照日志struct进行对应参数对接即可。

type AccessLog struct {
    Host    string  `json:"host"`    // WAF字段,域名
    Status  int     `json:"status"`  // WAF字段,状态码
    XFF     string  `json:"XFF"`     // WAF字段,X-Forwarded-for
    ...
}

// 注意下类型就好,lua里面数字都是number类型。
func GetReqVar(L *lua.LState) int {

    access := L.GetGlobal("access").(*lua.LUserData).Value.(*rule.AccessLog)
    _ = L.CheckAny(1)
    switch L.CheckString(2) {
    case "host":
        L.Push(lua.LString(access.Host))
    case "status":
        L.Push(lua.LNumber(access.Status))
    case "XFF":
        L.Push(lua.LString(access.XFF))
    ...
    default:
        L.Push(lua.LNil)
}

初次使用可通过打印一些变量来测试,例如

local var = fbi.var
local log = fbi.log
local ERROR = fbi.ERROR

log(ERROR, "status is ", tostring(var.status), ", req is ", var.host, var,uri, "?", var.query)
-- 可能输出 [error] status is 200, req is www.test.com/path/a?id=1

项目运行流程

基于golang+lua虚拟机的日志分析系统实现

安装

按照go.mod里的配置就行

kafka三方库需要安装librdkafka,参照
https://github.com/confluentinc/confluent-kafka-go#installing-librdkafka

redis三方库前几天刚更新,每个执行函数的参数都加了个ctx,如果不会改的话,go get 7.3版本即可
https://github.com/go-redis/redis/tree/v7

现阶段软件配置

日志源:Kafka

数据缓存:Redis

配置文件样例

# redis配置
redis: "127.0.0.1:6379"
password: ""
db: 9
# kafka配置
broker: 192.168.1.1:9092
groupid: group-access-test-v1
topic: 
- waflog
offset: latest
# 项目日志配置
path: Analyzer.log

使用方式

git clone https://github.com/C4o/FBI-Analyzer
go build main.go
./main

1.如果没有redis和kafka,没有关系,修改main.go的最后几行即可。通过print或log方法进行输出。

原始代码

// 初始化redis,连接和健康检查
    red := db.Redis{
        RedisAddr: conf.Cfg.RedAddr,
        RedisPass: conf.Cfg.RedPass,
        RedisDB:   conf.Cfg.DB,
    }
    // 初始化kafka配置
    kaf := db.Kafka{
        Broker:  conf.Cfg.Broker,
        GroupID: conf.Cfg.GroupID,
        Topic:   conf.Cfg.Topic,
        Offset:  conf.Cfg.Offset,
    }
    // 启动lua进程
    for i := 0; i 

更新代码

// 初始化redis,连接和健康检查
    //red := db.Redis{
    //    RedisAddr: conf.Cfg.RedAddr,
    //    RedisPass: conf.Cfg.RedPass,
    //    RedisDB:   conf.Cfg.DB,
    //}
    // 初始化kafka配置
    //kaf := db.Kafka{
    //Broker:  conf.Cfg.Broker,
    //GroupID: conf.Cfg.GroupID,
    //Topic:   conf.Cfg.Topic,
    //Offset:  conf.Cfg.Offset,
    //}
    // 启动lua进程
    for i := 0; i 

2.如果模块或参数使用不对,可在日志中查看lua脚本哪一行报错。

[root@localhost FBI-Analyzer]# cat Analyzer.log | grep "#" | head -n 5
2020/05/27 13:28:21 [error] Consumer error: 10.205.241.146:9092/bootstrap: Connect to ipv4#10.205.241.146:9092 failed: No route to host (after 4ms in state CONNECT) ()
2020/05/27 13:41:44 [error] coroutines failed : scripts/counter.lua:5: bad argument #3 to incr (value expected).
2020/05/27 13:41:49 [error] coroutines failed : scripts/counter.lua:5: bad argument #3 to incr (value expected).
2020/05/27 13:41:54 [error] coroutines failed : scripts/counter.lua:5: bad argument #3 to incr (value expected).
2020/05/27 13:41:59 [error] coroutines failed : scripts/counter.lua:5: bad argument #3 to incr (value expected).

本项目在现实中的应用

WAF体系

基于golang+lua虚拟机的日志分析系统实现

拦截中心

项目地址:https://github.com/C4o/IUS

实时日志传输模块

项目地址:https://github.com/C4o/LogFarmer


推荐阅读
  • 本文详细介绍了Java反射机制的基本概念、获取Class对象的方法、反射的主要功能及其在实际开发中的应用。通过具体示例,帮助读者更好地理解和使用Java反射。 ... [详细]
  • 普通树(每个节点可以有任意数量的子节点)级序遍历 ... [详细]
  • 小程序的授权和登陆
    小程序的授权和登陆 ... [详细]
  • python模块之正则
    re模块可以读懂你写的正则表达式根据你写的表达式去执行任务用re去操作正则正则表达式使用一些规则来检测一些字符串是否符合个人要求,从一段字符串中找到符合要求的内容。在 ... [详细]
  • 兆芯X86 CPU架构的演进与现状(国产CPU系列)
    本文详细介绍了兆芯X86 CPU架构的发展历程,从公司成立背景到关键技术授权,再到具体芯片架构的演进,全面解析了兆芯在国产CPU领域的贡献与挑战。 ... [详细]
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • C#实现文件的压缩与解压
    2019独角兽企业重金招聘Python工程师标准一、准备工作1、下载ICSharpCode.SharpZipLib.dll文件2、项目中引用这个dll二、文件压缩与解压共用类 ... [详细]
  • 我有一个从C项目编译的.o文件,该文件引用了名为init_static_pool ... [详细]
  • 本文节选自《NLTK基础教程——用NLTK和Python库构建机器学习应用》一书的第1章第1.2节,作者Nitin Hardeniya。本文将带领读者快速了解Python的基础知识,为后续的机器学习应用打下坚实的基础。 ... [详细]
  • 【妙】bug称它为数组越界的妙用
    1、聊一聊首先跟大家推荐一首非常温柔的歌曲,跑步的常听。本文主要把自己对C语言中柔性数组、零数组等等的理解分享给大家,并聊聊如何构建一种统一化的学习思想 ... [详细]
  • 浅析python实现布隆过滤器及Redis中的缓存穿透原理_python
    本文带你了解了位图的实现,布隆过滤器的原理及Python中的使用,以及布隆过滤器如何应对Redis中的缓存穿透,相信你对布隆过滤 ... [详细]
  • 探讨异步 Rust 中多线程代码无法实现并行化的原因及解决方案。 ... [详细]
  • 本文介绍如何使用OpenCV和线性支持向量机(SVM)模型来开发一个简单的人脸识别系统,特别关注在只有一个用户数据集时的处理方法。 ... [详细]
  • malloc 是 C 语言中的一个标准库函数,全称为 memory allocation,即动态内存分配。它用于在程序运行时申请一块指定大小的连续内存区域,并返回该区域的起始地址。当无法预先确定内存的具体位置时,可以通过 malloc 动态分配内存。 ... [详细]
  • 本文探讨了Go语言中iota关键字的具体含义及其在常量声明中的应用。 ... [详细]
author-avatar
大傻vv_528
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有