service.xpcool.com/internal/service/serversecurity/security.go

156 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package serversecurity 提供服务器安全监控日志领域服务(采集上报/查询/统计)。
package serversecurity
import (
"context"
"strings"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"service.xpcool.com/internal/consts"
"service.xpcool.com/internal/dao"
"service.xpcool.com/internal/library/response"
"service.xpcool.com/internal/model/dto"
)
// ISecurity 服务器安全日志领域服务接口。
type ISecurity interface {
Report(context.Context, string, []dto.SecurityLogInput) (int, error)
List(context.Context, dto.SecurityLogFilter) ([]dto.SecurityLogVO, int, error)
Stats(context.Context, string, string) (*dto.SecurityStats, error)
}
type security struct{}
var localSecurity ISecurity
// New 创建安全日志服务实现。
func New() ISecurity { return &security{} }
// Security 返回已注册的安全日志服务实现。
func Security() ISecurity {
if localSecurity == nil {
panic("Security implementation not registered")
}
return localSecurity
}
// RegisterServerSecurity 注册安全日志服务实现。
func RegisterServerSecurity(i ISecurity) { localSecurity = i }
// Report 校验内部令牌并把采集到的日志批量入库(幂等:按 log_time+src_ip+event_type 去重)。
func (s *security) Report(ctx context.Context, token string, items []dto.SecurityLogInput) (int, error) {
// 校验内部上报令牌(配置 internalToken来自环境变量 INTERNAL_TOKEN
expect := g.Cfg().MustGet(ctx, "internalToken").String()
if expect == "" || token != expect {
return 0, response.Error(consts.CodeUnauthorized, "内部上报令牌无效")
}
if len(items) == 0 {
return 0, nil
}
rows := make([]map[string]interface{}, 0, len(items))
for _, it := range items {
// 事件时间兜底:非法/空则用当前时间。
logTime := strings.TrimSpace(it.LogTime)
if logTime == "" {
logTime = gtime.Now().Format("Y-m-d H:i:s")
}
srcIp := strings.TrimSpace(it.SrcIp)
if len(srcIp) > 45 {
srcIp = srcIp[:45]
}
detail := strings.TrimSpace(it.Detail)
if len(detail) > 500 {
detail = detail[:500]
}
rows = append(rows, map[string]interface{}{
"log_time": logTime,
"src_ip": srcIp,
"src_port": it.SrcPort,
"dest_port": it.DestPort,
"event_type": strings.TrimSpace(it.EventType),
"detail": detail,
})
}
if _, err := dao.ServerSecurityLog.Ctx(ctx).Data(rows).Insert(); err != nil {
return 0, gerror.Wrap(err, "insert server security log")
}
return len(rows), nil
}
// List 分页查询安全日志(时间倒序)。
func (s *security) List(ctx context.Context, f dto.SecurityLogFilter) ([]dto.SecurityLogVO, int, error) {
m := dao.ServerSecurityLog.Ctx(ctx)
if f.SrcIp != "" {
m = m.Where("src_ip LIKE ?", "%"+f.SrcIp+"%")
}
if f.EventType != "" {
m = m.Where("event_type", f.EventType)
}
if f.DestPort > 0 {
m = m.Where("dest_port", f.DestPort)
}
if f.DateFrom != "" {
m = m.WhereGTE("log_time", f.DateFrom)
}
if f.DateTo != "" {
m = m.WhereLTE("log_time", f.DateTo)
}
total, err := m.Clone().Count()
if err != nil {
return nil, 0, gerror.Wrap(err, "count security log")
}
var list []dto.SecurityLogVO
if err = m.Clone().Page(f.Page, f.Size).OrderDesc("log_time").OrderDesc("id").Scan(&list); err != nil {
return nil, 0, gerror.Wrap(err, "query security log")
}
for i := range list {
list[i].EventName = dto.SecurityEventName(list[i].EventType)
}
return list, total, nil
}
// Stats 统计:总数/爆破/封禁/成功登录 + TOP 攻击源 + 按类型分布。
// 时间范围为闭区间,空则默认最近 24 小时。
func (s *security) Stats(ctx context.Context, dateFrom, dateTo string) (*dto.SecurityStats, error) {
if dateFrom == "" {
dateFrom = gtime.Now().AddDate(0, 0, -1).Format("Y-m-d H:i:s")
}
if dateTo == "" {
dateTo = gtime.Now().Format("Y-m-d H:i:s")
}
base := dao.ServerSecurityLog.Ctx(ctx).Where("log_time BETWEEN ? AND ?", dateFrom, dateTo)
st := &dto.SecurityStats{}
var err error
if st.Total, err = base.Clone().Count(); err != nil {
return nil, gerror.Wrap(err, "count total")
}
if st.Failed, err = base.Clone().Where("event_type", "failed_ssh").Count(); err != nil {
return nil, gerror.Wrap(err, "count failed")
}
if st.Banned, err = base.Clone().Where("event_type", "banned").Count(); err != nil {
return nil, gerror.Wrap(err, "count banned")
}
if st.Accepted, err = base.Clone().Where("event_type", "accepted_ssh").Count(); err != nil {
return nil, gerror.Wrap(err, "count accepted")
}
// TOP 攻击源(爆破+封禁优先)。
if err = base.Clone().WhereIn("event_type", g.Slice{"failed_ssh", "banned"}).
Fields("src_ip, COUNT(*) AS cnt").
Group("src_ip").OrderDesc("cnt").Limit(10).Scan(&st.TopIps); err != nil {
return nil, gerror.Wrap(err, "query top ips")
}
// 按类型分布。
if err = base.Clone().Fields("event_type, COUNT(*) AS cnt").
Group("event_type").Scan(&st.ByType); err != nil {
return nil, gerror.Wrap(err, "query by type")
}
for i := range st.ByType {
st.ByType[i].EventName = dto.SecurityEventName(st.ByType[i].EventType)
}
return st, nil
}