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("════════════════════════════════") }