背景
上一篇文章写到了对redis缓存数据库的返回报文脱敏,但是存在一些缺陷,识别算法和脱敏算法需要自己去添加配置,理想的做法就是如果有一个组件可以提供处理数据的能力就好了,只需要把数据给他,然后就会自动识别数据类型并且做相应的脱敏算法处理,刚好前段时间还真就找到了这么一个项目,字节团队开源的godlp项目内置了大量的识别规则和脱敏算法,完美解决了这个需求,而godlp只开源了算法没有给大家开源实际应用所以就想着趁周末把者两个项目给组装起来整成一个实际可以的项目,两者结合互补提供了对方缺失的内容堪称完美。
为什么要选go
其实之前做这个方向的时候就已经发现了这个问题了,java运行太重量级了,工程本身也会占用很多资源,这种需要处理大量数据的业务对资源和效率要求还是比较高的,而且这种代码如果用于商业解析数据库内容就算是核心代码了,算是这个项目的难点,java编译后是可逆的,可以对class文件进行反编译,如果被人拿去反编译了这个项目的核心暴露了也就没有商业价值了,所以存在很多安全隐患,本来想用c++的,但是c++需要自己管理内存,如果没有处理好出现了内存泄漏可能只有凉凉,对于小白来说选择c++绝不是一个好的选择,而go语言似乎对Java程序员来说就比较友好了,即使是小白上手也不是很难,现在能静态编译语言的不多,但Go算一个,编译后是二进制码不可逆,解决了卖出去后源码容易暴露的问题,同时他保留了java的自身管理内存功能,而且本身也跨平台,不需要像java运行一样需要虚拟机和环境,自身占用太多的资源,在线程处理方面go语言采用了协程去同时处理任务也比java的线程更加轻量级,在这个业务场景go语言确实是一个不错的选择。
ps:没有吹go语言好说其他的语言不好的意思哈,都各有优缺点,只是个人感觉在这个场景更合适而已。
实现
因为前面的两篇文章已经实现了对mysql,postgrepsql和redis的数据脱敏的,原计划的是做另外一个比较流行的开源数据库mongodb的,由于周末耽误了一天没搞完所以就还是先搞redis的报文解析了,因为redis的报文解析是几种开源数据库中最简单的,初学者还是脚踏实地先一步步来吧,mongodb留着后面有空再搞了。
之前已经详细对比分析过做数据库端口代理各种方案的优缺点了,这里不做过多的概述,感兴趣的可以看上一篇(如何基于java代理对大数据缓存组件返回的数据进行脱敏和阻断)文章。
直接上代码,写完才发现go语言是真的简单轻便,由于这个是临时决定简化的所以保留了之前的一些计划的内容,后续再考虑实现,其实去掉保留内容和简化内容整体代码也就不到100行。
package main
import (
"fmt"
"net"
"strings"
"sync"
dlp "github.com/bytedance/godlp"
)
type ConnInfo struct {
//数据库类型:预留字段
dbType string
//会话的pid:预留字段
sessionId string
//发送的连接
sendConn net.Conn
//接收数据的连接
receConn net.Conn
//当前信息状态:预留字段
status int
}
var (
wg sync.WaitGroup
split string
splitB = []byte{13, 10}
)
func start() {
wg.Add(1)
split = string(splitB)
//与客户端的连接,其实是客户端需要连接的服务端
cliAdd, _ := net.ResolveTCPAddr("tcp4", "172.16.x.x:6666")
cliListen, _ := net.ListenTCP("tcp4", cliAdd)
fmt.Println("代理工具启动监听端口:", cliAdd)
//无限循环,接收到来自于客户端的连接的时候船舰一个函数去处理,暂时用chan通道去处理
chConn := make(chan ConnInfo)
go func() {
fmt.Println("准备开始监听通道")
dealChannel(chConn)
}()
for {
conn, _ := cliListen.Accept()
fmt.Println("接收到新请求")
//这里接收到一个客户端请求则直接创建一个与服务端相连接的请求
serAdd, _ := net.ResolveTCPAddr("tcp4", "172.16.x.x:6379")
serConn, _ := net.DialTCP("tcp4", nil, serAdd)
//封装服务端的连接和客户端的连接对象建立关系
serConnInfo := new(ConnInfo)
serConnInfo.sessionId = "readcli"
serConnInfo.receConn = conn
serConnInfo.sendConn = serConn
cliConnInfo := new(ConnInfo)
cliConnInfo.sessionId = "readser"
cliConnInfo.receConn = serConn
cliConnInfo.sendConn = conn
chConn <- *serConnInfo
chConn <- *cliConnInfo
}
}
func dealChannel(chConn chan ConnInfo) {
//这里无限循环一直从通道中去获取连接信息,目前这种处理方式可能并发性能差点,待研究深入一点再考虑更好的方案
for {
conInfo := <-chConn
//当从通道读取到连接信息的时候创建一个协程去读取数据,这样会一直创建协程,因为go语言的协程本来久很轻量级,不确定这么处理是否有问题,待后续深入了解了再改进
go func() {
//获取到通道的信息
data := make([]byte, 1024)
dlen, err := conInfo.receConn.Read(data)
if err != nil {
//如果出错大概率是因为某个通道被断开了,会话断开直接连接,通道中丢掉这个连接信息
fmt.Print("出现错误")
conInfo.receConn.Close()
conInfo.sendConn.Close()
} else {
//这里直接取了所有数据,如果返回的数据量大可能会分包,暂时不处理后续再想办法。
content := strings.Split(string(data[:dlen]), split)
for i, value := range content {
if strings.HasPrefix(value, "*") || strings.HasPrefix(value, "$") {
conInfo.sendConn.Write([]byte(value))
} else {
conInfo.sendConn.Write([]byte(maskString(value)))
}
if i < len(content)-1 {
conInfo.sendConn.Write(splitB)
}
}
//读取结束再把信息加入通道,等待下次循环再次读取
chConn <- conInfo
}
}()
}
}
//这里直接把数据交给godlp处理,至于能识别到什么内容需要看godlp只是什么,当然以前的项目是自定义的也没问题
func maskString(inStr string) (outStr string) {
caller := "replace.your.caller"
if eng, err := dlp.NewEngine(caller); err == nil {
eng.ApplyConfigDefault()
if outStr, _, err := eng.Deidentify(inStr); err == nil {
//fmt.Println(inStr, "--------->", outStr)
return outStr
}
eng.Close()
} else {
fmt.Println("[dlp] NewEngine error: ", err.Error())
}
return inStr
}
//启动的主方法
func main() {
start()
}
代码不多,本人go语言小白,刚学的所以质量和规范做的不好,为了学习练手就先整了个小东西出来至少有点收获,大家懂得大佬可以指点下一起交流下。
使用效果图:
这个图看过上一篇文章的小伙伴相信都已经看到过类似的效果了,不过区别在于上一篇文章是拦截脱敏是基于命令和key脱敏的,具体脱敏哪些需要自己配置,而这一篇文章不一样,直接脱敏所有结果,所有内容都是交给dlp处理的(如果dlp没有对数据处理那另说),可靠性这些需要自己验证,毕竟众口难调还是需要根据自己的业务去配置规则和验证的,这里为了方便就直接用内置的规则了。
思考
如果是代理redis集群各个节点之前的无缝切换能实现吗,这个问题不做回答,留着给大家实验一下就知道了。
温馨提示
这玩意虽然用起来方便,几十行代码好像就可以做一个很重要的事了,但是用不用还是要斟酌一哈,因为这个是基于返回内容识别,所有的内容都需要交给dlp去通过他们的算法识别并脱敏,虽然安全(都交给dlp了)这个时间是无法预估的,返回结果刚好又是一个很大数据量的东西所以这么处理还是有问题的,这种对缓存的代理肯定还是不希望太慢了。理想状态最好还是根据key进行识别,只有敏感的key才对内容进行处理能节省很多时间(安全和方便本来就是互相冲突的,想要马儿跑就要给马儿吃草),本人学习项目,大家图个乐呵就好。