mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-30 17:05:19 +08:00
4e28fc60a3
- 通知渠道支持配置多个 SMTP 账号,配置整体加密落库,删除时清理规则与事件设置中的引用 - 告警规则支持 18 种指标:CPU/内存/Swap/负载/磁盘用量与 inode/磁盘与网卡速率/网站 5xx 与错误率、 服务与项目与容器与应用与数据库运行状态、证书与网站剩余天数,可配连续命中次数与静默期 - 系统事件通知覆盖证书续签失败、备份失败、后台任务失败、计划任务失败、网站到期、防篡改拦截、 面板健康问题、面板登录与爆破、SSH 登录与爆破 - 计划任务统一经 wrapper 执行以捕获退出码,flock 抢锁失败不计为失败 - 监控页改造为系统/告警/设置三 Tab - 数据库驱动建连打通 context 并补齐连接超时,探测不再阻塞告警评估 close #1041 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
115 lines
2.6 KiB
Go
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)
|
|
}
|