【背景】最近要做一个防篡改的功能,一开始是采用事件型的方式实现的,结果发现会有一种情况"如果某个文件短时间一直被外部进行多次恶意操作"时,一直防也不是个事,应该在加一层防护—文件锁定,这样就舒服多了
【好处】避免短时间站点被频繁攻击,保证安全性的同时,尽量保证业务正常运行
【实现思路】
一开始是想着用全局map去存储每一个文件的修改次数和最后一次时间,后面发现这样子操作,需要注意内存释放问题,一不小心就会导致内存泄漏的情况。
所以盯上了一个好东西——优先队列,通过结构体进行实现
【具体实现逻辑】
先定义了两个结构体和一个数据结构,FileInfo
用于存储文件的修改次数和最后修改时间,UnlockTask
用于存储待解锁的文件名和预定解锁时间。
type FileInfo struct {
ModCount int
LastMod time.Time
}
type UnlockTask struct {
FileName string
Time time.Time
}
type UnlockQueue []*UnlockTask
再写下实现heap.Interface接口的方法,主要用于操作优先队列,用堆操作(如Push和Pop)来管理队列。
func (q UnlockQueue) Len() int { return len(q) }
func (q UnlockQueue) Less(i, j int) bool {
return q[i].Time.Before(q[j].Time)
}
func (q UnlockQueue) Swap(i, j int) {
q[i], q[j] = q[j], q[i]
}
func (q *UnlockQueue) Push(x interface{}) {
item := x.(*UnlockTask)
*q = append(*q, item)
}
func (q *UnlockQueue) Pop() interface{} {
old := *q
n := len(old)
item := old[n-1]
*q = old[0 : n-1]
return item
}
主实现函数:
-
首先,
OnFileModified
函数会在文件被修改时被调用,更新文件的修改次数和最后修改时间。 -
然后,
CheckFileModCount
函数定时检查文件的修改次数,如果在5秒内文件被修改了10次,就会调用lockFile
函数锁定文件,并将其添加到优先队列中,预定在60秒后解锁。 -
最后,
CheckUnlockQueue
函数会检查优先队列,如果到了预定的解锁时间,就会调用unlockFile
函数解锁文件。
// 在文件被篡改时调用这个函数
func OnFileModified(fileName string) {
info, ok := fileMap[fileName]
if !ok {
info = &FileInfo{ModCount: 0, LastMod: time.Now()}
fileMap[fileName] = info
}
//fmt.Printf("---检测该文件【%s】被篡改,次数+1", fileName)
info.ModCount++
info.LastMod = time.Now()
}
// 定时检查文件修改次数
func CheckFileModCount() {
for fileName, info := range fileMap {
if time.Since(info.LastMod).Seconds() < 5 && info.ModCount >= 10 {
fmt.Printf("---检测到文件【%s】在5s内,被篡改了10次,即将封锁该文件", fileName)
lockFile(fileName)
queueLock.Lock()
heap.Push(&unlockQueue, &UnlockTask{FileName: fileName, Time: time.Now().Add(60 * time.Second)})
queueLock.Unlock()
}
}
}
// 检查解锁队列
func CheckUnlockQueue() {
queueLock.Lock()
for unlockQueue.Len() > 0 && time.Now().After(unlockQueue[0].Time) {
task := heap.Pop(&unlockQueue).(*UnlockTask)
unlockFile(task.FileName)
}
queueLock.Unlock()
}
// 锁定文件
func lockFile(fileName string) {
cmd := exec.Command("chattr", "+i", fileName)
err := cmd.Run()
if err != nil {
fmt.Printf("Error locking file %s: %v\n", fileName, err)
}
}
// 解锁文件
func unlockFile(fileName string) {
cmd := exec.Command("chattr", "-i", fileName)
err := cmd.Run()
if err != nil {
fmt.Printf("Error unlocking file %s: %v\n", fileName, err)
}
}
main.go 主调用函数
func main() {
heap.Init(&core.UnlockQueue{})
// 当文件确定被篡改时,调用这个函数
core.OnFileModified(event.Name)
go func() {
for {
core.CheckFileModCount()
core.CheckUnlockQueue()
time.Sleep(1 * time.Second)
}
}()
}