This repository has been archived on 2026-03-28. You can view files and clone it, but cannot push or open issues or pull requests.
old-sysmonitord/internal/monitor/ssh_monitor.go
2026-03-22 12:16:48 +08:00

173 lines
4.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package monitor
import (
"fmt"
"log"
"regexp"
"strings"
"time"
"github.com/coreos/go-systemd/v22/sdjournal"
"github.com/wuko233/sysmonitord/internal/config"
)
func NewSSHMonitor(cfg *config.SSHMonitorConfig, 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.AlertOnRootLogin {
// 在终端显示事件
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("════════════════════════════════")
}