mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2025-12-06 06:58:54 +08:00
feat:add notice manager
This commit is contained in:
@@ -47,6 +47,9 @@ type APIGroup struct {
|
||||
WafSystemMonitorApi
|
||||
WafCaServerInfoApi
|
||||
WafSqlQueryApi
|
||||
WafNotifyChannelApi
|
||||
WafNotifySubscriptionApi
|
||||
WafNotifyLogApi
|
||||
}
|
||||
|
||||
var APIGroupAPP = new(APIGroup)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"SamWaf/model/common/response"
|
||||
"SamWaf/model/request"
|
||||
response2 "SamWaf/model/response"
|
||||
"SamWaf/service/waf_service"
|
||||
"SamWaf/utils"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -152,6 +153,16 @@ func (w *WafLoginApi) LoginApi(c *gin.Context) {
|
||||
}
|
||||
global.GQEQUE_LOG_DB.Enqueue(&wafSysLog)
|
||||
|
||||
// 发送用户登录通知
|
||||
go func() {
|
||||
title, content := waf_service.WafNotifySenderServiceApp.FormatUserLoginMessage(
|
||||
bean.LoginAccount,
|
||||
clientIP,
|
||||
time.Now().Format("2006-01-02 15:04:05"),
|
||||
)
|
||||
waf_service.WafNotifySenderServiceApp.SendNotification(model.MSG_TYPE_USER_LOGIN, title, content)
|
||||
}()
|
||||
|
||||
response.OkWithDetailed(response2.LoginRep{
|
||||
AccessToken: tokenInfo.AccessToken,
|
||||
}, "登录成功", c)
|
||||
|
||||
116
api/waf_notify_channel.go
Normal file
116
api/waf_notify_channel.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"SamWaf/model/common/response"
|
||||
"SamWaf/model/request"
|
||||
"SamWaf/service/waf_service"
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type WafNotifyChannelApi struct{}
|
||||
|
||||
var wafNotifyChannelService = waf_service.WafNotifyChannelServiceApp
|
||||
|
||||
// AddApi 添加通知渠道
|
||||
func (w *WafNotifyChannelApi) AddApi(c *gin.Context) {
|
||||
var req request.WafNotifyChannelAddReq
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err == nil {
|
||||
err = wafNotifyChannelService.CheckIsExistApi(req)
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = wafNotifyChannelService.AddApi(req)
|
||||
if err == nil {
|
||||
response.OkWithMessage("添加成功", c)
|
||||
} else {
|
||||
response.FailWithMessage("添加失败", c)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
response.FailWithMessage("当前通知渠道已经存在", c)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// GetDetailApi 获取通知渠道详情
|
||||
func (w *WafNotifyChannelApi) GetDetailApi(c *gin.Context) {
|
||||
var req request.WafNotifyChannelDetailReq
|
||||
err := c.ShouldBind(&req)
|
||||
if err == nil {
|
||||
bean := wafNotifyChannelService.GetDetailApi(req)
|
||||
response.OkWithDetailed(bean, "获取成功", c)
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// GetListApi 获取通知渠道列表
|
||||
func (w *WafNotifyChannelApi) GetListApi(c *gin.Context) {
|
||||
var req request.WafNotifyChannelSearchReq
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err == nil {
|
||||
beans, total, _ := wafNotifyChannelService.GetListApi(req)
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: beans,
|
||||
Total: total,
|
||||
PageIndex: req.PageIndex,
|
||||
PageSize: req.PageSize,
|
||||
}, "获取成功", c)
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// DelApi 删除通知渠道
|
||||
func (w *WafNotifyChannelApi) DelApi(c *gin.Context) {
|
||||
var req request.WafNotifyChannelDelReq
|
||||
err := c.ShouldBind(&req)
|
||||
if err == nil {
|
||||
err = wafNotifyChannelService.DelApi(req)
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
response.FailWithMessage("请检测参数", c)
|
||||
} else if err != nil {
|
||||
response.FailWithMessage("发生错误", c)
|
||||
} else {
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// ModifyApi 修改通知渠道
|
||||
func (w *WafNotifyChannelApi) ModifyApi(c *gin.Context) {
|
||||
var req request.WafNotifyChannelEditReq
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err == nil {
|
||||
err = wafNotifyChannelService.ModifyApi(req)
|
||||
if err != nil {
|
||||
response.FailWithMessage("编辑发生错误", c)
|
||||
} else {
|
||||
response.OkWithMessage("编辑成功", c)
|
||||
}
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// TestApi 测试通知渠道
|
||||
func (w *WafNotifyChannelApi) TestApi(c *gin.Context) {
|
||||
var req request.WafNotifyChannelTestReq
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err == nil {
|
||||
err = wafNotifyChannelService.TestChannelApi(req)
|
||||
if err != nil {
|
||||
response.FailWithMessage("测试失败: "+err.Error(), c)
|
||||
} else {
|
||||
response.OkWithMessage("测试成功,请检查是否收到通知", c)
|
||||
}
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
61
api/waf_notify_log.go
Normal file
61
api/waf_notify_log.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"SamWaf/model/common/response"
|
||||
"SamWaf/model/request"
|
||||
"SamWaf/service/waf_service"
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type WafNotifyLogApi struct{}
|
||||
|
||||
var wafNotifyLogService = waf_service.WafNotifyLogServiceApp
|
||||
|
||||
// GetListApi 获取通知日志列表
|
||||
func (w *WafNotifyLogApi) GetListApi(c *gin.Context) {
|
||||
var req request.WafNotifyLogSearchReq
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err == nil {
|
||||
beans, total, _ := wafNotifyLogService.GetListApi(req)
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: beans,
|
||||
Total: total,
|
||||
PageIndex: req.PageIndex,
|
||||
PageSize: req.PageSize,
|
||||
}, "获取成功", c)
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// GetDetailApi 获取通知日志详情
|
||||
func (w *WafNotifyLogApi) GetDetailApi(c *gin.Context) {
|
||||
var req request.WafNotifyLogDetailReq
|
||||
err := c.ShouldBind(&req)
|
||||
if err == nil {
|
||||
bean := wafNotifyLogService.GetDetailApi(req)
|
||||
response.OkWithDetailed(bean, "获取成功", c)
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// DelApi 删除通知日志
|
||||
func (w *WafNotifyLogApi) DelApi(c *gin.Context) {
|
||||
var req request.WafNotifyLogDelReq
|
||||
err := c.ShouldBind(&req)
|
||||
if err == nil {
|
||||
err = wafNotifyLogService.DelApi(req)
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
response.FailWithMessage("请检测参数", c)
|
||||
} else if err != nil {
|
||||
response.FailWithMessage("发生错误", c)
|
||||
} else {
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
100
api/waf_notify_subscription.go
Normal file
100
api/waf_notify_subscription.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"SamWaf/model/common/response"
|
||||
"SamWaf/model/request"
|
||||
"SamWaf/service/waf_service"
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type WafNotifySubscriptionApi struct{}
|
||||
|
||||
var wafNotifySubscriptionService = waf_service.WafNotifySubscriptionServiceApp
|
||||
|
||||
// AddApi 添加通知订阅
|
||||
func (w *WafNotifySubscriptionApi) AddApi(c *gin.Context) {
|
||||
var req request.WafNotifySubscriptionAddReq
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err == nil {
|
||||
err = wafNotifySubscriptionService.CheckIsExistApi(req)
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = wafNotifySubscriptionService.AddApi(req)
|
||||
if err == nil {
|
||||
response.OkWithMessage("添加成功", c)
|
||||
} else {
|
||||
response.FailWithMessage("添加失败", c)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
response.FailWithMessage("该渠道已订阅此消息类型", c)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// GetDetailApi 获取通知订阅详情
|
||||
func (w *WafNotifySubscriptionApi) GetDetailApi(c *gin.Context) {
|
||||
var req request.WafNotifySubscriptionDetailReq
|
||||
err := c.ShouldBind(&req)
|
||||
if err == nil {
|
||||
bean := wafNotifySubscriptionService.GetDetailApi(req)
|
||||
response.OkWithDetailed(bean, "获取成功", c)
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// GetListApi 获取通知订阅列表
|
||||
func (w *WafNotifySubscriptionApi) GetListApi(c *gin.Context) {
|
||||
var req request.WafNotifySubscriptionSearchReq
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err == nil {
|
||||
beans, total, _ := wafNotifySubscriptionService.GetListApi(req)
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: beans,
|
||||
Total: total,
|
||||
PageIndex: req.PageIndex,
|
||||
PageSize: req.PageSize,
|
||||
}, "获取成功", c)
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// DelApi 删除通知订阅
|
||||
func (w *WafNotifySubscriptionApi) DelApi(c *gin.Context) {
|
||||
var req request.WafNotifySubscriptionDelReq
|
||||
err := c.ShouldBind(&req)
|
||||
if err == nil {
|
||||
err = wafNotifySubscriptionService.DelApi(req)
|
||||
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
response.FailWithMessage("请检测参数", c)
|
||||
} else if err != nil {
|
||||
response.FailWithMessage("发生错误", c)
|
||||
} else {
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// ModifyApi 修改通知订阅
|
||||
func (w *WafNotifySubscriptionApi) ModifyApi(c *gin.Context) {
|
||||
var req request.WafNotifySubscriptionEditReq
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err == nil {
|
||||
err = wafNotifySubscriptionService.ModifyApi(req)
|
||||
if err != nil {
|
||||
response.FailWithMessage("编辑发生错误", c)
|
||||
} else {
|
||||
response.OkWithMessage("编辑成功", c)
|
||||
}
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
21
model/notify_channel.go
Normal file
21
model/notify_channel.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"SamWaf/model/baseorm"
|
||||
)
|
||||
|
||||
/*
|
||||
*
|
||||
通知渠道配置
|
||||
*/
|
||||
type NotifyChannel struct {
|
||||
baseorm.BaseOrm
|
||||
Name string `json:"name"` // 渠道名称
|
||||
Type string `json:"type"` // 渠道类型:dingtalk, feishu, wechat, email等
|
||||
WebhookURL string `json:"webhook_url"` // Webhook地址
|
||||
Secret string `json:"secret"` // 密钥(用于签名验证)
|
||||
AccessToken string `json:"access_token"` // 访问令牌
|
||||
ConfigJSON string `json:"config_json"` // 额外配置(JSON格式)
|
||||
Status int `json:"status"` // 状态:1启用,0禁用
|
||||
Remarks string `json:"remarks"` // 备注
|
||||
}
|
||||
22
model/notify_log.go
Normal file
22
model/notify_log.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"SamWaf/model/baseorm"
|
||||
)
|
||||
|
||||
/*
|
||||
*
|
||||
通知发送日志
|
||||
*/
|
||||
type NotifyLog struct {
|
||||
baseorm.BaseOrm
|
||||
ChannelId string `json:"channel_id"` // 渠道ID
|
||||
ChannelName string `json:"channel_name"` // 渠道名称
|
||||
ChannelType string `json:"channel_type"` // 渠道类型
|
||||
MessageType string `json:"message_type"` // 消息类型
|
||||
MessageTitle string `json:"message_title"` // 消息标题
|
||||
MessageContent string `json:"message_content"` // 消息内容
|
||||
Status int `json:"status"` // 发送状态:1成功,0失败
|
||||
ErrorMsg string `json:"error_msg"` // 错误信息
|
||||
SendTime string `json:"send_time"` // 发送时间
|
||||
}
|
||||
28
model/notify_subscription.go
Normal file
28
model/notify_subscription.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"SamWaf/model/baseorm"
|
||||
)
|
||||
|
||||
/*
|
||||
*
|
||||
通知订阅配置
|
||||
*/
|
||||
type NotifySubscription struct {
|
||||
baseorm.BaseOrm
|
||||
ChannelId string `json:"channel_id"` // 关联的渠道ID
|
||||
MessageType string `json:"message_type"` // 消息类型:user_login, attack_info, weekly_report等
|
||||
Status int `json:"status"` // 状态:1启用,0禁用
|
||||
FilterJSON string `json:"filter_json"` // 过滤条件(JSON格式)
|
||||
Remarks string `json:"remarks"` // 备注
|
||||
}
|
||||
|
||||
// 消息类型常量
|
||||
const (
|
||||
MSG_TYPE_USER_LOGIN = "user_login" // 用户登录
|
||||
MSG_TYPE_ATTACK_INFO = "attack_info" // 攻击信息
|
||||
MSG_TYPE_WEEKLY_REPORT = "weekly_report" // 周报
|
||||
MSG_TYPE_SSL_EXPIRE = "ssl_expire" // SSL证书过期
|
||||
MSG_TYPE_SYSTEM_ERROR = "system_error" // 系统错误
|
||||
MSG_TYPE_IP_BAN = "ip_ban" // IP封禁
|
||||
)
|
||||
50
model/request/waf_notify_channel_req.go
Normal file
50
model/request/waf_notify_channel_req.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package request
|
||||
|
||||
// 添加通知渠道请求
|
||||
type WafNotifyChannelAddReq struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Type string `json:"type" binding:"required"`
|
||||
WebhookURL string `json:"webhook_url"`
|
||||
Secret string `json:"secret"`
|
||||
AccessToken string `json:"access_token"`
|
||||
ConfigJSON string `json:"config_json"`
|
||||
Status int `json:"status"`
|
||||
Remarks string `json:"remarks"`
|
||||
}
|
||||
|
||||
// 编辑通知渠道请求
|
||||
type WafNotifyChannelEditReq struct {
|
||||
Id string `json:"id" binding:"required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Type string `json:"type" binding:"required"`
|
||||
WebhookURL string `json:"webhook_url"`
|
||||
Secret string `json:"secret"`
|
||||
AccessToken string `json:"access_token"`
|
||||
ConfigJSON string `json:"config_json"`
|
||||
Status int `json:"status"`
|
||||
Remarks string `json:"remarks"`
|
||||
}
|
||||
|
||||
// 查询通知渠道详情请求
|
||||
type WafNotifyChannelDetailReq struct {
|
||||
Id string `form:"id" binding:"required"`
|
||||
}
|
||||
|
||||
// 搜索通知渠道请求
|
||||
type WafNotifyChannelSearchReq struct {
|
||||
PageIndex int `json:"pageIndex"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
// 删除通知渠道请求
|
||||
type WafNotifyChannelDelReq struct {
|
||||
Id string `form:"id" binding:"required"`
|
||||
}
|
||||
|
||||
// 测试通知渠道请求
|
||||
type WafNotifyChannelTestReq struct {
|
||||
Id string `json:"id" binding:"required"`
|
||||
}
|
||||
22
model/request/waf_notify_log_req.go
Normal file
22
model/request/waf_notify_log_req.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package request
|
||||
|
||||
// 搜索通知日志请求
|
||||
type WafNotifyLogSearchReq struct {
|
||||
PageIndex int `json:"pageIndex"`
|
||||
PageSize int `json:"pageSize"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
MessageType string `json:"message_type"`
|
||||
Status int `json:"status"`
|
||||
StartTime string `json:"start_time"`
|
||||
EndTime string `json:"end_time"`
|
||||
}
|
||||
|
||||
// 删除通知日志请求
|
||||
type WafNotifyLogDelReq struct {
|
||||
Id string `form:"id" binding:"required"`
|
||||
}
|
||||
|
||||
// 查询通知日志详情请求
|
||||
type WafNotifyLogDetailReq struct {
|
||||
Id string `form:"id" binding:"required"`
|
||||
}
|
||||
39
model/request/waf_notify_subscription_req.go
Normal file
39
model/request/waf_notify_subscription_req.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package request
|
||||
|
||||
// 添加通知订阅请求
|
||||
type WafNotifySubscriptionAddReq struct {
|
||||
ChannelId string `json:"channel_id" binding:"required"`
|
||||
MessageType string `json:"message_type" binding:"required"`
|
||||
Status int `json:"status"`
|
||||
FilterJSON string `json:"filter_json"`
|
||||
Remarks string `json:"remarks"`
|
||||
}
|
||||
|
||||
// 编辑通知订阅请求
|
||||
type WafNotifySubscriptionEditReq struct {
|
||||
Id string `json:"id" binding:"required"`
|
||||
ChannelId string `json:"channel_id" binding:"required"`
|
||||
MessageType string `json:"message_type" binding:"required"`
|
||||
Status int `json:"status"`
|
||||
FilterJSON string `json:"filter_json"`
|
||||
Remarks string `json:"remarks"`
|
||||
}
|
||||
|
||||
// 查询通知订阅详情请求
|
||||
type WafNotifySubscriptionDetailReq struct {
|
||||
Id string `form:"id" binding:"required"`
|
||||
}
|
||||
|
||||
// 搜索通知订阅请求
|
||||
type WafNotifySubscriptionSearchReq struct {
|
||||
PageIndex int `json:"pageIndex"`
|
||||
PageSize int `json:"pageSize"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
MessageType string `json:"message_type"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
// 删除通知订阅请求
|
||||
type WafNotifySubscriptionDelReq struct {
|
||||
Id string `form:"id" binding:"required"`
|
||||
}
|
||||
@@ -45,6 +45,9 @@ type ApiGroup struct {
|
||||
WafSystemMonitorRouter
|
||||
WafCaServerInfoRouter
|
||||
SqlQueryRouter
|
||||
NotifyChannelRouter
|
||||
NotifySubscriptionRouter
|
||||
NotifyLogRouter
|
||||
}
|
||||
type PublicApiGroup struct {
|
||||
LoginRouter
|
||||
|
||||
20
router/waf_notify_channel.go
Normal file
20
router/waf_notify_channel.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"SamWaf/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type NotifyChannelRouter struct {
|
||||
}
|
||||
|
||||
func (receiver *NotifyChannelRouter) InitNotifyChannelRouter(group *gin.RouterGroup) {
|
||||
api := api.APIGroupAPP.WafNotifyChannelApi
|
||||
router := group.Group("")
|
||||
router.POST("/samwaf/notify/channel/list", api.GetListApi)
|
||||
router.GET("/samwaf/notify/channel/detail", api.GetDetailApi)
|
||||
router.POST("/samwaf/notify/channel/add", api.AddApi)
|
||||
router.GET("/samwaf/notify/channel/del", api.DelApi)
|
||||
router.POST("/samwaf/notify/channel/edit", api.ModifyApi)
|
||||
router.POST("/samwaf/notify/channel/test", api.TestApi)
|
||||
}
|
||||
17
router/waf_notify_log.go
Normal file
17
router/waf_notify_log.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"SamWaf/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type NotifyLogRouter struct {
|
||||
}
|
||||
|
||||
func (receiver *NotifyLogRouter) InitNotifyLogRouter(group *gin.RouterGroup) {
|
||||
api := api.APIGroupAPP.WafNotifyLogApi
|
||||
router := group.Group("")
|
||||
router.POST("/samwaf/notify/log/list", api.GetListApi)
|
||||
router.GET("/samwaf/notify/log/detail", api.GetDetailApi)
|
||||
router.GET("/samwaf/notify/log/del", api.DelApi)
|
||||
}
|
||||
19
router/waf_notify_subscription.go
Normal file
19
router/waf_notify_subscription.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"SamWaf/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type NotifySubscriptionRouter struct {
|
||||
}
|
||||
|
||||
func (receiver *NotifySubscriptionRouter) InitNotifySubscriptionRouter(group *gin.RouterGroup) {
|
||||
api := api.APIGroupAPP.WafNotifySubscriptionApi
|
||||
router := group.Group("")
|
||||
router.POST("/samwaf/notify/subscription/list", api.GetListApi)
|
||||
router.GET("/samwaf/notify/subscription/detail", api.GetDetailApi)
|
||||
router.POST("/samwaf/notify/subscription/add", api.AddApi)
|
||||
router.GET("/samwaf/notify/subscription/del", api.DelApi)
|
||||
router.POST("/samwaf/notify/subscription/edit", api.ModifyApi)
|
||||
}
|
||||
151
service/waf_service/waf_notify_channel.go
Normal file
151
service/waf_service/waf_notify_channel.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package waf_service
|
||||
|
||||
import (
|
||||
"SamWaf/common/uuid"
|
||||
"SamWaf/customtype"
|
||||
"SamWaf/global"
|
||||
"SamWaf/model"
|
||||
"SamWaf/model/baseorm"
|
||||
"SamWaf/model/request"
|
||||
"SamWaf/wafnotify/dingtalk"
|
||||
"SamWaf/wafnotify/feishu"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WafNotifyChannelService struct{}
|
||||
|
||||
var WafNotifyChannelServiceApp = new(WafNotifyChannelService)
|
||||
|
||||
// AddApi 添加通知渠道
|
||||
func (receiver *WafNotifyChannelService) AddApi(req request.WafNotifyChannelAddReq) error {
|
||||
var bean = &model.NotifyChannel{
|
||||
BaseOrm: baseorm.BaseOrm{
|
||||
Id: uuid.GenUUID(),
|
||||
USER_CODE: global.GWAF_USER_CODE,
|
||||
Tenant_ID: global.GWAF_TENANT_ID,
|
||||
CREATE_TIME: customtype.JsonTime(time.Now()),
|
||||
UPDATE_TIME: customtype.JsonTime(time.Now()),
|
||||
},
|
||||
Name: req.Name,
|
||||
Type: req.Type,
|
||||
WebhookURL: req.WebhookURL,
|
||||
Secret: req.Secret,
|
||||
AccessToken: req.AccessToken,
|
||||
ConfigJSON: req.ConfigJSON,
|
||||
Status: req.Status,
|
||||
Remarks: req.Remarks,
|
||||
}
|
||||
return global.GWAF_LOCAL_DB.Create(bean).Error
|
||||
}
|
||||
|
||||
// CheckIsExistApi 检查是否存在
|
||||
func (receiver *WafNotifyChannelService) CheckIsExistApi(req request.WafNotifyChannelAddReq) error {
|
||||
return global.GWAF_LOCAL_DB.First(&model.NotifyChannel{}, "name = ? ", req.Name).Error
|
||||
}
|
||||
|
||||
// ModifyApi 修改通知渠道
|
||||
func (receiver *WafNotifyChannelService) ModifyApi(req request.WafNotifyChannelEditReq) error {
|
||||
editMap := map[string]interface{}{
|
||||
"Name": req.Name,
|
||||
"Type": req.Type,
|
||||
"WebhookURL": req.WebhookURL,
|
||||
"Secret": req.Secret,
|
||||
"AccessToken": req.AccessToken,
|
||||
"ConfigJSON": req.ConfigJSON,
|
||||
"Status": req.Status,
|
||||
"Remarks": req.Remarks,
|
||||
"UPDATE_TIME": customtype.JsonTime(time.Now()),
|
||||
}
|
||||
return global.GWAF_LOCAL_DB.Model(model.NotifyChannel{}).Where("id = ?", req.Id).Updates(editMap).Error
|
||||
}
|
||||
|
||||
// GetDetailApi 获取详情
|
||||
func (receiver *WafNotifyChannelService) GetDetailApi(req request.WafNotifyChannelDetailReq) model.NotifyChannel {
|
||||
var bean model.NotifyChannel
|
||||
global.GWAF_LOCAL_DB.Where("id=?", req.Id).Find(&bean)
|
||||
return bean
|
||||
}
|
||||
|
||||
// GetListApi 获取列表
|
||||
func (receiver *WafNotifyChannelService) GetListApi(req request.WafNotifyChannelSearchReq) ([]model.NotifyChannel, int64, error) {
|
||||
var list []model.NotifyChannel
|
||||
var total int64 = 0
|
||||
|
||||
var whereField = ""
|
||||
var whereValues []interface{}
|
||||
|
||||
if len(req.Name) > 0 {
|
||||
if len(whereField) > 0 {
|
||||
whereField = whereField + " and "
|
||||
}
|
||||
whereField = whereField + " name like ? "
|
||||
whereValues = append(whereValues, "%"+req.Name+"%")
|
||||
}
|
||||
|
||||
if len(req.Type) > 0 {
|
||||
if len(whereField) > 0 {
|
||||
whereField = whereField + " and "
|
||||
}
|
||||
whereField = whereField + " type = ? "
|
||||
whereValues = append(whereValues, req.Type)
|
||||
}
|
||||
|
||||
if req.Status > 0 {
|
||||
if len(whereField) > 0 {
|
||||
whereField = whereField + " and "
|
||||
}
|
||||
whereField = whereField + " status = ? "
|
||||
whereValues = append(whereValues, req.Status)
|
||||
}
|
||||
|
||||
global.GWAF_LOCAL_DB.Model(&model.NotifyChannel{}).Where(whereField, whereValues...).Limit(req.PageSize).Offset(req.PageSize * (req.PageIndex - 1)).Find(&list)
|
||||
global.GWAF_LOCAL_DB.Model(&model.NotifyChannel{}).Where(whereField, whereValues...).Count(&total)
|
||||
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// DelApi 删除
|
||||
func (receiver *WafNotifyChannelService) DelApi(req request.WafNotifyChannelDelReq) error {
|
||||
var bean model.NotifyChannel
|
||||
err := global.GWAF_LOCAL_DB.Where("id = ?", req.Id).First(&bean).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除关联的订阅
|
||||
global.GWAF_LOCAL_DB.Where("channel_id = ?", req.Id).Delete(&model.NotifySubscription{})
|
||||
|
||||
// 删除渠道
|
||||
return global.GWAF_LOCAL_DB.Where("id = ?", req.Id).Delete(&model.NotifyChannel{}).Error
|
||||
}
|
||||
|
||||
// TestChannelApi 测试通知渠道
|
||||
func (receiver *WafNotifyChannelService) TestChannelApi(req request.WafNotifyChannelTestReq) error {
|
||||
var channel model.NotifyChannel
|
||||
err := global.GWAF_LOCAL_DB.Where("id = ?", req.Id).First(&channel).Error
|
||||
if err != nil {
|
||||
return errors.New("通知渠道不存在")
|
||||
}
|
||||
|
||||
title := "SamWAF 测试通知"
|
||||
content := "这是一条测试消息,发送时间:" + time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
switch channel.Type {
|
||||
case "dingtalk":
|
||||
notifier := dingtalk.NewDingTalkNotifier(channel.WebhookURL, channel.Secret)
|
||||
return notifier.SendMarkdown(title, content)
|
||||
case "feishu":
|
||||
notifier := feishu.NewFeishuNotifier(channel.WebhookURL, channel.Secret)
|
||||
return notifier.SendMarkdown(title, content)
|
||||
default:
|
||||
return errors.New("不支持的通知类型")
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllChannels 获取所有启用的通知渠道
|
||||
func (receiver *WafNotifyChannelService) GetAllChannels() []model.NotifyChannel {
|
||||
var channels []model.NotifyChannel
|
||||
global.GWAF_LOCAL_DB.Where("status = ?", 1).Find(&channels)
|
||||
return channels
|
||||
}
|
||||
96
service/waf_service/waf_notify_log.go
Normal file
96
service/waf_service/waf_notify_log.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package waf_service
|
||||
|
||||
import (
|
||||
"SamWaf/common/uuid"
|
||||
"SamWaf/customtype"
|
||||
"SamWaf/global"
|
||||
"SamWaf/model"
|
||||
"SamWaf/model/baseorm"
|
||||
"SamWaf/model/request"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WafNotifyLogService struct{}
|
||||
|
||||
var WafNotifyLogServiceApp = new(WafNotifyLogService)
|
||||
|
||||
// AddLog 添加通知日志
|
||||
func (receiver *WafNotifyLogService) AddLog(channelId, channelName, channelType, messageType, messageTitle, messageContent string, status int, errorMsg string) error {
|
||||
var bean = &model.NotifyLog{
|
||||
BaseOrm: baseorm.BaseOrm{
|
||||
Id: uuid.GenUUID(),
|
||||
USER_CODE: global.GWAF_USER_CODE,
|
||||
Tenant_ID: global.GWAF_TENANT_ID,
|
||||
CREATE_TIME: customtype.JsonTime(time.Now()),
|
||||
UPDATE_TIME: customtype.JsonTime(time.Now()),
|
||||
},
|
||||
ChannelId: channelId,
|
||||
ChannelName: channelName,
|
||||
ChannelType: channelType,
|
||||
MessageType: messageType,
|
||||
MessageTitle: messageTitle,
|
||||
MessageContent: messageContent,
|
||||
Status: status,
|
||||
ErrorMsg: errorMsg,
|
||||
SendTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
return global.GWAF_LOCAL_LOG_DB.Create(bean).Error
|
||||
}
|
||||
|
||||
// GetListApi 获取列表
|
||||
func (receiver *WafNotifyLogService) GetListApi(req request.WafNotifyLogSearchReq) ([]model.NotifyLog, int64, error) {
|
||||
var list []model.NotifyLog
|
||||
var total int64 = 0
|
||||
|
||||
var whereField = ""
|
||||
var whereValues []interface{}
|
||||
|
||||
if len(req.ChannelId) > 0 {
|
||||
if len(whereField) > 0 {
|
||||
whereField = whereField + " and "
|
||||
}
|
||||
whereField = whereField + " channel_id = ? "
|
||||
whereValues = append(whereValues, req.ChannelId)
|
||||
}
|
||||
|
||||
if len(req.MessageType) > 0 {
|
||||
if len(whereField) > 0 {
|
||||
whereField = whereField + " and "
|
||||
}
|
||||
whereField = whereField + " message_type = ? "
|
||||
whereValues = append(whereValues, req.MessageType)
|
||||
}
|
||||
|
||||
if req.Status > 0 {
|
||||
if len(whereField) > 0 {
|
||||
whereField = whereField + " and "
|
||||
}
|
||||
whereField = whereField + " status = ? "
|
||||
whereValues = append(whereValues, req.Status)
|
||||
}
|
||||
|
||||
if len(req.StartTime) > 0 && len(req.EndTime) > 0 {
|
||||
if len(whereField) > 0 {
|
||||
whereField = whereField + " and "
|
||||
}
|
||||
whereField = whereField + " send_time >= ? and send_time <= ? "
|
||||
whereValues = append(whereValues, req.StartTime, req.EndTime)
|
||||
}
|
||||
|
||||
global.GWAF_LOCAL_LOG_DB.Model(&model.NotifyLog{}).Where(whereField, whereValues...).Order("create_time desc").Limit(req.PageSize).Offset(req.PageSize * (req.PageIndex - 1)).Find(&list)
|
||||
global.GWAF_LOCAL_LOG_DB.Model(&model.NotifyLog{}).Where(whereField, whereValues...).Count(&total)
|
||||
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// GetDetailApi 获取详情
|
||||
func (receiver *WafNotifyLogService) GetDetailApi(req request.WafNotifyLogDetailReq) model.NotifyLog {
|
||||
var bean model.NotifyLog
|
||||
global.GWAF_LOCAL_LOG_DB.Where("id=?", req.Id).Find(&bean)
|
||||
return bean
|
||||
}
|
||||
|
||||
// DelApi 删除
|
||||
func (receiver *WafNotifyLogService) DelApi(req request.WafNotifyLogDelReq) error {
|
||||
return global.GWAF_LOCAL_LOG_DB.Where("id = ?", req.Id).Delete(&model.NotifyLog{}).Error
|
||||
}
|
||||
128
service/waf_service/waf_notify_sender.go
Normal file
128
service/waf_service/waf_notify_sender.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package waf_service
|
||||
|
||||
import (
|
||||
"SamWaf/common/zlog"
|
||||
"SamWaf/global"
|
||||
"SamWaf/model"
|
||||
"SamWaf/wafnotify/dingtalk"
|
||||
"SamWaf/wafnotify/feishu"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type WafNotifySenderService struct{}
|
||||
|
||||
var WafNotifySenderServiceApp = new(WafNotifySenderService)
|
||||
|
||||
// SendNotification 发送通知
|
||||
func (receiver *WafNotifySenderService) SendNotification(messageType, title, content string) {
|
||||
// 获取订阅
|
||||
subscriptions := WafNotifySubscriptionServiceApp.GetSubscriptionsByMessageType(messageType)
|
||||
if len(subscriptions) == 0 {
|
||||
zlog.Debug(fmt.Sprintf("没有找到消息类型 %s 的订阅", messageType))
|
||||
return
|
||||
}
|
||||
|
||||
// 遍历订阅,发送通知
|
||||
for _, subscription := range subscriptions {
|
||||
// 获取渠道信息
|
||||
var channel model.NotifyChannel
|
||||
err := receiver.getChannelById(subscription.ChannelId, &channel)
|
||||
if err != nil {
|
||||
zlog.Error(fmt.Sprintf("获取渠道信息失败: %v", err))
|
||||
continue
|
||||
}
|
||||
|
||||
// 发送通知
|
||||
go receiver.sendToChannel(channel, messageType, title, content)
|
||||
}
|
||||
}
|
||||
|
||||
// getChannelById 根据ID获取渠道
|
||||
func (receiver *WafNotifySenderService) getChannelById(channelId string, channel *model.NotifyChannel) error {
|
||||
return global.GWAF_LOCAL_DB.Where("id = ? and status = ?", channelId, 1).First(channel).Error
|
||||
}
|
||||
|
||||
// sendToChannel 发送到具体渠道
|
||||
func (receiver *WafNotifySenderService) sendToChannel(channel model.NotifyChannel, messageType, title, content string) {
|
||||
var err error
|
||||
status := 1
|
||||
errorMsg := ""
|
||||
|
||||
switch channel.Type {
|
||||
case "dingtalk":
|
||||
notifier := dingtalk.NewDingTalkNotifier(channel.WebhookURL, channel.Secret)
|
||||
err = notifier.SendMarkdown(title, content)
|
||||
case "feishu":
|
||||
notifier := feishu.NewFeishuNotifier(channel.WebhookURL, channel.Secret)
|
||||
err = notifier.SendMarkdown(title, content)
|
||||
default:
|
||||
err = fmt.Errorf("不支持的通知类型: %s", channel.Type)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
status = 0
|
||||
errorMsg = err.Error()
|
||||
zlog.Error(fmt.Sprintf("发送通知失败: %v", err))
|
||||
}
|
||||
|
||||
// 记录日志
|
||||
logErr := WafNotifyLogServiceApp.AddLog(
|
||||
channel.Id,
|
||||
channel.Name,
|
||||
channel.Type,
|
||||
messageType,
|
||||
title,
|
||||
content,
|
||||
status,
|
||||
errorMsg,
|
||||
)
|
||||
if logErr != nil {
|
||||
zlog.Error(fmt.Sprintf("记录通知日志失败: %v", logErr))
|
||||
}
|
||||
}
|
||||
|
||||
// FormatUserLoginMessage 格式化用户登录消息
|
||||
func (receiver *WafNotifySenderService) FormatUserLoginMessage(username, ip, time string) (string, string) {
|
||||
title := "用户登录通知"
|
||||
content := fmt.Sprintf("**用户:** %s\n\n**IP地址:** %s\n\n**登录时间:** %s", username, ip, time)
|
||||
return title, content
|
||||
}
|
||||
|
||||
// FormatAttackInfoMessage 格式化攻击信息消息
|
||||
func (receiver *WafNotifySenderService) FormatAttackInfoMessage(attackType, url, ip, time string) (string, string) {
|
||||
title := "攻击告警通知"
|
||||
content := fmt.Sprintf("**攻击类型:** %s\n\n**URL:** %s\n\n**攻击IP:** %s\n\n**攻击时间:** %s", attackType, url, ip, time)
|
||||
return title, content
|
||||
}
|
||||
|
||||
// FormatWeeklyReportMessage 格式化周报消息
|
||||
func (receiver *WafNotifySenderService) FormatWeeklyReportMessage(totalRequests, blockedRequests int64, weekRange string) (string, string) {
|
||||
title := "WAF周报"
|
||||
content := fmt.Sprintf("**周期:** %s\n\n**总请求数:** %d\n\n**拦截请求数:** %d\n\n**拦截率:** %.2f%%",
|
||||
weekRange,
|
||||
totalRequests,
|
||||
blockedRequests,
|
||||
float64(blockedRequests)/float64(totalRequests)*100)
|
||||
return title, content
|
||||
}
|
||||
|
||||
// FormatSSLExpireMessage 格式化SSL证书过期消息
|
||||
func (receiver *WafNotifySenderService) FormatSSLExpireMessage(domain string, expireTime string, daysLeft int) (string, string) {
|
||||
title := "SSL证书即将过期通知"
|
||||
content := fmt.Sprintf("**域名:** %s\n\n**过期时间:** %s\n\n**剩余天数:** %d天", domain, expireTime, daysLeft)
|
||||
return title, content
|
||||
}
|
||||
|
||||
// FormatSystemErrorMessage 格式化系统错误消息
|
||||
func (receiver *WafNotifySenderService) FormatSystemErrorMessage(errorType, errorMsg, time string) (string, string) {
|
||||
title := "系统错误通知"
|
||||
content := fmt.Sprintf("**错误类型:** %s\n\n**错误信息:** %s\n\n**发生时间:** %s", errorType, errorMsg, time)
|
||||
return title, content
|
||||
}
|
||||
|
||||
// FormatIPBanMessage 格式化IP封禁消息
|
||||
func (receiver *WafNotifySenderService) FormatIPBanMessage(ip, reason, time string, duration int) (string, string) {
|
||||
title := "IP封禁通知"
|
||||
content := fmt.Sprintf("**IP地址:** %s\n\n**封禁原因:** %s\n\n**封禁时长:** %d分钟\n\n**封禁时间:** %s", ip, reason, duration, time)
|
||||
return title, content
|
||||
}
|
||||
109
service/waf_service/waf_notify_subscription.go
Normal file
109
service/waf_service/waf_notify_subscription.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package waf_service
|
||||
|
||||
import (
|
||||
"SamWaf/common/uuid"
|
||||
"SamWaf/customtype"
|
||||
"SamWaf/global"
|
||||
"SamWaf/model"
|
||||
"SamWaf/model/baseorm"
|
||||
"SamWaf/model/request"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WafNotifySubscriptionService struct{}
|
||||
|
||||
var WafNotifySubscriptionServiceApp = new(WafNotifySubscriptionService)
|
||||
|
||||
// AddApi 添加通知订阅
|
||||
func (receiver *WafNotifySubscriptionService) AddApi(req request.WafNotifySubscriptionAddReq) error {
|
||||
var bean = &model.NotifySubscription{
|
||||
BaseOrm: baseorm.BaseOrm{
|
||||
Id: uuid.GenUUID(),
|
||||
USER_CODE: global.GWAF_USER_CODE,
|
||||
Tenant_ID: global.GWAF_TENANT_ID,
|
||||
CREATE_TIME: customtype.JsonTime(time.Now()),
|
||||
UPDATE_TIME: customtype.JsonTime(time.Now()),
|
||||
},
|
||||
ChannelId: req.ChannelId,
|
||||
MessageType: req.MessageType,
|
||||
Status: req.Status,
|
||||
FilterJSON: req.FilterJSON,
|
||||
Remarks: req.Remarks,
|
||||
}
|
||||
return global.GWAF_LOCAL_DB.Create(bean).Error
|
||||
}
|
||||
|
||||
// CheckIsExistApi 检查是否存在
|
||||
func (receiver *WafNotifySubscriptionService) CheckIsExistApi(req request.WafNotifySubscriptionAddReq) error {
|
||||
return global.GWAF_LOCAL_DB.First(&model.NotifySubscription{}, "channel_id = ? and message_type = ? ", req.ChannelId, req.MessageType).Error
|
||||
}
|
||||
|
||||
// ModifyApi 修改通知订阅
|
||||
func (receiver *WafNotifySubscriptionService) ModifyApi(req request.WafNotifySubscriptionEditReq) error {
|
||||
editMap := map[string]interface{}{
|
||||
"ChannelId": req.ChannelId,
|
||||
"MessageType": req.MessageType,
|
||||
"Status": req.Status,
|
||||
"FilterJSON": req.FilterJSON,
|
||||
"Remarks": req.Remarks,
|
||||
"UPDATE_TIME": customtype.JsonTime(time.Now()),
|
||||
}
|
||||
return global.GWAF_LOCAL_DB.Model(model.NotifySubscription{}).Where("id = ?", req.Id).Updates(editMap).Error
|
||||
}
|
||||
|
||||
// GetDetailApi 获取详情
|
||||
func (receiver *WafNotifySubscriptionService) GetDetailApi(req request.WafNotifySubscriptionDetailReq) model.NotifySubscription {
|
||||
var bean model.NotifySubscription
|
||||
global.GWAF_LOCAL_DB.Where("id=?", req.Id).Find(&bean)
|
||||
return bean
|
||||
}
|
||||
|
||||
// GetListApi 获取列表
|
||||
func (receiver *WafNotifySubscriptionService) GetListApi(req request.WafNotifySubscriptionSearchReq) ([]model.NotifySubscription, int64, error) {
|
||||
var list []model.NotifySubscription
|
||||
var total int64 = 0
|
||||
|
||||
var whereField = ""
|
||||
var whereValues []interface{}
|
||||
|
||||
if len(req.ChannelId) > 0 {
|
||||
if len(whereField) > 0 {
|
||||
whereField = whereField + " and "
|
||||
}
|
||||
whereField = whereField + " channel_id = ? "
|
||||
whereValues = append(whereValues, req.ChannelId)
|
||||
}
|
||||
|
||||
if len(req.MessageType) > 0 {
|
||||
if len(whereField) > 0 {
|
||||
whereField = whereField + " and "
|
||||
}
|
||||
whereField = whereField + " message_type = ? "
|
||||
whereValues = append(whereValues, req.MessageType)
|
||||
}
|
||||
|
||||
if req.Status > 0 {
|
||||
if len(whereField) > 0 {
|
||||
whereField = whereField + " and "
|
||||
}
|
||||
whereField = whereField + " status = ? "
|
||||
whereValues = append(whereValues, req.Status)
|
||||
}
|
||||
|
||||
global.GWAF_LOCAL_DB.Model(&model.NotifySubscription{}).Where(whereField, whereValues...).Limit(req.PageSize).Offset(req.PageSize * (req.PageIndex - 1)).Find(&list)
|
||||
global.GWAF_LOCAL_DB.Model(&model.NotifySubscription{}).Where(whereField, whereValues...).Count(&total)
|
||||
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// DelApi 删除
|
||||
func (receiver *WafNotifySubscriptionService) DelApi(req request.WafNotifySubscriptionDelReq) error {
|
||||
return global.GWAF_LOCAL_DB.Where("id = ?", req.Id).Delete(&model.NotifySubscription{}).Error
|
||||
}
|
||||
|
||||
// GetSubscriptionsByMessageType 根据消息类型获取订阅
|
||||
func (receiver *WafNotifySubscriptionService) GetSubscriptionsByMessageType(messageType string) []model.NotifySubscription {
|
||||
var subscriptions []model.NotifySubscription
|
||||
global.GWAF_LOCAL_DB.Where("message_type = ? and status = ?", messageType, 1).Find(&subscriptions)
|
||||
return subscriptions
|
||||
}
|
||||
@@ -164,6 +164,29 @@ func RunCoreDBMigrations(db *gorm.DB) error {
|
||||
return dropCoreIndexes(tx)
|
||||
},
|
||||
},
|
||||
// 迁移3: 创建通知管理相关表
|
||||
{
|
||||
ID: "202511240001_add_notify_tables",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
zlog.Info("迁移 202511240001: 创建通知管理表")
|
||||
// 创建通知渠道和订阅表
|
||||
if err := tx.AutoMigrate(
|
||||
&model.NotifyChannel{},
|
||||
&model.NotifySubscription{},
|
||||
); err != nil {
|
||||
return fmt.Errorf("创建通知管理表失败: %w", err)
|
||||
}
|
||||
zlog.Info("通知管理表创建成功")
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
zlog.Info("回滚 202511240001: 删除通知管理表")
|
||||
return tx.Migrator().DropTable(
|
||||
&model.NotifyChannel{},
|
||||
&model.NotifySubscription{},
|
||||
)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// 执行迁移
|
||||
|
||||
@@ -87,6 +87,27 @@ func RunLogDBMigrations(db *gorm.DB) error {
|
||||
return dropLogIndexes(tx)
|
||||
},
|
||||
},
|
||||
// 迁移3: 创建通知日志表
|
||||
{
|
||||
ID: "202511240001_add_notify_log_table",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
zlog.Info("迁移 202511240001: 创建通知日志表")
|
||||
// 创建通知日志表
|
||||
if err := tx.AutoMigrate(
|
||||
&model.NotifyLog{},
|
||||
); err != nil {
|
||||
return fmt.Errorf("创建通知日志表失败: %w", err)
|
||||
}
|
||||
zlog.Info("通知日志表创建成功")
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
zlog.Info("回滚 202511240001: 删除通知日志表")
|
||||
return tx.Migrator().DropTable(
|
||||
&model.NotifyLog{},
|
||||
)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// 执行迁移
|
||||
|
||||
@@ -87,6 +87,9 @@ func (web *WafWebManager) initRouter(r *gin.Engine) {
|
||||
router.ApiGroupApp.InitWafSystemMonitorRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitWafCaServerInfoRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitSqlQueryRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitNotifyChannelRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitNotifySubscriptionRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitNotifyLogRouter(RouterGroup)
|
||||
}
|
||||
|
||||
if global.GWAF_RELEASE == "true" {
|
||||
|
||||
134
wafnotify/dingtalk/dingtalk.go
Normal file
134
wafnotify/dingtalk/dingtalk.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package dingtalk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DingTalkNotifier 钉钉通知器
|
||||
type DingTalkNotifier struct {
|
||||
WebhookURL string
|
||||
Secret string
|
||||
}
|
||||
|
||||
// NewDingTalkNotifier 创建钉钉通知器
|
||||
func NewDingTalkNotifier(webhookURL, secret string) *DingTalkNotifier {
|
||||
return &DingTalkNotifier{
|
||||
WebhookURL: webhookURL,
|
||||
Secret: secret,
|
||||
}
|
||||
}
|
||||
|
||||
// DingTalkMessage 钉钉消息结构
|
||||
type DingTalkMessage struct {
|
||||
MsgType string `json:"msgtype"`
|
||||
Markdown map[string]interface{} `json:"markdown,omitempty"`
|
||||
Text map[string]interface{} `json:"text,omitempty"`
|
||||
}
|
||||
|
||||
// SendMarkdown 发送Markdown消息
|
||||
func (d *DingTalkNotifier) SendMarkdown(title, content string) error {
|
||||
message := DingTalkMessage{
|
||||
MsgType: "markdown",
|
||||
Markdown: map[string]interface{}{
|
||||
"title": title,
|
||||
"text": content,
|
||||
},
|
||||
}
|
||||
return d.send(message)
|
||||
}
|
||||
|
||||
// SendText 发送文本消息
|
||||
func (d *DingTalkNotifier) SendText(content string) error {
|
||||
message := DingTalkMessage{
|
||||
MsgType: "text",
|
||||
Text: map[string]interface{}{
|
||||
"content": content,
|
||||
},
|
||||
}
|
||||
return d.send(message)
|
||||
}
|
||||
|
||||
// send 发送消息
|
||||
func (d *DingTalkNotifier) send(message DingTalkMessage) error {
|
||||
// 构建URL(包含签名)
|
||||
urlWithSign, err := d.buildURL()
|
||||
if err != nil {
|
||||
return fmt.Errorf("构建URL失败: %v", err)
|
||||
}
|
||||
|
||||
// 序列化消息
|
||||
payload, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化消息失败: %v", err)
|
||||
}
|
||||
|
||||
// 发送HTTP请求
|
||||
resp, err := http.Post(urlWithSign, "application/json", bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
return fmt.Errorf("发送HTTP请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取响应失败: %v", err)
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return fmt.Errorf("解析响应失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查响应码
|
||||
if errCode, ok := result["errcode"].(float64); ok && errCode != 0 {
|
||||
return fmt.Errorf("钉钉返回错误: %v", result["errmsg"])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildURL 构建包含签名的URL
|
||||
func (d *DingTalkNotifier) buildURL() (string, error) {
|
||||
if d.Secret == "" {
|
||||
return d.WebhookURL, nil
|
||||
}
|
||||
|
||||
timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
||||
sign, err := d.sign(timestamp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
u, err := url.Parse(d.WebhookURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
query.Set("timestamp", timestamp)
|
||||
query.Set("sign", sign)
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
// sign 计算签名
|
||||
func (d *DingTalkNotifier) sign(timestamp string) (string, error) {
|
||||
stringToSign := timestamp + "\n" + d.Secret
|
||||
h := hmac.New(sha256.New, []byte(d.Secret))
|
||||
h.Write([]byte(stringToSign))
|
||||
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
return url.QueryEscape(signature), nil
|
||||
}
|
||||
152
wafnotify/feishu/feishu.go
Normal file
152
wafnotify/feishu/feishu.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package feishu
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FeishuNotifier 飞书通知器
|
||||
type FeishuNotifier struct {
|
||||
WebhookURL string
|
||||
Secret string
|
||||
}
|
||||
|
||||
// NewFeishuNotifier 创建飞书通知器
|
||||
func NewFeishuNotifier(webhookURL, secret string) *FeishuNotifier {
|
||||
return &FeishuNotifier{
|
||||
WebhookURL: webhookURL,
|
||||
Secret: secret,
|
||||
}
|
||||
}
|
||||
|
||||
// FeishuMessage 飞书消息结构
|
||||
type FeishuMessage struct {
|
||||
Timestamp string `json:"timestamp,omitempty"`
|
||||
Sign string `json:"sign,omitempty"`
|
||||
MsgType string `json:"msg_type"`
|
||||
Content map[string]interface{} `json:"content,omitempty"`
|
||||
Card map[string]interface{} `json:"card,omitempty"`
|
||||
}
|
||||
|
||||
// SendText 发送文本消息
|
||||
func (f *FeishuNotifier) SendText(content string) error {
|
||||
message := FeishuMessage{
|
||||
MsgType: "text",
|
||||
Content: map[string]interface{}{
|
||||
"text": content,
|
||||
},
|
||||
}
|
||||
return f.send(message)
|
||||
}
|
||||
|
||||
// SendRichText 发送富文本消息
|
||||
func (f *FeishuNotifier) SendRichText(title, content string) error {
|
||||
message := FeishuMessage{
|
||||
MsgType: "post",
|
||||
Content: map[string]interface{}{
|
||||
"post": map[string]interface{}{
|
||||
"zh_cn": map[string]interface{}{
|
||||
"title": title,
|
||||
"content": [][]map[string]interface{}{
|
||||
{
|
||||
{
|
||||
"tag": "text",
|
||||
"text": content,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return f.send(message)
|
||||
}
|
||||
|
||||
// SendMarkdown 发送Markdown消息(交互式卡片)
|
||||
func (f *FeishuNotifier) SendMarkdown(title, content string) error {
|
||||
message := FeishuMessage{
|
||||
MsgType: "interactive",
|
||||
Card: map[string]interface{}{
|
||||
"config": map[string]interface{}{
|
||||
"wide_screen_mode": true,
|
||||
},
|
||||
"header": map[string]interface{}{
|
||||
"title": map[string]interface{}{
|
||||
"tag": "plain_text",
|
||||
"content": title,
|
||||
},
|
||||
"template": "blue",
|
||||
},
|
||||
"elements": []map[string]interface{}{
|
||||
{
|
||||
"tag": "markdown",
|
||||
"content": content,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return f.send(message)
|
||||
}
|
||||
|
||||
// send 发送消息
|
||||
func (f *FeishuNotifier) send(message FeishuMessage) error {
|
||||
// 如果有密钥,添加签名
|
||||
if f.Secret != "" {
|
||||
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
sign, err := f.sign(timestamp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("生成签名失败: %v", err)
|
||||
}
|
||||
message.Timestamp = timestamp
|
||||
message.Sign = sign
|
||||
}
|
||||
|
||||
// 序列化消息
|
||||
payload, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化消息失败: %v", err)
|
||||
}
|
||||
|
||||
// 发送HTTP请求
|
||||
resp, err := http.Post(f.WebhookURL, "application/json", bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
return fmt.Errorf("发送HTTP请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取响应失败: %v", err)
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return fmt.Errorf("解析响应失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查响应码
|
||||
if code, ok := result["code"].(float64); ok && code != 0 {
|
||||
return fmt.Errorf("飞书返回错误: %v", result["msg"])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sign 计算签名
|
||||
func (f *FeishuNotifier) sign(timestamp string) (string, error) {
|
||||
stringToSign := timestamp + "\n" + f.Secret
|
||||
h := hmac.New(sha256.New, []byte(stringToSign))
|
||||
h.Write([]byte(stringToSign))
|
||||
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
return signature, nil
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"SamWaf/common/zlog"
|
||||
"SamWaf/global"
|
||||
"SamWaf/innerbean"
|
||||
"SamWaf/model"
|
||||
"SamWaf/service/waf_service"
|
||||
"SamWaf/wafipban"
|
||||
"SamWaf/waftask"
|
||||
"strconv"
|
||||
@@ -60,6 +62,21 @@ func ProcessLogDequeEngine() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 检查攻击日志并发送通知
|
||||
for _, log := range webLogArray {
|
||||
if log.RULE != "" && log.STATUS_CODE >= 400 {
|
||||
// 异步发送攻击通知
|
||||
go func(attackLog *innerbean.WebLog) {
|
||||
title, content := waf_service.WafNotifySenderServiceApp.FormatAttackInfoMessage(
|
||||
attackLog.RULE,
|
||||
attackLog.URL,
|
||||
attackLog.SRC_IP,
|
||||
attackLog.CREATE_TIME,
|
||||
)
|
||||
waf_service.WafNotifySenderServiceApp.SendNotification(model.MSG_TYPE_ATTACK_INFO, title, content)
|
||||
}(log)
|
||||
}
|
||||
}
|
||||
if global.GCONFIG_LOG_PERSIST_ENABLED == 1 {
|
||||
global.GWAF_LOCAL_LOG_DB.CreateInBatches(webLogArray, len(webLogArray))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user