diff --git a/.gitignore b/.gitignore index adf8f72..a5c618e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,23 @@ -# ---> Go -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -# Go workspace file -go.work - +# ---> Go +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + diff --git a/README.md b/README.md index 5573b3a..d8a1ef8 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@ -# sysmonitord - -推翻重做的VigilGoGuard,更符合工程规范 - -## ToDo - -1. 用户登录监控 (ssh, su, sudo) - -2. 进程监控 (异常进程、资源占用) - -3. 文件监控 (关键文件变更) - -4. 网络连接监控 - -5. 系统资源监控 (CPU、内存、磁盘) - -6. 安全事件报警 +# sysmonitord + +重写VigilGoGuard,更符合工程规范 + +多线程支持、更加模块化,以及... + +## ToDo + +1. 用户登录监控 (ssh, su, sudo) + +2. 进程监控 (异常进程、资源占用) + +3. 文件监控 (关键文件变更) + +4. 网络连接监控 + +5. 系统资源监控 (CPU、内存、磁盘) + +6. 安全事件报警 diff --git a/cmd/sysmonitord/main.go b/cmd/sysmonitord/main.go new file mode 100644 index 0000000..a43a8e8 --- /dev/null +++ b/cmd/sysmonitord/main.go @@ -0,0 +1,70 @@ +package main + +import ( + "log" + "os" + "time" + + "github.com/wuko233/sysmonitord/internal/config" + "github.com/wuko233/sysmonitord/internal/monitor" +) + +func main() { + log.Println("启动sysmonitord...") + + cfg := &config.SSHMonitor{ + Enabled: true, + DisplayOnShell: true, + AlertOnRootLogin: true, + } + + log.Printf("加载SSH监控配置: %+v\n", cfg) + + alertChan := make(chan monitor.Alert, 100) + + log.Println("初始化SSH监控器...") + sshMonitor := monitor.NewSSHMonitor(cfg, alertChan) + + log.Println("启用告警处理...") + go handleAlerts(alertChan) + + go func() { + if err := sshMonitor.Start(); err != nil { + log.Fatalf("启动SSH监控器失败: %v", err) + } + }() + + time.Sleep(3 * time.Second) + + log.Println("启动sysmonitord完成.") + log.Println("sysmonitord正在运行...") + + log.Println("按Ctrl+C退出...") + + stopChan := make(chan os.Signal, 1) + + <-stopChan + + log.Println("停止SSH监控器...") + if err := sshMonitor.Stop(); err != nil { + log.Fatalf("停止SSH监控器失败: %v", err) + } + + time.Sleep(1 * time.Second) + + log.Println("sysmonitord已退出.") + +} + +func handleAlerts(alertChan <-chan monitor.Alert) { + for alert := range alertChan { + log.Printf("[告警] 类型: %s | 级别: %s | 时间: %s | 消息: %s | 数据: %+v\n", + alert.Type, alert.Level, alert.Timestamp.Format(time.RFC3339), alert.Message, alert.Data) + + switch alert.Type { + case "SSH_ROOT_LOGIN": + log.Println("ROOT用户登入") + // Todo: 接入发信接口 + } + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1f7b00c --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module github.com/wuko233/sysmonitord + +go 1.24.3 + +require ( + github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf +) \ No newline at end of file diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d186478 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..123525e --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,7 @@ +package config + +type SSHMonitor struct { + Enabled bool `yaml:"enabled"` + DisplayOnShell bool `yaml:"display_on_shell"` + AlertOnRootLogin bool `yaml:"alert_on_root_login"` +} diff --git a/internal/monitor/ssh_monitor.go b/internal/monitor/ssh_monitor.go new file mode 100644 index 0000000..cf7cb70 --- /dev/null +++ b/internal/monitor/ssh_monitor.go @@ -0,0 +1,172 @@ +package monitor + +import ( + "fmt" + "log" + "regexp" + "strings" + "time" + + "github.com/coreos/go-systemd/sdjournal" + "github.com/wuko233/sysmonitord/internal/config" +) + +func NewSSHMonitor(cfg *config.SSHMonitor, alertChan chan<- Alert) *SSHMonitor { + return &SSHMonitor{ + config: cfg, + alertChan: alertChan, + stopChan: make(chan struct{}), + } +} + +func (m *SSHMonitor) Start() error { + log.Println("启动SSH监控...") + return m.startJournalMonitor() +} + +func (m *SSHMonitor) Stop() error { + log.Println("停止SSH监控...") + close(m.stopChan) + if m.journal != nil { + m.journal.Close() + } + return nil +} + +func (m *SSHMonitor) startJournalMonitor() error { + journal, err := sdjournal.NewJournal() + if err != nil { + return fmt.Errorf("打开日志失败: %v", err) + } + m.journal = journal + + filters := []string{ + "_TRANSPORT=syslog", + "SYSLOG_IDENTIFIER=sshd", + } + + for _, filter := range filters { + if err := journal.AddMatch(filter); err != nil { + return fmt.Errorf("添加日志过滤器%s失败: %v", filter, err) + } + } + + if err := journal.SeekTail(); err != nil { + return fmt.Errorf("跳转到日志末尾失败: %v", err) + } + + m.monitorJournal() + + return nil +} + +func (m *SSHMonitor) monitorJournal() { + for { + select { + case <-m.stopChan: + return + + default: + count, err := m.journal.Next() + if err != nil { + log.Printf("读取日志错误: %v", err) + time.Sleep(time.Second) + continue + } + if count == 0 { + time.Sleep(100 * time.Millisecond) + continue + } + + entry, err := m.journal.GetEntry() + if err != nil { + log.Printf("获取日志条目错误: %v", err) + continue + } + + m.processLogEntry(entry.Fields) + } + } +} + +func (m *SSHMonitor) processLogEntry(field map[string]string) { + message, exists := field[sdjournal.SD_JOURNAL_FIELD_MESSAGE] + + if !exists { + return + } + + if strings.Contains(message, "Accepted") || + strings.Contains(message, "authentication failure") || + strings.Contains(message, "Failed password") { + event := m.parseSSHEvent(field, message) + if event != nil { + m.handleSSHEvent(event) + } + } +} + +func (m *SSHMonitor) parseSSHEvent(field map[string]string, message string) *SSHLoginEvent { + var event *SSHLoginEvent + + if strings.Contains(message, "Accepted") { + re := regexp.MustCompile(`Accepted (\S+) for (\S+) from ([\d\.:a-fA-F]+) port (\d+)`) + // \S+ 匹配非空白字符,[\d\.:a-fA-F]+ 匹配IP地址 + matches := re.FindStringSubmatch(message) + if matches != nil { + // timestamp := time.Unix(0, int64(field[sdjournal.SD_JOURNAL_FIELD_REALTIME_TIMESTAMP])*int64(time.Microsecond)) + timestamp := time.Now() + event = &SSHLoginEvent{ + Timestamp: timestamp, + Hostname: field["_HOSTNAME"], + Username: matches[2], + Method: matches[1], + SourceIP: matches[3], + Port: matches[4], + Service: field["SYSLOG_IDENTIFIER"], + PID: field["_PID"], + Message: message, + } + } + + } + + // Todo:处理其他情况 + + return event +} + +func (m *SSHMonitor) handleSSHEvent(event *SSHLoginEvent) { + if m.config.DisplayOnShell { + // 在终端显示事件 + m.displayEventOnShell(event) + } + + if event.Username == "root" && m.config.AlertOnRootLogin { + alert := Alert{ + Type: "SSH_ROOT_LOGIN", + Level: "HIGH", + Message: fmt.Sprintf("检测到来自%s的root登录", event.SourceIP), + Timestamp: time.Now(), + Data: event, + } + m.alertChan <- alert + } + + log.Printf("SSH登录事件: 用户=%s, 来源IP=%s, 方式=%s", + event.Username, event.SourceIP, event.Method) +} + +func (m *SSHMonitor) displayEventOnShell(event *SSHLoginEvent) { + fmt.Println("════════ SSH登录事件 ════════") + fmt.Printf("时间: %s\n", event.Timestamp.Format("2006-01-02 15:04:05")) + + fmt.Printf("主机: %s\n", event.Hostname) + fmt.Printf("用户: %s\n", event.Username) + fmt.Printf("方式: %s\n", event.Method) + fmt.Printf("来源IP: %s\n", event.SourceIP) + fmt.Printf("端口: %s\n", event.Port) + fmt.Printf("服务: %s (PID: %s)\n", event.Service, event.PID) + fmt.Printf("消息: %s\n", event.Message) + fmt.Println("════════════════════════════════") +} diff --git a/internal/monitor/types.go b/internal/monitor/types.go new file mode 100644 index 0000000..e686e0d --- /dev/null +++ b/internal/monitor/types.go @@ -0,0 +1,36 @@ +package monitor + +import ( + "time" + + "github.com/coreos/go-systemd/sdjournal" + "github.com/wuko233/sysmonitord/internal/config" +) + +type Alert struct { + Type string `json:"type"` // 告警类型 + Level string `json:"level"` // 告警级别 + Message string `json:"message"` // 告警消息 + Timestamp time.Time `json:"timestamp"` // 时间戳 + Data interface{} `json:"data"` // 附加数据 +} + +type SSHMonitor struct { + config *config.SSHMonitor + alertChan chan<- Alert + stopChan chan struct{} + journal *sdjournal.Journal +} + +type SSHLoginEvent struct { + Timestamp time.Time `json:"timestamp"` + + Hostname string `json:"hostname"` // 主机名 + Username string `json:"username"` // 用户名 + Method string `json:"method"` // 登录方式:password/publickey + SourceIP string `json:"source_ip"` // 来源IP + Port string `json:"port"` // 端口 + Service string `json:"service"` // 服务名 + PID string `json:"pid"` // 进程ID + Message string `json:"message"` // 原始日志消息 +} diff --git a/sysmonitord b/sysmonitord new file mode 100644 index 0000000..eff9781 Binary files /dev/null and b/sysmonitord differ