- 核心功能:监控SSH登录事件,检测root登录 - 日志解析:集成systemd journal实时监控 - 告警系统:支持root登录告警和通道处理 - 项目结构:规范的Go项目布局 - 文档:完善README使用说明"
173 lines
4.2 KiB
Go
173 lines
4.2 KiB
Go
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("════════════════════════════════")
|
||
}
|