Commit 095e09e6 authored by liming6's avatar liming6
Browse files

feature 添加文件上传监控相关代码

parent d198770e
# 监控scp创建文件动作
-a always,exit -F arch=b64 -S openat -F exe=/usr/bin/scp -F 'a2&0x40' -F key=scp_create_file
-a always,exit -F arch=b64 -S open -F exe=/usr/bin/scp -F 'a1&0x40' -F key=scp_create_file
# 监控scp关闭文件动作,通常表示文件已经写入完成
-a always,exit -F arch=b64 -S close -F exe=/usr/bin/scp -F key=scp_close_file
# 监控sftp创建文件动作
-a always,exit -F arch=b64 -S openat -F exe=/usr/libexec/openssh/sftp-server -F 'a2&0x40' -F key=sftp_create_file
-a always,exit -F arch=b64 -S open -F exe=/usr/libexec/openssh/sftp-server -F 'a1&0x40' -F key=sftp_create_file
# 监控sftp关闭文件动作,通常表示文件已经写入完成
-a always,exit -F arch=b64 -S close -F exe=/usr/libexec/openssh/sftp-server -F key=sftp_close_file
# 监控sftp修改文件名动作
-a always,exit -F arch=b64 -S rename,renameat,renameat2 -F exe=/usr/libexec/openssh/sftp-server -F key=sftp_rename_file
-a always,exit -F arch=b64 -S link,unlink,unlinkat,linkat -F exe=/usr/libexec/openssh/sftp-server -F key=sftp_link_file
\ No newline at end of file
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
"github.com/elastic/go-libaudit/v2"
"github.com/elastic/go-libaudit/v2/aucoalesce"
"github.com/elastic/go-libaudit/v2/auparse"
)
type EventHandler struct{}
func (h *EventHandler) ReassemblyComplete(msgs []*auparse.AuditMessage) {
event, err := aucoalesce.CoalesceMessages(msgs)
if err != nil {
fmt.Printf("coalesce messages error: %v", err)
}
fmt.Println("---------------------------")
j, _ := json.MarshalIndent(event, "", " ")
fmt.Printf("%s\n", string(j))
fmt.Println("---------------------------")
}
func (h *EventHandler) EventsLost(count int) {
fmt.Fprintf(os.Stderr, "=== event lost: %d \n", count)
}
func main() {
cli, err := libaudit.NewMulticastAuditClient(nil)
if err != nil {
log.Fatalf("failed to create audit client: %v", err)
}
defer cli.Close()
handler := &EventHandler{}
rea, err := libaudit.NewReassembler(256, time.Second*2, handler)
if err != nil {
log.Printf("%v", err)
return
}
defer rea.Close()
go func() {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for range ticker.C {
if rea.Maintain() != nil {
break
}
}
}()
for {
rawMsg, err := cli.Receive(false)
if err != nil {
break
}
_ = rea.Push(rawMsg.Type, rawMsg.Data)
}
}
<86>Jan 27 16:00:36 admin02 sftp-server[3761842]: opendir "/tmp"
<86>Jan 27 16:00:36 admin02 sftp-server[3761842]: closedir "/tmp"
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: open "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" flags WRITE,CREATE mode 0666
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: close "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" bytes read 0 written 1412082
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: remove name "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar"
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: sent status No such file
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: rename old "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" new "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar"
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: opendir "/tmp"
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: closedir "/tmp"
<86>Jan 27 16:48:22 admin02 sftp-server[3965704]: open "/tmp/id_ed25519" flags WRITE,CREATE,TRUNCATE mode 0666
<86>Jan 27 16:48:22 admin02 sftp-server[3965704]: close "/tmp/id_ed25519" bytes read 0 written 419
<86>Jan 27 16:48:22 admin02 sftp-server[3965052]: opendir "/tmp"
<86>Jan 27 16:48:23 admin02 sftp-server[3965052]: closedir "/tmp"
<86>Jan 27 16:48:53 admin02 sftp-server[3965052]: remove name "/tmp/id_ed25519"
<86>Jan 27 16:48:53 admin02 sftp-server[3965052]: opendir "/tmp"
<86>Jan 27 16:48:53 admin02 sftp-server[3965052]: closedir "/tmp"
\ No newline at end of file
package logic
import (
"fmt"
"os"
"sshd-tool/utils"
)
var (
NeedExec = []string{"chattr", "lsattr", "clamscan", "clamdscan"}
)
// CheckExec 检查必要的命令是否存在
func CheckExec() error {
for _, v := range NeedExec {
if p := utils.FindCmd(v); p == nil {
return fmt.Errorf("command %s not found", v)
}
}
return nil
}
// CheckRoot 检查是否以root的身份执行
func CheckRoot() bool {
return os.Getuid() == 0
}
package logic
import (
"testing"
"time"
)
func TestReg1(t *testing.T) {
itmes := []string{
`<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: rename old "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" new "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar"`,
}
for _, v := range itmes {
if ReMatch.MatchString(v) {
t.Logf("match: %s", v)
fields := ReMatch.FindStringSubmatch(v)
tt, err := time.Parse("Jan _2 15:04:05", fields[1])
if err != nil {
t.Log("parse time error!")
} else {
tn := time.Date(time.Now().Year(), tt.Month(), tt.Day(), tt.Hour(), tt.Minute(), tt.Second(), 0, time.Local)
t.Logf("time is %v", tn)
}
for ki, vi := range fields {
t.Logf(" %d: %v", ki, vi)
}
} else {
t.Logf("not match: %s", v)
}
}
}
func TestParseItem(t *testing.T) {
itmes := []string{
`<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: rename old "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" new "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar"`,
`<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: open "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" flags WRITE,CREATE mode 0666`,
`<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: close "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" bytes read 0 written 1412082`,
}
for _, v := range itmes {
i, err := ParseItem(v)
if err != nil {
t.Error(err)
}
if i != nil {
t.Logf("%+v", i)
} else {
t.Logf("not match: %s", v)
}
}
}
package logic
import (
"fmt"
"log"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/shirou/gopsutil/v4/process"
)
/*
思路:由于tabby比较特殊,所以分两类:普通sftp客户端日志和tabby客户端日志
普通:
<86>Jan 27 16:48:22 admin02 sftp-server[3965704]: open "/tmp/id_ed25519" flags WRITE,CREATE,TRUNCATE mode 0666
<86>Jan 27 16:48:22 admin02 sftp-server[3965704]: close "/tmp/id_ed25519" bytes read 0 written 419
解析到open动作以及CREATE标志,记录文件位置;等到解析到同文件的close动作表示写文件完成,立即扫描该文件
tabby:
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: open "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" flags WRITE,CREATE mode 0666
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: close "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" bytes read 0 written 1412082
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: remove name "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar"
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: sent status No such file
<86>Jan 27 16:00:53 admin02 sftp-server[3761842]: rename old "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar.tabby-upload" new "/tmp/X680G55_9800195108886297_20251230-012816_bmcblackinfo.tar"
解析到open动作以及CREATE标志,记录文件位置;等到解析到同文件的rename动作表示写文件完成,立即扫描该文件
*/
var (
ReMatch = regexp.MustCompile(`<\d+>([A-Z][a-z]+\s+\d+\s+\d+:\d+:\d+)\s+[a-zA-Z0-9]+\s+sftp-server\[(\d+)\]:\s+([a-z]+)\s+(.*)$`)
LogItemMap = make(map[string][]*SftpLogItem) // key为文件名,value为两个对象指针,一个记录文件上传开始,一个记录文件上传结束
LogItemMapLock = sync.RWMutex{}
)
type Action int
const (
ActionOpenDir Action = iota
ActionCloseDir
ActionOpenFile
ActionCloseFile
ActionRemove
ActionRename
)
const (
TabbySuffix = ".tabby-upload"
ScanSuffix = ".scanning"
)
// SftpLogItem sftp日志类型,Flag的key可以为:name(remove) old(rename) new(rename) flags(open) mode(open) read(close) written(close)
type SftpLogItem struct {
Pid int
UserInfo string // uid或用户名
Time time.Time
Act Action
Target string // 动作的目标,针对open、close,是文件路径,针对rename,是文件原名
Flag map[string]any // 动作的参数,针对open、close、rename才有
IsTabby bool // 针对tabby需要特殊处理
}
// FinalFile 获取最终文件路径
func (sli *SftpLogItem) FinalFile() (string, error) {
if !sli.IsTabby {
return sli.Target, nil
}
switch sli.Act {
case ActionOpenFile, ActionCloseFile:
return strings.TrimSuffix(sli.Target, TabbySuffix), nil
case ActionRename:
if sli.Flag == nil {
return strings.TrimSuffix(sli.Target, TabbySuffix), nil
}
nname, have := sli.Flag["new"]
if !have {
return strings.TrimSuffix(sli.Target, TabbySuffix), nil
}
return nname.(string), nil
default:
return "", fmt.Errorf("unexcept action: %d", sli.Act)
}
}
func ParseItem(str string) (*SftpLogItem, error) {
str = strings.Trim(strings.Trim(str, "\n"), " ")
fields := ReMatch.FindStringSubmatch(str)
if len(fields) == 0 {
return nil, nil
}
var act Action
switch fields[3] {
case "open":
act = ActionOpenFile
case "close":
act = ActionCloseFile
case "rename":
act = ActionRename
default:
return nil, nil
}
time_raw, err := time.Parse("Jan _2 15:04:05", fields[1])
if err != nil {
return nil, err
}
time_raw = time_raw.AddDate(time.Now().Year()-time_raw.Year(), 0, 0)
pid, err := strconv.Atoi(fields[2])
if err != nil {
return nil, err
}
proc, err := process.NewProcess(int32(pid))
if err != nil {
return nil, err
}
uinfo, err := proc.Username()
if err != nil {
uinfo = fmt.Sprintf("%d",proc.Uids())
} else {
}
result := SftpLogItem{Act: act, Time: time_raw, Pid: pid}
switch result.Act {
case ActionOpenFile:
err = parseOpen(fields[4], &result)
case ActionCloseFile:
err = parseClose(fields[4], &result)
case ActionRename:
err = parseRename(fields[4], &result)
}
return &result, err
}
// parseClose
func parseClose(str string, target *SftpLogItem) error {
str = strings.Trim(strings.Trim(str, "\n"), " ")
fields := strings.Fields(str)
last := ""
for _, v := range fields {
if strings.HasPrefix(v, `"`) && strings.HasSuffix(v, `"`) {
target.Target = strings.Trim(v, `"`)
target.IsTabby = strings.HasSuffix(target.Target, TabbySuffix)
last = v
continue
}
switch v {
case "bytes", "read", "written":
last = v
default:
i, err := strconv.Atoi(v)
if err != nil {
last = v
break
}
if last != "" {
if target.Flag == nil {
target.Flag = make(map[string]any)
}
target.Flag[last] = i
last = ""
} else {
return fmt.Errorf("unknown parse close log: %s", str)
}
}
}
return nil
}
func parseOpen(str string, target *SftpLogItem) error {
str = strings.Trim(strings.Trim(str, "\n"), " ")
fields := strings.Fields(str)
last := ""
for _, v := range fields {
if strings.HasPrefix(v, `"`) && strings.HasSuffix(v, `"`) {
target.Target = strings.Trim(v, `"`)
target.IsTabby = strings.HasSuffix(target.Target, TabbySuffix)
last = v
continue
}
switch v {
case "flags", "mode":
last = v
default:
if last != "" {
if target.Flag == nil {
target.Flag = make(map[string]any)
}
target.Flag[last] = v
last = ""
} else {
return fmt.Errorf("unknown parse open log: %s", str)
}
}
}
return nil
}
func parseRename(str string, target *SftpLogItem) error {
str = strings.Trim(strings.Trim(str, "\n"), " ")
fields := strings.Fields(str)
last := ""
for _, v := range fields {
switch v {
case "old", "new":
last = v
default:
if strings.HasPrefix(v, `"`) && strings.HasSuffix(v, `"`) {
if last != "" {
if target.Flag == nil {
target.Flag = make(map[string]any)
}
target.Flag[last] = strings.Trim(v, `"`)
if last == "old" {
target.Target = strings.Trim(v, `"`)
target.IsTabby = strings.HasSuffix(target.Target, TabbySuffix)
}
last = ""
} else {
return fmt.Errorf("unknown parse rename log: %s", str)
}
last = v
} else {
return fmt.Errorf("unknown parse rename log: %s", str)
}
}
}
return nil
}
// HandleSftpLog 处理sftp日志
func HandleSftpLog(str string) {
item, err := ParseItem(str)
if err != nil {
log.Printf("parse sftp log error,log: %s, err: %v", str, err)
}
if item == nil {
return
}
switch item.Act {
case ActionOpenFile:
if item.Flag == nil {
return
}
flag, have := item.Flag["flags"]
if !have {
return
}
f := flag.(string)
if strings.Contains(f, "CREATE") {
LogItemMapLock.Lock()
ii := make([]*SftpLogItem, 0, 2)
ii = append(ii, item)
LogItemMap[item.Target] = ii
LogItemMapLock.Unlock()
}
case ActionCloseFile:
if item.IsTabby {
return
}
rl := LogItemMapLock.RLocker()
rl.Lock()
i, have := LogItemMap[item.Target]
rl.Unlock()
if !have {
log.Printf("close file but not found open: %s", item.Target)
return
}
i[1] = item
scanFile(i)
case ActionRename:
if !item.IsTabby {
return
}
rl := LogItemMapLock.RLocker()
rl.Lock()
i, have := LogItemMap[item.Target]
rl.Unlock()
if !have {
log.Printf("rename file but not found open: %s", item.Target)
return
}
i[1] = item
scanFile(i)
}
}
func scanFile(logItems []*SftpLogItem) {
if len(logItems) < 2 {
return
}
// // 记录权限
filePath, err := logItems[0].FinalFile()
if err != nil {
log.Printf("get final file path error: %v", err)
return
}
fileStat, err := os.Stat(filePath)
fileMode := fileStat.Mode()
os.Chmod(filePath, 0000)
// 修改名称
reFilePath := fmt.Sprintf("%s%s", filePath, ScanSuffix)
os.Rename(filePath, reFilePath)
// 扫描文件
cmd := exec.Command("clamdscan", "-i", "--no-summary", reFilePath)
output, err := cmd.CombinedOutput()
if cmd.ProcessState.ExitCode() != 0 {
// 有问题
content := string(output)
log.Printf("find virus file: %s", content)
} else {
// 没问题
os.Rename(reFilePath, filePath)
os.Chmod(filePath, fileMode)
}
}
package main
import (
"fmt"
"log"
"net"
"os/exec"
"sshd-tool/cmd/sftp-monitor/logic"
"strings"
"github.com/gofrs/flock"
)
const (
SocketPath = "/var/run/authpriv.sock"
FileLockPath = "/tmp/sftp-monitor.pid"
)
func main() {
fileLock := flock.New(FileLockPath, flock.SetPermissions(0644))
l, err := fileLock.TryLock()
if err != nil {
log.Fatalf("error lock file %s, %v", fileLock, err)
}
if !l {
log.Fatalf("can't lock %s, Perhaps an instance is already running.", FileLockPath)
}
// 删除旧
_ = exec.Command("rm", "-f", SocketPath).Run()
conn, err := net.ListenPacket("unixgram", SocketPath)
if err != nil {
log.Fatalf("can't listen unix socket: %v", err)
}
buff := make([]byte, 16384)
for {
n, _, err := conn.ReadFrom(buff)
if err != nil {
log.Fatalf("read error: %v", err)
break
}
content := string(buff[:n])
if strings.Contains(content, "sftp-server") {
fmt.Println(content)
go logic.HandleSftpLog(content)
}
}
}
......@@ -2,7 +2,23 @@ module sshd-tool
go 1.24.9
require github.com/gin-gonic/gin v1.11.0
require (
github.com/elastic/go-libaudit/v2 v2.6.2
github.com/gin-gonic/gin v1.11.0
)
require (
github.com/ebitengine/purego v0.9.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
require (
github.com/bytedance/sonic v1.14.0 // indirect
......@@ -25,6 +41,7 @@ require (
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect
github.com/shirou/gopsutil/v4 v4.25.12
github.com/spf13/pflag v1.0.10
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
......@@ -34,7 +51,7 @@ require (
golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/tools v0.34.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect
......
......@@ -4,14 +4,26 @@ github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZw
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/elastic/go-libaudit/v2 v2.6.2 h1:1PM6wVBTJHJQYsKl8jfA9/Aw9pFty5uUezPiUfKtOI4=
github.com/elastic/go-libaudit/v2 v2.6.2/go.mod h1:8205nkf2oSrXFlO4H5j8/cyVMoSF3Y7jt+FjgS4ubQU=
github.com/elastic/go-licenser v0.4.1 h1:1xDURsc8pL5zYT9R29425J3vkHdt4RT5TNEMeRN48x4=
github.com/elastic/go-licenser v0.4.1/go.mod h1:V56wHMpmdURfibNBggaSBfqgPxyT1Tldns1i87iTEvU=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
......@@ -24,13 +36,24 @@ github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
......@@ -39,11 +62,18 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
......@@ -53,10 +83,18 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
......@@ -69,17 +107,21 @@ golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment