Files
panel/pkg/sshlog/sshlog.go
T
耗子 4e28fc60a3 feat: 添加通知与告警模块
- 通知渠道支持配置多个 SMTP 账号,配置整体加密落库,删除时清理规则与事件设置中的引用
- 告警规则支持 18 种指标:CPU/内存/Swap/负载/磁盘用量与 inode/磁盘与网卡速率/网站 5xx 与错误率、
  服务与项目与容器与应用与数据库运行状态、证书与网站剩余天数,可配连续命中次数与静默期
- 系统事件通知覆盖证书续签失败、备份失败、后台任务失败、计划任务失败、网站到期、防篡改拦截、
  面板健康问题、面板登录与爆破、SSH 登录与爆破
- 计划任务统一经 wrapper 执行以捕获退出码,flock 抢锁失败不计为失败
- 监控页改造为系统/告警/设置三 Tab
- 数据库驱动建连打通 context 并补齐连接超时,探测不再阻塞告警评估

close #1041

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 22:45:51 +08:00

115 lines
2.6 KiB
Go

// Package sshlog 解析 sshd 登录日志
package sshlog
import (
"bufio"
"bytes"
"regexp"
"strings"
"time"
"github.com/acepanel/panel/v3/pkg/types"
)
// 登录记录状态
const (
StatusAccepted = "accepted"
StatusFailed = "failed"
StatusInvalidUser = "invalid_user"
StatusDisconnected = "disconnected"
)
var (
accepted = regexp.MustCompile(`Accepted\s+(\S+)\s+for\s+(\S+)\s+from\s+(\S+)\s+port\s+(\d+)`)
failed = regexp.MustCompile(`Failed\s+(\S+)\s+for\s+(?:invalid user\s+)?(\S+)\s+from\s+(\S+)\s+port\s+(\d+)`)
invalidUser = regexp.MustCompile(`Invalid user\s+(\S+)\s+from\s+(\S+)\s+port\s+(\d+)`)
disconnect = regexp.MustCompile(`Disconnected from\s+(?:authenticating\s+)?user\s+(\S+)\s+(\S+)\s+port\s+(\d+)`)
)
// ParseMessage 从日志消息中提取 SSH 登录信息,无法识别时返回 nil
func ParseMessage(msg string) *types.SSHLoginLog {
if m := accepted.FindStringSubmatch(msg); m != nil {
return &types.SSHLoginLog{
Method: m[1],
User: m[2],
IP: m[3],
Port: m[4],
Status: StatusAccepted,
}
}
if m := failed.FindStringSubmatch(msg); m != nil {
return &types.SSHLoginLog{
Method: m[1],
User: m[2],
IP: m[3],
Port: m[4],
Status: StatusFailed,
}
}
if m := invalidUser.FindStringSubmatch(msg); m != nil {
return &types.SSHLoginLog{
User: m[1],
IP: m[2],
Port: m[3],
Method: "-",
Status: StatusInvalidUser,
}
}
if m := disconnect.FindStringSubmatch(msg); m != nil {
return &types.SSHLoginLog{
User: m[1],
IP: m[2],
Port: m[3],
Method: "-",
Status: StatusDisconnected,
}
}
return nil
}
// ParseChunk 从连续日志字节中解析 SSH 登录记录
func ParseChunk(data []byte) []types.SSHLoginLog {
if len(data) == 0 {
return nil
}
var logs []types.SSHLoginLog
scanner := bufio.NewScanner(bytes.NewReader(data))
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
for scanner.Scan() {
line := scanner.Text()
if !strings.Contains(line, "sshd[") {
continue
}
record := ParseMessage(line)
if record == nil {
continue
}
record.Time = ParseTime(line)
logs = append(logs, *record)
}
return logs
}
// ParseTime 从 syslog 格式行中解析时间,失败返回 "-"
func ParseTime(line string) string {
// syslog 格式:Mon DD HH:MM:SS(前 15 个字符)
if len(line) < 15 {
return "-"
}
ts := line[:15]
// 使用当前年份补全
t, err := time.Parse("Jan 2 15:04:05", ts)
if err != nil {
t, err = time.Parse("Jan 2 15:04:05", ts)
if err != nil {
return "-"
}
}
return t.AddDate(time.Now().Year(), 0, 0).Format(time.DateTime)
}