Files
EdgeAPI/internal/rpc/services/service_firewall.go
2024-05-17 18:27:26 +08:00

340 lines
11 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.

// Copyright 2021 GoEdge CDN goedge.cdn@gmail.com. All rights reserved.
package services
import (
"context"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/regions"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/stats"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"sort"
"strconv"
"time"
)
// FirewallService 防火墙全局服务
type FirewallService struct {
BaseService
}
// ComposeFirewallGlobalBoard 组合看板数据
func (this *FirewallService) ComposeFirewallGlobalBoard(ctx context.Context, req *pb.ComposeFirewallGlobalBoardRequest) (*pb.ComposeFirewallGlobalBoardResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var now = time.Now()
var day = timeutil.Format("Ymd")
var w = types.Int(timeutil.Format("w"))
if w == 0 {
w = 7
}
weekFrom := timeutil.Format("Ymd", now.AddDate(0, 0, -w+1))
weekTo := timeutil.Format("Ymd", now.AddDate(0, 0, -w+7))
var result = &pb.ComposeFirewallGlobalBoardResponse{}
var tx = this.NullTx()
countDailyLog, err := stats.SharedServerHTTPFirewallDailyStatDAO.SumDailyCount(tx, 0, 0, "log", day, day)
if err != nil {
return nil, err
}
result.CountDailyLogs = countDailyLog
countDailyBlock, err := stats.SharedServerHTTPFirewallDailyStatDAO.SumDailyCount(tx, 0, 0, "block", day, day)
if err != nil {
return nil, err
}
result.CountDailyBlocks = countDailyBlock
countDailyCaptcha, err := stats.SharedServerHTTPFirewallDailyStatDAO.SumDailyCount(tx, 0, 0, "captcha", day, day)
if err != nil {
return nil, err
}
result.CountDailyCaptcha = countDailyCaptcha
countWeeklyBlock, err := stats.SharedServerHTTPFirewallDailyStatDAO.SumDailyCount(tx, 0, 0, "block", weekFrom, weekTo)
if err != nil {
return nil, err
}
result.CountWeeklyBlocks = countWeeklyBlock
// 24小时趋势
var hourFrom = timeutil.Format("YmdH", time.Now().Add(-23*time.Hour))
var hourTo = timeutil.Format("YmdH")
hours, err := utils.RangeHours(hourFrom, hourTo)
if err != nil {
return nil, err
}
{
statList, err := stats.SharedServerHTTPFirewallHourlyStatDAO.FindHourlyStats(tx, 0, 0, "log", hourFrom, hourTo)
if err != nil {
return nil, err
}
m := map[string]int64{} // day => count
for _, stat := range statList {
m[stat.Hour] = int64(stat.Count)
}
for _, hour := range hours {
result.HourlyStats = append(result.HourlyStats, &pb.ComposeFirewallGlobalBoardResponse_HourlyStat{Hour: hour, CountLogs: m[hour]})
}
}
{
statList, err := stats.SharedServerHTTPFirewallHourlyStatDAO.FindHourlyStats(tx, 0, 0, "captcha", hourFrom, hourTo)
if err != nil {
return nil, err
}
m := map[string]int64{} // day => count
for _, stat := range statList {
m[stat.Hour] = int64(stat.Count)
}
for index, hour := range hours {
result.HourlyStats[index].CountCaptcha = m[hour]
}
}
{
statList, err := stats.SharedServerHTTPFirewallHourlyStatDAO.FindHourlyStats(tx, 0, 0, "block", hourFrom, hourTo)
if err != nil {
return nil, err
}
m := map[string]int64{} // day => count
for _, stat := range statList {
m[stat.Hour] = int64(stat.Count)
}
for index, hour := range hours {
result.HourlyStats[index].CountBlocks = m[hour]
}
}
// 14天趋势
dayFrom := timeutil.Format("Ymd", now.AddDate(0, 0, -14))
days, err := utils.RangeDays(dayFrom, day)
if err != nil {
return nil, err
}
{
statList, err := stats.SharedServerHTTPFirewallDailyStatDAO.FindDailyStats(tx, 0, 0, []string{"log", "tag"}, dayFrom, day)
if err != nil {
return nil, err
}
m := map[string]int64{} // day => count
for _, stat := range statList {
m[stat.Day] = int64(stat.Count)
}
for _, day := range days {
result.DailyStats = append(result.DailyStats, &pb.ComposeFirewallGlobalBoardResponse_DailyStat{Day: day, CountLogs: m[day]})
}
}
{
statList, err := stats.SharedServerHTTPFirewallDailyStatDAO.FindDailyStats(tx, 0, 0, []string{"captcha"}, dayFrom, day)
if err != nil {
return nil, err
}
m := map[string]int64{} // day => count
for _, stat := range statList {
m[stat.Day] = int64(stat.Count)
}
for index, day := range days {
result.DailyStats[index].CountCaptcha = m[day]
}
}
{
statList, err := stats.SharedServerHTTPFirewallDailyStatDAO.FindDailyStats(tx, 0, 0, []string{"block", "page"}, dayFrom, day)
if err != nil {
return nil, err
}
m := map[string]int64{} // day => count
for _, stat := range statList {
m[stat.Day] = int64(stat.Count)
}
for index, day := range days {
result.DailyStats[index].CountBlocks = m[day]
}
}
// 规则分组
var today = timeutil.Format("Ymd")
groupStats, err := stats.SharedServerHTTPFirewallDailyStatDAO.GroupDailyCount(tx, 0, 0, today, today, 0, 20)
if err != nil {
return nil, err
}
// 合并同名
var groupNamedStatsMap = map[string]*stats.ServerHTTPFirewallDailyStat{} // name => *ServerHTTPFirewallDailyStat
for _, stat := range groupStats {
ruleGroupName, err := models.SharedHTTPFirewallRuleGroupDAO.FindHTTPFirewallRuleGroupName(tx, int64(stat.HttpFirewallRuleGroupId))
if err != nil {
return nil, err
}
if len(ruleGroupName) == 0 {
continue
}
namedStat, ok := groupNamedStatsMap[ruleGroupName]
if ok {
namedStat.Count += stat.Count
} else {
groupNamedStatsMap[ruleGroupName] = stat
}
}
for ruleGroupName, stat := range groupNamedStatsMap {
result.HttpFirewallRuleGroups = append(result.HttpFirewallRuleGroups, &pb.ComposeFirewallGlobalBoardResponse_HTTPFirewallRuleGroupStat{
HttpFirewallRuleGroup: &pb.HTTPFirewallRuleGroup{Id: int64(stat.HttpFirewallRuleGroupId), Name: ruleGroupName},
Count: int64(stat.Count),
})
}
sort.Slice(result.HttpFirewallRuleGroups, func(i, j int) bool {
return result.HttpFirewallRuleGroups[i].Count > result.HttpFirewallRuleGroups[j].Count
})
if len(result.HttpFirewallRuleGroups) > 10 {
result.HttpFirewallRuleGroups = result.HttpFirewallRuleGroups[:10]
}
// 节点排行
topNodeStats, err := stats.SharedNodeTrafficHourlyStatDAO.FindTopNodeStatsWithAttack(tx, "node", hourFrom, hourTo, 10)
if err != nil {
return nil, err
}
for _, stat := range topNodeStats {
nodeName, err := models.SharedNodeDAO.FindNodeName(tx, int64(stat.NodeId))
if err != nil {
return nil, err
}
if len(nodeName) == 0 {
continue
}
result.TopNodeStats = append(result.TopNodeStats, &pb.ComposeFirewallGlobalBoardResponse_NodeStat{
NodeId: int64(stat.NodeId),
NodeName: nodeName,
CountRequests: int64(stat.CountRequests),
Bytes: int64(stat.Bytes),
CountAttackRequests: int64(stat.CountAttackRequests),
AttackBytes: int64(stat.AttackBytes),
})
}
// 域名排行
topDomainStats, err := stats.SharedServerDomainHourlyStatDAO.FindTopDomainStatsWithAttack(tx, hourFrom, hourTo, 10)
if err != nil {
return nil, err
}
for _, stat := range topDomainStats {
result.TopDomainStats = append(result.TopDomainStats, &pb.ComposeFirewallGlobalBoardResponse_DomainStat{
ServerId: int64(stat.ServerId),
Domain: stat.Domain,
CountRequests: int64(stat.CountRequests),
Bytes: int64(stat.Bytes),
CountAttackRequests: int64(stat.CountAttackRequests),
AttackBytes: int64(stat.AttackBytes),
})
}
// 地区流量排行
totalCountryRequests, err := stats.SharedServerRegionCountryDailyStatDAO.SumDailyTotalAttackRequests(tx, timeutil.Format("Ymd"))
if err != nil {
return nil, err
}
if totalCountryRequests > 0 {
topCountryStats, err := stats.SharedServerRegionCountryDailyStatDAO.ListSumStats(tx, timeutil.Format("Ymd"), "countAttackRequests", 0, 100)
if err != nil {
return nil, err
}
for _, stat := range topCountryStats {
countryName, err := regions.SharedRegionCountryDAO.FindRegionCountryName(tx, int64(stat.CountryId))
if err != nil {
return nil, err
}
result.TopCountryStats = append(result.TopCountryStats, &pb.ComposeFirewallGlobalBoardResponse_CountryStat{
CountryName: countryName,
Bytes: int64(stat.Bytes),
CountRequests: int64(stat.CountRequests),
AttackBytes: int64(stat.AttackBytes),
CountAttackRequests: int64(stat.CountAttackRequests),
Percent: float32(stat.CountAttackRequests*100) / float32(totalCountryRequests),
})
}
}
return result, nil
}
// NotifyHTTPFirewallEvent 发送告警(notify)消息
func (this *FirewallService) NotifyHTTPFirewallEvent(ctx context.Context, req *pb.NotifyHTTPFirewallEventRequest) (*pb.RPCSuccess, error) {
nodeId, err := this.ValidateNode(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
clusterId, err := models.SharedNodeDAO.FindNodeClusterId(tx, nodeId)
if err != nil {
return nil, err
}
if clusterId <= 0 {
return this.Success()
}
clusterName, err := models.SharedNodeClusterDAO.FindNodeClusterName(tx, clusterId)
if err != nil {
return nil, err
}
nodeName, err := models.SharedNodeDAO.FindNodeName(tx, nodeId)
if err != nil {
return nil, err
}
serverName, err := models.SharedServerDAO.FindEnabledServerName(tx, req.ServerId)
if err != nil {
return nil, err
}
ruleGroupName, err := models.SharedHTTPFirewallRuleGroupDAO.FindHTTPFirewallRuleGroupName(tx, req.HttpFirewallRuleGroupId)
if err != nil {
return nil, err
}
ruleSetName, err := models.SharedHTTPFirewallRuleSetDAO.FindHTTPFirewallRuleSetName(tx, req.HttpFirewallRuleSetId)
if err != nil {
return nil, err
}
msg := "集群:" + clusterName + "ID" + strconv.FormatInt(clusterId, 10) + "" +
"\n节点" + nodeName + "ID" + strconv.FormatInt(nodeId, 10) + "" +
"\n服务" + serverName + "ID" + strconv.FormatInt(req.ServerId, 10) + "" +
"\n规则分组" + ruleGroupName +
"\n规则集" + ruleSetName +
"\n时间" + timeutil.FormatTime("Y-m-d H:i:s", req.CreatedAt)
err = models.SharedMessageTaskDAO.CreateMessageTasks(tx, nodeconfigs.NodeRoleNode, clusterId, nodeId, req.ServerId, models.MessageTypeFirewallEvent, "触发防火墙事件", msg)
if err != nil {
return nil, err
}
return this.Success()
}
// CountFirewallDailyBlocks 读取当前Block动作次数
func (this *FirewallService) CountFirewallDailyBlocks(ctx context.Context, req *pb.CountFirewallDailyBlocksRequest) (*pb.CountFirewallDailyBlocksResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
var day = timeutil.Format("Ymd")
countDailyBlock, err := stats.SharedServerHTTPFirewallDailyStatDAO.SumDailyCount(tx, 0, 0, "block", day, day)
if err != nil {
return nil, err
}
return &pb.CountFirewallDailyBlocksResponse{
CountBlocks: countDailyBlock,
}, nil
}