feat: 初始实现SSH服务器监控系统
- 核心功能:监控SSH登录事件,检测root登录 - 日志解析:集成systemd journal实时监控 - 告警系统:支持root登录告警和通道处理 - 项目结构:规范的Go项目布局 - 文档:完善README使用说明"
This commit is contained in:
parent
91a22e79d9
commit
e6d4a8303c
|
|
@ -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
|
||||
|
||||
|
|
|
|||
36
README.md
36
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. 安全事件报警
|
||||
|
|
|
|||
|
|
@ -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: 接入发信接口
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
module github.com/wuko233/sysmonitord
|
||||
|
||||
go 1.24.3
|
||||
|
||||
require (
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||
)
|
||||
|
|
@ -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=
|
||||
|
|
@ -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"`
|
||||
}
|
||||
|
|
@ -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("════════════════════════════════")
|
||||
}
|
||||
|
|
@ -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"` // 原始日志消息
|
||||
}
|
||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user