feat: 初始实现SSH服务器监控系统
- 核心功能:监控SSH登录事件,检测root登录 - 日志解析:集成systemd journal实时监控 - 告警系统:支持root登录告警和通道处理 - 项目结构:规范的Go项目布局 - 文档:完善README使用说明"
This commit is contained in:
parent
91a22e79d9
commit
e6d4a8303c
|
|
@ -1,6 +1,8 @@
|
||||||
# sysmonitord
|
# sysmonitord
|
||||||
|
|
||||||
推翻重做的VigilGoGuard,更符合工程规范
|
重写VigilGoGuard,更符合工程规范
|
||||||
|
|
||||||
|
多线程支持、更加模块化,以及...
|
||||||
|
|
||||||
## ToDo
|
## ToDo
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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