基於golang+lua的Web日誌安全分析系統
FBI-Analyzer
FBI-Analyzer是一個靈活的日誌分析系統,基於golang和lua,插件風格類似ngx-lua。
使用者只需要編寫簡單的lua邏輯就可以實現golang能實現的所有需求,跳轉實現原理。現實中可作為WAF的輔助系統進行安全分析,跳轉實例。
可快速遷移waf中行為分析插件至本系統,避免插件在處理請求時發起過多對數據緩存的請求而導致WAF性能下降,幫助waf減負。
實現這個項目的目的其實也是加深下對lua虛擬機的認識,以及其他語言通過插件的方式調用lua腳本的工作原理,本項目因為只是單純的lua虛擬機,不是luaJIT,所以不能使用ffi也不能引用三方so的方法。
當然使用lua插件化的性能最佳的語言肯定是C,但是因為太菜了,所以只能以golang來實現,但是就目前觀察看下來,處理性能還是可以的。
跳過介紹,使用説明點擊跳轉。
特點 插件編寫靈活
簡單的需求在配置文件中完成其實挺不錯的,但是在一些較為複雜的需求面前,配置文件寫出來的可能比較抽象,或者説為了簡化配置就要為某個單獨的需求專門在主項目裏寫一段專門用來處理的邏輯,可以是可以,但沒必要。
在使用openresty一段時間後,發現靈活的插件真的會減輕不少的工作量。接下來基於一個相對複雜的小需求來進行插件編寫,點擊跳轉插件示例。
需求:對5分鐘內的訪問狀態碼40x的ip進行針對統計,5分鐘內超過100次的打上標籤鎖定10分鐘,供WAF進行攔截。
這種肯定也可以在waf中寫插件,但是當類似需求多了,那麼一條請求處理就可能會產生多次請求,影響waf性能。
這樣的話只讓waf發起一條請求讀取下分析結果就可以直接進行攔截,將工作量轉移給旁路系統,不影響線上服務。
插件秒級生效
在線上環境運行示例風控插件,能涉及到的業務總QPS高峯大概有十萬。,但是其實可以使用inotify通過修改事件來驅動插件更新,我這裏沒寫是因為我還沒寫完服務端更新的操作,vim編輯保存文件會刪除舊文件創建新文件導致文件監控失敗,有點憨批所以沒搞。LogFarmer中實時傳日誌的方式就是使用事件驅動,實現比較簡單。
插件更新時會自動編譯緩存,供協程調用,避免每次都會要編譯腳本運行。
動圖中演示註釋和運行打印日誌方法來檢測插件生效的速度。
靈活自定義的函數庫
以打印日誌為例
func logging int { buf := new n := L.GetTop for i := 2; i <>
golang能夠使用的所有方法都可以被lua使用,通過如上的定義方式,添加進lua虛擬機供lua使用。
例如樣例lua策略腳本中,使用的redis模塊和方法實際是使用的golang內的redis三方庫。
// 註冊給lua虛擬機的golang函數 var rdsFns = maplua.LGFunction{ "incr": incr, "hmget": hmget, "hmset": hmset, "expire": expire, "delete": delete, } // redis的遞增函數 func incr int { var err error var result int64 result, err = db.RedSess.HIncrBy, L.CheckString, 1).Result L.Push) pushErr return 2 } 已內置的lua函數庫和變量 -- 對於方法的調用,都是通過`.` 而不是`:` -- 原因在與通過`:`調用方法會默認帶和self的table作為第一個參數,所以避免每次調用函數都判斷棧頂數據是不是這個table,就只用`.`好了。 -- example local redis = require local ok, err = redis.incr 內置全局變量
fbi
-- 項目變量 -- 下面包含var變量,類似openresty local var = fbi.var print -- 項目回顯打印 200 -- log方法和log等級參數 local log = fbi.log local ERROR = fbi.ERROR local DEBUG = fbi.DEBUG local INFO = fbi.INFO log -- 項目日誌中打印 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方法類似 local result, err = redis.hmget local ok, err = redis.hmset local result, err = redis.incr local ok, err = redis.expire local ok, err = redis.delete -- redis批量請求方法 local redis = require local pipeline = redis.pipeline -- 新建一個pipeline pipeline.new local result, err = pipeline.hmget local ok, err = pipeline.hmset local result, err = pipeline.incr local ok, err = pipeline.expire local ok, err = pipeline.delete local err = pipeline.exec pipeline.close
re
-- 類型都是lua中的類型。ok是bool類型,err是nil或者string類型,str是string類型 -- 項目在定義給lua用的golang正則方法時,緩存了每個待匹配模式,比如"^ab",提升速度和性能 local re = require local ok, err = re.match local str, err = re.find
time
local time = require local tu = time.unix -- 時間戳 local tf = time.format -- 格式化時間 2020-05-31 00:15 local zero = time.zero -- 1590829200, 基準時間,用於跟當前時間做差取餘算時間段 項目運行流程和手冊 説明
目前只寫了kafka的數據輸入,且日誌格式為json,後期看情況加。
如需對接自家日誌,需要在中定義下日誌格式,可以網上找json2gostrcut的轉換;再在對照日誌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 int { access := L.GetGlobal..Value. _ = L.CheckAny switch L.CheckString { case "host": L.Push) case "status": L.Push) case "XFF": L.Push) ... default: L.Push }
初次使用可通過打印一些變量來測試,例如
local var = fbi.var local log = fbi.log local ERROR = fbi.ERROR log, ", req is ", var.host, var,uri, "?", var.query) -- 可能輸出 status is 200, req is www.test.com/path/a?id=1 項目運行流程
安裝 按照go.mod裏的配置就行 kafka三方庫需要安裝librdkafka,參照 https://github.com/confluentinc/confluent-kafka-go redis配置 redis: "127.0.0.1:6379" password: "" db: 9 項目日誌配置 path: Analyzer.log 使用方式 git clone https://github.com/C4o/FBI-Analyzer go build main.go ./main
1.如果沒有redis和kafka,沒有關係,修改main.go的最後幾行即可。通過print或log方法進行輸出。
原始代碼
更新代碼
2.如果模塊或參數使用不對,可在日誌中查看lua腳本哪一行報錯。
" / head -n 5 2020/05/27 13:28:21 Consumer error: 10.205.241.146:9092/bootstrap: Connect to ipv43 to incr . 2020/05/27 13:41:49 coroutines failed : s/counter.lua:5: bad argument 3 to incr . 2020/05/27 13:41:59 coroutines failed : s/counter.lua:5: bad argument #3 to incr . 本項目在現實中的應用 WAF體系
攔截中心 實時日誌傳輸模塊