feat: 初始实现SSH服务器监控系统

- 核心功能:监控SSH登录事件,检测root登录
- 日志解析:集成systemd journal实时监控
- 告警系统:支持root登录告警和通道处理
- 项目结构:规范的Go项目布局
- 文档:完善README使用说明"
This commit is contained in:
wk233 2026-01-06 21:21:45 +08:00
parent 91a22e79d9
commit e6d4a8303c
9 changed files with 338 additions and 40 deletions

46
.gitignore vendored
View File

@ -1,23 +1,23 @@
# ---> Go # ---> Go
# If you prefer the allow list template instead of the deny list, see community template: # 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 # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
# #
# Binaries for programs and plugins # Binaries for programs and plugins
*.exe *.exe
*.exe~ *.exe~
*.dll *.dll
*.so *.so
*.dylib *.dylib
# Test binary, built with `go test -c` # Test binary, built with `go test -c`
*.test *.test
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ # vendor/
# Go workspace file # Go workspace file
go.work go.work

View File

@ -1,17 +1,19 @@
# sysmonitord # sysmonitord
推翻重做的VigilGoGuard更符合工程规范 重写VigilGoGuard更符合工程规范
## ToDo 多线程支持、更加模块化,以及...
1. 用户登录监控 (ssh, su, sudo) ## ToDo
2. 进程监控 (异常进程、资源占用) 1. 用户登录监控 (ssh, su, sudo)
3. 文件监控 (关键文件变更) 2. 进程监控 (异常进程、资源占用)
4. 网络连接监控 3. 文件监控 (关键文件变更)
5. 系统资源监控 (CPU、内存、磁盘) 4. 网络连接监控
6. 安全事件报警 5. 系统资源监控 (CPU、内存、磁盘)
6. 安全事件报警

70
cmd/sysmonitord/main.go Normal file
View File

@ -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: 接入发信接口
}
}
}

7
go.mod Normal file
View File

@ -0,0 +1,7 @@
module github.com/wuko233/sysmonitord
go 1.24.3
require (
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
)

4
go.sum Normal file
View File

@ -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=

View File

@ -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"`
}

View File

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

36
internal/monitor/types.go Normal file
View File

@ -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"` // 原始日志消息
}

BIN
sysmonitord Normal file

Binary file not shown.