From 703360d7fe72445536f5d1f36a846a40e0994019 Mon Sep 17 00:00:00 2001 From: samwaf Date: Fri, 6 Mar 2026 17:39:01 +0800 Subject: [PATCH] feat:add open platform #664 #698 --- api/entrance.go | 6 + api/waf_host.go | 32 ++ api/waf_log.go | 23 + api/waf_oplatform_doc.go | 667 +++++++++++++++++++++++ api/waf_oplatform_key.go | 102 ++++ api/waf_oplatform_log.go | 57 ++ api/waf_sys_info.go | 8 + cmd/samwaf/main.go | 7 + global/config.go | 3 + go.mod | 12 + go.sum | 34 ++ middleware/auth_api_check.go | 12 + middleware/openapi_auth.go | 221 ++++++++ model/common/response/response.go | 9 + model/oplatform_key.go | 22 + model/oplatform_log.go | 23 + model/request/waf_oplatform_key_req.go | 38 ++ model/request/waf_oplatform_log_req.go | 19 + router/entrance.go | 3 + router/waf_oplatform_doc.go | 15 + router/waf_oplatform_key.go | 19 + router/waf_oplatform_log.go | 16 + service/waf_service/waf_oplatform_key.go | 138 +++++ service/waf_service/waf_oplatform_log.go | 88 +++ wafdb/migrations_core.go | 45 ++ wafdb/migrations_log.go | 26 + wafmangeweb/localserver.go | 11 +- waftask/task_config.go | 5 + 28 files changed, 1659 insertions(+), 2 deletions(-) create mode 100644 api/waf_oplatform_doc.go create mode 100644 api/waf_oplatform_key.go create mode 100644 api/waf_oplatform_log.go create mode 100644 middleware/openapi_auth.go create mode 100644 model/oplatform_key.go create mode 100644 model/oplatform_log.go create mode 100644 model/request/waf_oplatform_key_req.go create mode 100644 model/request/waf_oplatform_log_req.go create mode 100644 router/waf_oplatform_doc.go create mode 100644 router/waf_oplatform_key.go create mode 100644 router/waf_oplatform_log.go create mode 100644 service/waf_service/waf_oplatform_key.go create mode 100644 service/waf_service/waf_oplatform_log.go diff --git a/api/entrance.go b/api/entrance.go index 4371224..ae40429 100644 --- a/api/entrance.go +++ b/api/entrance.go @@ -54,6 +54,9 @@ type APIGroup struct { WafPluginApi WafLogFileWriteApi WafIPLocationApi + WafOPlatformKeyApi + WafOPlatformLogApi + WafOPlatformDocApi } var APIGroupAPP = new(APIGroup) @@ -118,4 +121,7 @@ var ( wafSqlQueryService = waf_service.WafSqlQueryServiceApp wafFirewallIPBlockService = waf_service.WafFirewallIPBlockServiceApp + + wafOPlatformKeyService = waf_service.WafOPlatformKeyServiceApp + wafOPlatformLogService = waf_service.WafOPlatformLogServiceApp ) diff --git a/api/waf_host.go b/api/waf_host.go index 71a7c62..092505c 100644 --- a/api/waf_host.go +++ b/api/waf_host.go @@ -25,6 +25,16 @@ import ( type WafHostAPi struct { } +// AddApi 新增网站防护主机 +// @Summary 新增网站防护主机 +// @Description 新增一个网站防护主机配置 +// @Tags 网站防护-主机管理 +// @Accept json +// @Produce json +// @Param data body request.WafHostAddReq true "主机配置" +// @Success 200 {object} response.Response{data=string} "添加成功,返回主机code" +// @Security ApiKeyAuth +// @Router /wafhost/host/add [post] func (w *WafHostAPi) AddApi(c *gin.Context) { var req request.WafHostAddReq err := c.ShouldBindJSON(&req) @@ -96,6 +106,17 @@ func (w *WafHostAPi) BatchCopyConfigApi(c *gin.Context) { response.FailWithMessage("解析失败", c) } } + +// GetDetailApi 获取网站防护主机详情 +// @Summary 获取网站防护主机详情 +// @Description 根据 code 获取单个主机的详细配置 +// @Tags 网站防护-主机管理 +// @Accept json +// @Produce json +// @Param code query string true "主机唯一编码" +// @Success 200 {object} response.Response{data=model.Hosts} "获取成功" +// @Security ApiKeyAuth +// @Router /wafhost/host/detail [get] func (w *WafHostAPi) GetDetailApi(c *gin.Context) { var req request.WafHostDetailReq err := c.ShouldBind(&req) @@ -106,6 +127,17 @@ func (w *WafHostAPi) GetDetailApi(c *gin.Context) { response.FailWithMessage("解析失败", c) } } + +// GetListApi 获取网站防护主机列表 +// @Summary 获取网站防护主机列表 +// @Description 分页查询网站防护主机列表 +// @Tags 网站防护-主机管理 +// @Accept json +// @Produce json +// @Param data body request.WafHostSearchReq true "分页查询参数" +// @Success 200 {object} response.Response{data=response.PageResult} "获取成功" +// @Security ApiKeyAuth +// @Router /wafhost/host/list [post] func (w *WafHostAPi) GetListApi(c *gin.Context) { var req request.WafHostSearchReq err := c.ShouldBindJSON(&req) diff --git a/api/waf_log.go b/api/waf_log.go index d3f6899..7eff519 100644 --- a/api/waf_log.go +++ b/api/waf_log.go @@ -24,6 +24,18 @@ import ( type WafLogAPi struct { } +// GetDetailApi 获取攻击日志详情 +// @Summary 获取攻击日志详情 +// @Description 根据 req_uuid 获取单条攻击日志详情(含请求体、响应体) +// @Tags 日志-攻击日志 +// @Accept json +// @Produce json +// @Param req_uuid query string true "请求UUID" +// @Param current_db_name query string false "数据库名称,默认 local_log" +// @Param output_format query string false "输出格式:raw 或 curl" +// @Success 200 {object} response.Response "获取成功" +// @Security ApiKeyAuth +// @Router /waflog/attack/detail [get] func (w *WafLogAPi) GetDetailApi(c *gin.Context) { var req request.WafAttackLogDetailReq err := c.ShouldBind(&req) @@ -39,6 +51,17 @@ func (w *WafLogAPi) GetDetailApi(c *gin.Context) { response.FailWithMessage("解析失败", c) } } + +// GetListApi 获取攻击日志列表 +// @Summary 获取攻击日志列表 +// @Description 分页查询攻击日志,支持按主机码、规则、IP、时间范围等过滤 +// @Tags 日志-攻击日志 +// @Accept json +// @Produce json +// @Param data body request.WafAttackLogSearch true "查询参数" +// @Success 200 {object} response.Response{data=response.PageResult} "获取成功" +// @Security ApiKeyAuth +// @Router /waflog/attack/list [post] func (w *WafLogAPi) GetListApi(c *gin.Context) { var req request.WafAttackLogSearch err := c.ShouldBindJSON(&req) diff --git a/api/waf_oplatform_doc.go b/api/waf_oplatform_doc.go new file mode 100644 index 0000000..7edea20 --- /dev/null +++ b/api/waf_oplatform_doc.go @@ -0,0 +1,667 @@ +package api + +import ( + "SamWaf/global" + "SamWaf/model/common/response" + "fmt" + "github.com/gin-gonic/gin" + "sort" + "strings" +) + +// GinEngineRef 保存 gin.Engine 引用,在 localserver 初始化路由后设置 +var GinEngineRef *gin.Engine + +type WafOPlatformDocApi struct{} + +// apiRouteInfo API路由文档信息 +type apiRouteInfo struct { + Method string `json:"method"` + Path string `json:"path"` + Module string `json:"module"` + Description string `json:"description"` + ParamExample string `json:"param_example"` + ResponseExample string `json:"response_example"` + CurlExample string `json:"curl_example"` +} + +// routeDetail 静态路由注释信息 +type routeDetail struct { + Description string + ParamExample string + ResponseExample string +} + +// apiDocResponse API文档响应结构 +type apiDocResponse struct { + AuthInfo authInfo `json:"auth_info"` + BaseURL string `json:"base_url"` + TotalCount int `json:"total_count"` + Modules map[string][]apiRouteInfo `json:"modules"` +} + +type authInfo struct { + HeaderName string `json:"header_name"` + Description string `json:"description"` + Example string `json:"example"` +} + +// routeDetailMap 各接口的静态参数说明注册表,key 格式为 "METHOD /path" +var routeDetailMap = map[string]routeDetail{ + // ======== 网站防护-主机管理 ======== + "POST /api/v1/wafhost/host/list": { + Description: "获取网站防护主机列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10,"code":"","remarks":""}`, + ResponseExample: `{"code":0,"data":{"list":[{"code":"abc123","host":"example.com","port":80,"remote_host":"127.0.0.1","remote_port":8080,"start_status":1}],"total":1},"msg":"查询成功"}`, + }, + "POST /api/v1/wafhost/host/add": { + Description: "新增网站防护主机", + ParamExample: `{"host":"example.com","port":80,"ssl":0,"remote_host":"127.0.0.1","remote_port":8080,"remarks":"备注说明","start_status":1}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "POST /api/v1/wafhost/host/edit": { + Description: "编辑网站防护主机配置", + ParamExample: `{"code":"abc123","host":"example.com","port":80,"ssl":0,"remote_host":"127.0.0.1","remote_port":8080,"remarks":"新备注","start_status":1}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/wafhost/host/detail": { + Description: "获取网站防护主机详情", + ParamExample: `?code=abc123`, + ResponseExample: `{"code":0,"data":{"code":"abc123","host":"example.com","port":80},"msg":"查询成功"}`, + }, + "GET /api/v1/wafhost/host/del": { + Description: "删除网站防护主机", + ParamExample: `?code=abc123`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "POST /api/v1/wafhost/host/guardstatus": { + Description: "修改主机防御状态(开启/关闭防御)", + ParamExample: `{"code":"abc123","guard_status":1}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "POST /api/v1/wafhost/host/startstatus": { + Description: "修改主机启动状态(启动/停止)", + ParamExample: `{"code":"abc123","start_status":1}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 网站防护-规则管理 ======== + "POST /api/v1/wafhost/rule/list": { + Description: "获取WAF规则列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123","rule_name":""}`, + ResponseExample: `{"code":0,"data":{"list":[{"code":"rule001","rule_code":"v4-xxx","rule_status":1}],"total":1},"msg":"查询成功"}`, + }, + "POST /api/v1/wafhost/rule/add": { + Description: "新增WAF规则", + ParamExample: `{"rule_code":"v4-xxx","rule_json":"{...}","is_manual_rule":0,"rule_content":"","rule_status":1}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "POST /api/v1/wafhost/rule/edit": { + Description: "编辑WAF规则", + ParamExample: `{"code":"rule001","rule_json":"{...}","is_manual_rule":0,"rule_status":1}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/wafhost/rule/del": { + Description: "删除WAF规则", + ParamExample: `?code=rule001`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/wafhost/rule/detail": { + Description: "获取WAF规则详情", + ParamExample: `?code=rule001`, + ResponseExample: `{"code":0,"data":{"code":"rule001","rule_json":"{...}","rule_status":1},"msg":"查询成功"}`, + }, + "POST /api/v1/wafhost/rule/status": { + Description: "修改WAF规则启用/禁用状态", + ParamExample: `{"code":"rule001","rule_status":1}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 网站防护-IP黑名单 ======== + "POST /api/v1/wafhost/blockip/list": { + Description: "获取IP黑名单列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123","ip":""}`, + ResponseExample: `{"code":0,"data":{"list":[{"id":"xxx","ip":"1.2.3.4","host_code":"abc123"}],"total":1},"msg":"查询成功"}`, + }, + "POST /api/v1/wafhost/blockip/add": { + Description: "新增IP黑名单", + ParamExample: `{"host_code":"abc123","ip":"1.2.3.4","remarks":"恶意IP"}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "POST /api/v1/wafhost/blockip/edit": { + Description: "编辑IP黑名单", + ParamExample: `{"id":"xxx","host_code":"abc123","ip":"1.2.3.5","remarks":"更新备注"}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/wafhost/blockip/del": { + Description: "删除IP黑名单", + ParamExample: `?id=xxx`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 网站防护-IP白名单 ======== + "POST /api/v1/wafhost/allowip/list": { + Description: "获取IP白名单列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123","ip":""}`, + ResponseExample: `{"code":0,"data":{"list":[{"id":"xxx","ip":"192.168.1.1","host_code":"abc123"}],"total":1},"msg":"查询成功"}`, + }, + "POST /api/v1/wafhost/allowip/add": { + Description: "新增IP白名单", + ParamExample: `{"host_code":"abc123","ip":"192.168.1.100","remarks":"办公室IP"}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "POST /api/v1/wafhost/allowip/edit": { + Description: "编辑IP白名单", + ParamExample: `{"id":"xxx","host_code":"abc123","ip":"192.168.1.101","remarks":"更新"}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/wafhost/allowip/del": { + Description: "删除IP白名单", + ParamExample: `?id=xxx`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 网站防护-URL黑名单 ======== + "POST /api/v1/wafhost/blockurl/list": { + Description: "获取URL黑名单列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123"}`, + ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`, + }, + "POST /api/v1/wafhost/blockurl/add": { + Description: "新增URL黑名单", + ParamExample: `{"host_code":"abc123","url":"/admin/","remarks":"禁止访问后台"}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/wafhost/blockurl/del": { + Description: "删除URL黑名单", + ParamExample: `?id=xxx`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 网站防护-URL白名单 ======== + "POST /api/v1/wafhost/allowurl/list": { + Description: "获取URL白名单列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123"}`, + ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`, + }, + "POST /api/v1/wafhost/allowurl/add": { + Description: "新增URL白名单(绕过WAF检测)", + ParamExample: `{"host_code":"abc123","url":"/api/upload","remarks":"上传接口"}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/wafhost/allowurl/del": { + Description: "删除URL白名单", + ParamExample: `?id=xxx`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 网站防护-CC防护 ======== + "POST /api/v1/wafhost/anticc/list": { + Description: "获取CC防护规则列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123"}`, + ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`, + }, + "POST /api/v1/wafhost/anticc/add": { + Description: "新增CC防护规则", + ParamExample: `{"host_code":"abc123","url":"/login","threshold":100,"time_window":60,"action":"block"}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/wafhost/anticc/del": { + Description: "删除CC防护规则", + ParamExample: `?id=xxx`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 日志-攻击日志 ======== + "POST /api/v1/waflog/attack/list": { + Description: "获取攻击日志列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"","src_ip":"","action":"","unix_add_time_begin":"2026-01-01 00:00:00","unix_add_time_end":"2026-12-31 23:59:59"}`, + ResponseExample: `{"code":0,"data":{"list":[{"req_uuid":"xxx","src_ip":"1.2.3.4","rule":"SQL注入","action":"block","time_str":"2026-03-06 10:00:00"}],"total":100},"msg":"查询成功"}`, + }, + "POST /api/v1/waflog/attack/detail": { + Description: "获取攻击日志详情", + ParamExample: `{"req_uuid":"xxx-yyy-zzz","current_db_name":"local_log"}`, + ResponseExample: `{"code":0,"data":{"req_uuid":"xxx","src_ip":"1.2.3.4","method":"POST","url":"/login","body":"..."},"msg":"查询成功"}`, + }, + + // ======== 统计-数据统计 ======== + "POST /api/v1/stat/dayrangecount": { + Description: "按日期范围统计访问量/拦截量", + ParamExample: `{"begin_date":"2026-01-01","end_date":"2026-03-06","host_code":""}`, + ResponseExample: `{"code":0,"data":{"list":[{"date":"2026-03-06","total":1000,"block":50}]},"msg":"查询成功"}`, + }, + "POST /api/v1/stat/ipcount": { + Description: "统计攻击IP排行", + ParamExample: `{"begin_date":"2026-01-01","end_date":"2026-03-06","host_code":"","top":10}`, + ResponseExample: `{"code":0,"data":{"list":[{"src_ip":"1.2.3.4","count":500}]},"msg":"查询成功"}`, + }, + + // ======== 系统日志 ======== + "POST /api/v1/syslog/list": { + Description: "获取系统操作日志列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10,"op_type":"","op_content":""}`, + ResponseExample: `{"code":0,"data":{"list":[{"op_type":"login","op_content":"用户登录","time_str":"2026-03-06 10:00:00"}],"total":50},"msg":"查询成功"}`, + }, + + // ======== 账号管理 ======== + "POST /api/v1/account/list": { + Description: "获取账号列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10}`, + ResponseExample: `{"code":0,"data":{"list":[{"id":"xxx","user_name":"admin","status":1}],"total":1},"msg":"查询成功"}`, + }, + "POST /api/v1/account/add": { + Description: "新增账号", + ParamExample: `{"user_name":"apiuser","password":"Password@123","status":1}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/account/del": { + Description: "删除账号", + ParamExample: `?id=xxx`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 系统配置 ======== + "POST /api/v1/systemconfig/list": { + Description: "获取系统配置项列表", + ParamExample: `{"pageIndex":1,"pageSize":50,"config_type":""}`, + ResponseExample: `{"code":0,"data":{"list":[{"config_type":"ssl","config_key":"sslorder_expire_day","config_value":"30"}],"total":10},"msg":"查询成功"}`, + }, + "POST /api/v1/systemconfig/updateitem": { + Description: "更新单个系统配置项", + ParamExample: `{"config_type":"ssl","config_key":"sslorder_expire_day","config_value":"15"}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 系统信息 ======== + "GET /api/v1/sysinfo/info": { + Description: "获取系统运行信息(CPU、内存、版本等)", + ParamExample: `(无需参数)`, + ResponseExample: `{"code":0,"data":{"cpu_percent":10.5,"mem_percent":40.2,"version":"1.x.x"},"msg":"查询成功"}`, + }, + + // ======== SSL证书配置 ======== + "POST /api/v1/wafhost/sslconfig/list": { + Description: "获取SSL证书列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10}`, + ResponseExample: `{"code":0,"data":{"list":[{"id":"xxx","domain":"example.com","expire_time":"2027-01-01"}],"total":1},"msg":"查询成功"}`, + }, + "POST /api/v1/wafhost/sslconfig/add": { + Description: "上传新增SSL证书", + ParamExample: `{"domain":"example.com","cert_content":"-----BEGIN CERTIFICATE-----...","key_content":"-----BEGIN PRIVATE KEY-----..."}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/wafhost/sslconfig/del": { + Description: "删除SSL证书", + ParamExample: `?id=xxx`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 负载均衡 ======== + "POST /api/v1/wafhost/loadbalance/list": { + Description: "获取负载均衡配置列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123"}`, + ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`, + }, + "POST /api/v1/wafhost/loadbalance/add": { + Description: "新增负载均衡后端节点", + ParamExample: `{"host_code":"abc123","remote_host":"192.168.1.10","remote_port":8080,"weight":1}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/wafhost/loadbalance/del": { + Description: "删除负载均衡节点", + ParamExample: `?id=xxx`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 通知渠道 ======== + "POST /api/v1/notify/channel/list": { + Description: "获取通知渠道列表", + ParamExample: `{"pageIndex":1,"pageSize":10}`, + ResponseExample: `{"code":0,"data":{"list":[{"id":"xxx","channel_name":"企业微信","channel_type":"wechat_work","status":1}],"total":1},"msg":"查询成功"}`, + }, + "POST /api/v1/notify/channel/add": { + Description: "新增通知渠道", + ParamExample: `{"channel_name":"钉钉机器人","channel_type":"dingtalk","webhook_url":"https://oapi.dingtalk.com/robot/send?access_token=xxx","status":1}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/notify/channel/del": { + Description: "删除通知渠道", + ParamExample: `?id=xxx`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 通知订阅 ======== + "POST /api/v1/notify/subscription/list": { + Description: "获取通知订阅列表", + ParamExample: `{"pageIndex":1,"pageSize":10}`, + ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`, + }, + "POST /api/v1/notify/subscription/add": { + Description: "新增通知订阅(将事件与渠道关联)", + ParamExample: `{"channel_id":"xxx","event_type":"attack_block","host_code":""}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 防火墙IP封禁 ======== + "POST /api/v1/firewallipblock/list": { + Description: "获取防火墙IP封禁规则列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10}`, + ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`, + }, + "POST /api/v1/firewallipblock/add": { + Description: "新增防火墙IP封禁规则", + ParamExample: `{"ip":"1.2.3.4","remarks":"攻击者","expire_time":""}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/firewallipblock/del": { + Description: "删除防火墙IP封禁规则", + ParamExample: `?id=xxx`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 开放平台-Key管理 ======== + "POST /api/v1/oplatform/key/list": { + Description: "获取OpenAPI Key列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10,"key_name":""}`, + ResponseExample: `{"code":0,"data":{"list":[{"id":"xxx","key_name":"我的Key","api_key":"sk-xxx","status":1,"call_count":100}],"total":1},"msg":"查询成功"}`, + }, + "POST /api/v1/oplatform/key/add": { + Description: "新增OpenAPI Key(系统自动生成api_key和api_secret)", + ParamExample: `{"key_name":"第三方系统Key","remark":"用于对接XXX系统","rate_limit":1000,"ip_whitelist":"192.168.1.0/24","expire_time":""}`, + ResponseExample: `{"code":0,"data":{"api_key":"sk-xxxxxxxx","api_secret":"sk-secret-xxx"},"msg":"操作成功"}`, + }, + "POST /api/v1/oplatform/key/edit": { + Description: "编辑OpenAPI Key信息", + ParamExample: `{"id":"xxx","key_name":"更新名称","status":1,"remark":"","rate_limit":500,"ip_whitelist":"","expire_time":"2027-01-01 00:00:00"}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/oplatform/key/del": { + Description: "删除OpenAPI Key", + ParamExample: `?id=xxx`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/oplatform/key/detail": { + Description: "获取OpenAPI Key详情", + ParamExample: `?id=xxx`, + ResponseExample: `{"code":0,"data":{"id":"xxx","key_name":"我的Key","api_key":"sk-xxx","rate_limit":1000,"call_count":500},"msg":"查询成功"}`, + }, + "POST /api/v1/oplatform/key/resetSecret": { + Description: "重置OpenAPI Key的Secret(旧Secret立即失效)", + ParamExample: `{"id":"xxx"}`, + ResponseExample: `{"code":0,"data":{"api_secret":"sk-new-secret-xxx"},"msg":"操作成功"}`, + }, + + // ======== 开放平台-调用日志 ======== + "POST /api/v1/oplatform/log/list": { + Description: "获取OpenAPI调用日志列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10,"key_name":"","request_path":""}`, + ResponseExample: `{"code":0,"data":{"list":[{"api_key_id":"xxx","key_name":"我的Key","request_path":"/api/v1/wafhost/host/list","request_method":"POST","status_code":200,"duration":12,"time_str":"2026-03-06 10:00:00"}],"total":50},"msg":"查询成功"}`, + }, + "GET /api/v1/oplatform/log/detail": { + Description: "获取OpenAPI调用日志详情(含请求体和响应体)", + ParamExample: `?id=xxx`, + ResponseExample: `{"code":0,"data":{"request_body":"{...}","response_body":"{...}","client_ip":"192.168.1.1"},"msg":"查询成功"}`, + }, + "GET /api/v1/oplatform/log/del": { + Description: "删除OpenAPI调用日志记录", + ParamExample: `?id=xxx`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 敏感词 ======== + "POST /api/v1/wafhost/sensitive/list": { + Description: "获取敏感词列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123"}`, + ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`, + }, + "POST /api/v1/wafhost/sensitive/add": { + Description: "新增敏感词(用于响应内容过滤)", + ParamExample: `{"host_code":"abc123","sensitive_word":"身份证","replace_word":"***","remarks":""}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + "GET /api/v1/wafhost/sensitive/del": { + Description: "删除敏感词", + ParamExample: `?id=xxx`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== IP失败封禁 ======== + "POST /api/v1/wafhost/ipfailure/list": { + Description: "获取IP失败封禁规则列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123"}`, + ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`, + }, + "POST /api/v1/wafhost/ipfailure/add": { + Description: "新增IP失败次数封禁规则", + ParamExample: `{"host_code":"abc123","url":"/login","fail_count":5,"time_window":300,"block_time":3600,"remarks":"登录失败封禁"}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== WAF引擎 ======== + "GET /api/v1/wafhost/engine/status": { + Description: "获取WAF引擎运行状态", + ParamExample: `(无需参数)`, + ResponseExample: `{"code":0,"data":{"status":"running","host_count":5},"msg":"查询成功"}`, + }, + "POST /api/v1/wafhost/engine/restart": { + Description: "重启WAF引擎(重新加载所有规则和配置)", + ParamExample: `{}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, + + // ======== 系统监控 ======== + "GET /api/v1/monitor/info": { + Description: "获取实时系统监控数据(CPU、内存、网络流量)", + ParamExample: `(无需参数)`, + ResponseExample: `{"code":0,"data":{"cpu":15.2,"mem":40.5,"net_in":1024,"net_out":2048},"msg":"查询成功"}`, + }, + + // ======== 缓存规则 ======== + "POST /api/v1/wafhost/cacherule/list": { + Description: "获取缓存规则列表(分页)", + ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123"}`, + ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`, + }, + "POST /api/v1/wafhost/cacherule/add": { + Description: "新增缓存规则", + ParamExample: `{"host_code":"abc123","url_pattern":"/static/","cache_time":3600,"remarks":"静态资源缓存"}`, + ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`, + }, +} + +// routeModuleMap 路由路径前缀到模块名的映射 +var routeModuleMap = map[string]string{ + "/api/v1/wafhost/host": "网站防护-主机管理", + "/api/v1/wafhost/rule": "网站防护-规则管理", + "/api/v1/wafhost/allowip": "网站防护-IP白名单", + "/api/v1/wafhost/allowurl": "网站防护-URL白名单", + "/api/v1/wafhost/ldpurl": "网站防护-隐私保护URL", + "/api/v1/wafhost/blockip": "网站防护-IP黑名单", + "/api/v1/wafhost/blockurl": "网站防护-URL黑名单", + "/api/v1/wafhost/anticc": "网站防护-CC防护", + "/api/v1/wafhost/ipfailure": "网站防护-IP失败封禁", + "/api/v1/wafhost/sensitive": "网站防护-敏感词", + "/api/v1/wafhost/loadbalance": "网站防护-负载均衡", + "/api/v1/wafhost/sslconfig": "SSL-证书配置", + "/api/v1/wafhost/sslorder": "SSL-证书申请", + "/api/v1/wafhost/sslexpire": "SSL-证书过期", + "/api/v1/wafhost/httpauthbase": "网站防护-HTTP认证", + "/api/v1/wafhost/blockingpage": "网站防护-拦截页面", + "/api/v1/wafhost/cacherule": "网站防护-缓存规则", + "/api/v1/wafhost/otp": "安全-OTP双因素", + "/api/v1/waflog/attack": "日志-攻击日志", + "/api/v1/stat": "统计-数据统计", + "/api/v1/wafhost/engine": "引擎-WAF引擎", + "/api/v1/account": "账号管理", + "/api/v1/accountlog": "账号日志", + "/api/v1/syslog": "系统日志", + "/api/v1/sysinfo": "系统信息", + "/api/v1/systemconfig": "系统配置", + "/api/v1/wafcommon": "公共接口", + "/api/v1/onekeymod": "一键模式", + "/api/v1/center": "管理中心", + "/api/v1/license": "License管理", + "/api/v1/batchtask": "批量任务", + "/api/v1/task": "任务管理", + "/api/v1/gpt": "AI助手", + "/api/v1/analysis": "流量分析", + "/api/v1/privateinfo": "隐私信息", + "/api/v1/privategroup": "隐私分组", + "/api/v1/tunnel": "隧道管理", + "/api/v1/vpconfig": "VP配置", + "/api/v1/file": "文件管理", + "/api/v1/monitor": "系统监控", + "/api/v1/caserver": "CA服务器", + "/api/v1/sqlquery": "SQL查询", + "/api/v1/notify/channel": "通知-渠道", + "/api/v1/notify/subscription": "通知-订阅", + "/api/v1/notify/log": "通知-日志", + "/api/v1/firewallipblock": "防火墙-IP封禁", + "/api/v1/plugin": "插件管理", + "/api/v1/logfilewrite": "日志文件写入", + "/api/v1/iplocation": "IP地址库", + "/api/v1/oplatform/key": "开放平台-Key管理", + "/api/v1/oplatform/log": "开放平台-调用日志", +} + +// getModuleByPath 根据路径判断所属模块 +func getModuleByPath(path string) string { + type prefixEntry struct { + prefix string + module string + } + var entries []prefixEntry + for prefix, module := range routeModuleMap { + entries = append(entries, prefixEntry{prefix, module}) + } + sort.Slice(entries, func(i, j int) bool { + return len(entries[i].prefix) > len(entries[j].prefix) + }) + for _, entry := range entries { + if strings.HasPrefix(path, entry.prefix) { + return entry.module + } + } + return "其他" +} + +// buildCurlExample 生成 curl 调用示例,优先使用静态参数注册表中的真实参数 +func buildCurlExample(method, path string, paramExample string) string { + baseURL := "http://your-samwaf-host:26666" + upper := strings.ToUpper(method) + switch upper { + case "GET": + queryStr := "" + if paramExample != "" && strings.HasPrefix(paramExample, "?") { + queryStr = paramExample + } + return fmt.Sprintf(`curl -X GET "%s%s%s" \ + -H "X-API-Key: your_api_key"`, baseURL, path, queryStr) + case "POST": + body := `{"pageIndex":1,"pageSize":10}` + if paramExample != "" && !strings.HasPrefix(paramExample, "?") && paramExample != "(无需参数)" { + body = paramExample + } + return fmt.Sprintf(`curl -X POST "%s%s" \ + -H "X-API-Key: your_api_key" \ + -H "Content-Type: application/json" \ + -d '%s'`, baseURL, path, body) + default: + return fmt.Sprintf(`curl -X %s "%s%s" \ + -H "X-API-Key: your_api_key"`, method, baseURL, path) + } +} + +// shouldExcludeRoute 判断该路由是否应该排除出文档(非业务路由) +func shouldExcludeRoute(path string) bool { + excludePrefixes := []string{ + "/api/v1/public/login", + "/api/v1/logout", + "/api/v1/ws", + "/api/v1/center/update", + "/api/v1/oplatform/doc", + } + for _, prefix := range excludePrefixes { + if strings.HasPrefix(path, prefix) { + return true + } + } + return false +} + +// GetDocApi 获取 API 文档(供管理界面调用,需要 Token 或 API Key 鉴权) +func (w *WafOPlatformDocApi) GetDocApi(c *gin.Context) { + if GinEngineRef == nil { + response.FailWithMessage("文档服务未就绪", c) + return + } + routes := GinEngineRef.Routes() + + modules := make(map[string][]apiRouteInfo) + totalCount := 0 + + for _, route := range routes { + if shouldExcludeRoute(route.Path) { + continue + } + if !strings.HasPrefix(route.Path, "/api/v1/") { + continue + } + + module := getModuleByPath(route.Path) + + // 查找静态注释信息,key 格式为 "METHOD /path" + detailKey := strings.ToUpper(route.Method) + " " + route.Path + detail, hasDetail := routeDetailMap[detailKey] + + description := "" + paramExample := "" + responseExample := "" + if hasDetail { + description = detail.Description + paramExample = detail.ParamExample + responseExample = detail.ResponseExample + } + + info := apiRouteInfo{ + Method: route.Method, + Path: route.Path, + Module: module, + Description: description, + ParamExample: paramExample, + ResponseExample: responseExample, + CurlExample: buildCurlExample(route.Method, route.Path, paramExample), + } + modules[module] = append(modules[module], info) + totalCount++ + } + + // 对每个模块内的路由按路径排序,便于阅读 + for mod := range modules { + sort.Slice(modules[mod], func(i, j int) bool { + return modules[mod][i].Path < modules[mod][j].Path + }) + } + + doc := apiDocResponse{ + AuthInfo: authInfo{ + HeaderName: "X-API-Key", + Description: "在请求头中传入 API Key 进行鉴权,通过管理端「开放平台 > Key管理」创建后使用", + Example: "X-API-Key: sk-xxxxxxxxxxxx", + }, + BaseURL: "http://your-samwaf-host:26666", + TotalCount: totalCount, + Modules: modules, + } + + response.OkWithData(doc, c) +} + +// GetDocStatusApi 查询开放平台是否已启用(供管理界面 doc.vue 决定是否展示 Swagger UI) +func (w *WafOPlatformDocApi) GetDocStatusApi(c *gin.Context) { + enabled := int64(0) + if global.GCONFIG_OPEN_PLATFORM_ENABLED == 1 { + enabled = 1 + } + response.OkWithData(gin.H{"enabled": enabled}, c) +} diff --git a/api/waf_oplatform_key.go b/api/waf_oplatform_key.go new file mode 100644 index 0000000..9187c19 --- /dev/null +++ b/api/waf_oplatform_key.go @@ -0,0 +1,102 @@ +package api + +import ( + "SamWaf/model/common/response" + "SamWaf/model/request" + "github.com/gin-gonic/gin" +) + +type WafOPlatformKeyApi struct{} + +func (w *WafOPlatformKeyApi) AddApi(c *gin.Context) { + var req request.WafOPlatformKeyAddReq + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage("解析失败:"+err.Error(), c) + return + } + if req.KeyName == "" { + response.FailWithMessage("Key名称不能为空", c) + return + } + id, apiKey, err := wafOPlatformKeyService.AddApi(req) + if err != nil { + response.FailWithMessage("添加失败:"+err.Error(), c) + return + } + response.OkWithDetailed(gin.H{"id": id, "api_key": apiKey}, "添加成功", c) +} + +func (w *WafOPlatformKeyApi) ModifyApi(c *gin.Context) { + var req request.WafOPlatformKeyEditReq + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage("解析失败:"+err.Error(), c) + return + } + if err := wafOPlatformKeyService.ModifyApi(req); err != nil { + response.FailWithMessage("修改失败:"+err.Error(), c) + return + } + response.OkWithMessage("修改成功", c) +} + +func (w *WafOPlatformKeyApi) DelApi(c *gin.Context) { + var req request.WafOPlatformKeyDelReq + if err := c.ShouldBindQuery(&req); err != nil { + response.FailWithMessage("解析失败:"+err.Error(), c) + return + } + if err := wafOPlatformKeyService.DelApi(req); err != nil { + response.FailWithMessage("删除失败:"+err.Error(), c) + return + } + response.OkWithMessage("删除成功", c) +} + +func (w *WafOPlatformKeyApi) GetDetailApi(c *gin.Context) { + var req request.WafOPlatformKeyDetailReq + if err := c.ShouldBindQuery(&req); err != nil { + response.FailWithMessage("解析失败:"+err.Error(), c) + return + } + bean := wafOPlatformKeyService.GetDetailApi(req) + response.OkWithData(bean, c) +} + +func (w *WafOPlatformKeyApi) GetListApi(c *gin.Context) { + var req request.WafOPlatformKeySearchReq + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage("解析失败:"+err.Error(), c) + return + } + if req.PageSize == 0 { + req.PageSize = 10 + } + if req.PageIndex == 0 { + req.PageIndex = 1 + } + list, total, err := wafOPlatformKeyService.GetListApi(req) + if err != nil { + response.FailWithMessage("查询失败", c) + return + } + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + PageIndex: req.PageIndex, + PageSize: req.PageSize, + }, "查询成功", c) +} + +func (w *WafOPlatformKeyApi) ResetSecretApi(c *gin.Context) { + var req request.WafOPlatformKeyResetSecretReq + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage("解析失败:"+err.Error(), c) + return + } + newApiKey, err := wafOPlatformKeyService.ResetApiKey(req) + if err != nil { + response.FailWithMessage("重置失败:"+err.Error(), c) + return + } + response.OkWithDetailed(gin.H{"api_key": newApiKey}, "重置成功", c) +} diff --git a/api/waf_oplatform_log.go b/api/waf_oplatform_log.go new file mode 100644 index 0000000..804db4b --- /dev/null +++ b/api/waf_oplatform_log.go @@ -0,0 +1,57 @@ +package api + +import ( + "SamWaf/model/common/response" + "SamWaf/model/request" + "github.com/gin-gonic/gin" +) + +type WafOPlatformLogApi struct{} + +func (w *WafOPlatformLogApi) GetDetailApi(c *gin.Context) { + var req request.WafOPlatformLogDetailReq + if err := c.ShouldBindQuery(&req); err != nil { + response.FailWithMessage("解析失败:"+err.Error(), c) + return + } + bean := wafOPlatformLogService.GetDetailApi(req) + response.OkWithData(bean, c) +} + +func (w *WafOPlatformLogApi) GetListApi(c *gin.Context) { + var req request.WafOPlatformLogSearchReq + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage("解析失败:"+err.Error(), c) + return + } + if req.PageSize == 0 { + req.PageSize = 10 + } + if req.PageIndex == 0 { + req.PageIndex = 1 + } + list, total, err := wafOPlatformLogService.GetListApi(req) + if err != nil { + response.FailWithMessage("查询失败", c) + return + } + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + PageIndex: req.PageIndex, + PageSize: req.PageSize, + }, "查询成功", c) +} + +func (w *WafOPlatformLogApi) DelApi(c *gin.Context) { + var req request.WafOPlatformLogDelReq + if err := c.ShouldBindQuery(&req); err != nil { + response.FailWithMessage("解析失败:"+err.Error(), c) + return + } + if err := wafOPlatformLogService.DelApi(req); err != nil { + response.FailWithMessage("删除失败:"+err.Error(), c) + return + } + response.OkWithMessage("删除成功", c) +} diff --git a/api/waf_sys_info.go b/api/waf_sys_info.go index 674a3ca..51b7be1 100644 --- a/api/waf_sys_info.go +++ b/api/waf_sys_info.go @@ -93,6 +93,14 @@ func (w *WafSysInfoApi) GetAnnouncementApi(c *gin.Context) { } } +// SysVersionApi 获取系统版本信息 +// @Summary 获取系统版本信息 +// @Description 返回当前 SamWaf 的版本号、版本名称及发布状态 +// @Tags 系统信息 +// @Produce json +// @Success 200 {object} response.Response{data=model.VersionInfo} "获取成功" +// @Security ApiKeyAuth +// @Router /sysinfo/version [get] func (w *WafSysInfoApi) SysVersionApi(c *gin.Context) { response.OkWithDetailed(model.VersionInfo{ Version: global.GWAF_RELEASE_VERSION, diff --git a/cmd/samwaf/main.go b/cmd/samwaf/main.go index b86b9fe..f68d5d5 100644 --- a/cmd/samwaf/main.go +++ b/cmd/samwaf/main.go @@ -805,6 +805,13 @@ func (m *wafSystenService) Graceful() { //https://github.com/pengge/uranus/blob/main/main.go 预备参考 } +// @title SamWaf Open API +// @version 1.0 +// @description SamWaf Web Application Firewall 开放平台 API 文档。需要在请求头携带 X-API-Key 进行鉴权。 +// @BasePath /api/v1 +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name X-API-Key func main() { fmt.Println(` ========================================== diff --git a/global/config.go b/global/config.go index 9e5361b..179bb91 100644 --- a/global/config.go +++ b/global/config.go @@ -70,6 +70,9 @@ var ( GCONFIG_ZEROSSL_EAB_KID string = "" // zerossl eab_kid GCONFIG_ZEROSSL_EAB_HMAC_KEY string = "" // zerossl eab_hmac_key + // 开放平台配置 + GCONFIG_OPEN_PLATFORM_ENABLED int64 = 0 // 开放平台开关,默认关闭 有需要的开启 1启用 0禁用 + // 日志文件写入配置 (额外输出,不影响SQLite存储) GCONFIG_LOG_FILE_WRITE_ENABLE int64 = 0 // 日志文件写入开关 (0关闭 1开启) GCONFIG_LOG_FILE_WRITE_PATH string = "logs/access.log" // 日志文件路径 diff --git a/go.mod b/go.mod index 0295821..21e7cc1 100644 --- a/go.mod +++ b/go.mod @@ -54,8 +54,11 @@ require ( require ( dario.cat/mergo v1.0.0 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 // indirect github.com/alibabacloud-go/debug v1.0.1 // indirect @@ -88,6 +91,10 @@ require ( github.com/go-git/go-git/v5 v5.11.0 // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.19.15 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.23.0 // indirect @@ -101,6 +108,7 @@ require ( github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.17.8 // indirect @@ -108,6 +116,7 @@ require ( github.com/leodido/go-urn v1.4.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magefile/mage v1.15.1-0.20241126214340-bdc92f694516 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/miekg/dns v1.1.69 // indirect @@ -137,6 +146,9 @@ require ( github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/swaggo/files v1.0.1 // indirect + github.com/swaggo/gin-swagger v1.6.1 // indirect + github.com/swaggo/swag v1.8.12 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.12 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect diff --git a/go.sum b/go.sum index e2e2b1a..e109e73 100644 --- a/go.sum +++ b/go.sum @@ -4,11 +4,17 @@ dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA= github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= @@ -102,6 +108,7 @@ github.com/corazawaf/coraza/v3 v3.3.3 h1:kqjStHAgWqwP5dh7n0vhTOF0a3t+VikNS/EaMiG github.com/corazawaf/coraza/v3 v3.3.3/go.mod h1:xSaXWOhFMSbrV8qOOfBKAyw3aOqfwaSaOy5BgSF8XlA= github.com/corazawaf/libinjection-go v0.2.2 h1:Chzodvb6+NXh6wew5/yhD0Ggioif9ACrQGR4qjTCs1g= github.com/corazawaf/libinjection-go v0.2.2/go.mod h1:OP4TM7xdJ2skyXqNX1AN1wN5nNZEmJNuWbNPOItn7aw= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -166,6 +173,16 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -238,6 +255,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU= @@ -276,6 +295,10 @@ github.com/magefile/mage v1.15.1-0.20231118170541-2385abb49a1f h1:iiLWLoibjCL0XN github.com/magefile/mage v1.15.1-0.20231118170541-2385abb49a1f/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magefile/mage v1.15.1-0.20241126214340-bdc92f694516 h1:aAO0L0ulox6m/CLRYvJff+jWXYYCKGpEm3os7dM/Z+M= github.com/magefile/mage v1.15.1-0.20241126214340-bdc92f694516/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= @@ -389,6 +412,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= @@ -399,6 +423,12 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY= +github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw= +github.com/swaggo/swag v1.8.12 h1:pctzkNPu0AlQP2royqX3apjKCQonAnf7KGoxeO4y64w= +github.com/swaggo/swag v1.8.12/go.mod h1:lNfm6Gg+oAq3zRJQNEMBE66LIJKM44mxFqhEEgy2its= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.25/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.12 h1:/ABtv4x4FSGxGW0d6Sc88iQn6Up2LalWKwt/Tj7Dtz8= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.12/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= @@ -520,6 +550,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= @@ -563,6 +594,7 @@ golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -665,6 +697,7 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -682,6 +715,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw= diff --git a/middleware/auth_api_check.go b/middleware/auth_api_check.go index f806552..5ec18f8 100644 --- a/middleware/auth_api_check.go +++ b/middleware/auth_api_check.go @@ -23,6 +23,18 @@ var ( func Auth() gin.HandlerFunc { innerName := "Auth" return func(c *gin.Context) { + // 优先检查 OpenAPI Key(X-API-Key) + apiKey := c.GetHeader("X-API-Key") + if apiKey != "" { + _, ok := ValidateOpenApiKey(c, apiKey) + if !ok { + c.Abort() + return + } + c.Next() + return + } + // 获取请求头中 token,实际是一个完整被签名过的 token;a complete, signed token tokenStr := "" loginType := c.GetHeader("X-Login-Type") diff --git a/middleware/openapi_auth.go b/middleware/openapi_auth.go new file mode 100644 index 0000000..7fb1cf2 --- /dev/null +++ b/middleware/openapi_auth.go @@ -0,0 +1,221 @@ +package middleware + +import ( + "SamWaf/common/zlog" + "SamWaf/global" + "SamWaf/model" + "SamWaf/model/common/response" + "SamWaf/service/waf_service" + "bytes" + "io" + "net" + "strings" + "sync" + "time" + + "github.com/gin-gonic/gin" +) + +// rateLimitEntry 限流计数器条目 +type rateLimitEntry struct { + count int64 + windowEnd time.Time + mu sync.Mutex +} + +var ( + openApiKeyService = waf_service.WafOPlatformKeyServiceApp + openApiLogService = waf_service.WafOPlatformLogServiceApp + rateLimitMap sync.Map // key: api_key_id, value: *rateLimitEntry +) + +// bodyWriter 包装 gin.ResponseWriter,用于捕获响应体 +type bodyWriter struct { + gin.ResponseWriter + body *bytes.Buffer + statusCode int +} + +func newBodyWriter(c *gin.Context) *bodyWriter { + return &bodyWriter{ + ResponseWriter: c.Writer, + body: &bytes.Buffer{}, + statusCode: 200, + } +} + +func (w *bodyWriter) Write(b []byte) (int, error) { + w.body.Write(b) + return w.ResponseWriter.Write(b) +} + +func (w *bodyWriter) WriteHeader(code int) { + w.statusCode = code + w.ResponseWriter.WriteHeader(code) +} + +// ValidateOpenApiKey 校验 API Key 并设置上下文标记,返回 Key 记录和是否通过 +func ValidateOpenApiKey(c *gin.Context, apiKey string) (model.OPlatformKey, bool) { + // 1. 检查开放平台总开关 + if global.GCONFIG_OPEN_PLATFORM_ENABLED == 0 { + response.AuthFailWithMessage("开放平台已关闭", c) + return model.OPlatformKey{}, false + } + + // 2. 查询 Key 记录 + keyBean, err := openApiKeyService.GetByApiKey(apiKey) + if err != nil { + response.AuthFailWithMessage("无效的 API Key", c) + return model.OPlatformKey{}, false + } + + // 3. 检查启用状态 + if keyBean.Status != 1 { + response.AuthFailWithMessage("API Key 已禁用", c) + return model.OPlatformKey{}, false + } + + // 4. 检查过期时间 + if keyBean.ExpireTime != "" { + expireTime, parseErr := time.Parse("2006-01-02 15:04:05", keyBean.ExpireTime) + if parseErr == nil && time.Now().After(expireTime) { + response.AuthFailWithMessage("API Key 已过期", c) + return model.OPlatformKey{}, false + } + } + + // 5. 检查 IP 白名单 + if keyBean.IPWhitelist != "" { + clientIP := c.ClientIP() + allowedIPs := strings.Split(keyBean.IPWhitelist, ",") + ipAllowed := false + for _, allowedIP := range allowedIPs { + allowedIP = strings.TrimSpace(allowedIP) + if allowedIP == "" { + continue + } + // 支持 CIDR 格式 + if strings.Contains(allowedIP, "/") { + _, ipNet, cidrErr := net.ParseCIDR(allowedIP) + if cidrErr == nil && ipNet.Contains(net.ParseIP(clientIP)) { + ipAllowed = true + break + } + } else if allowedIP == clientIP { + ipAllowed = true + break + } + } + if !ipAllowed { + response.AuthFailWithMessage("IP 不在白名单中", c) + return model.OPlatformKey{}, false + } + } + + // 6. 限流检查(按分钟) + if keyBean.RateLimit > 0 { + entryVal, _ := rateLimitMap.LoadOrStore(keyBean.Id, &rateLimitEntry{ + windowEnd: time.Now().Add(time.Minute), + }) + entry := entryVal.(*rateLimitEntry) + entry.mu.Lock() + now := time.Now() + if now.After(entry.windowEnd) { + // 新的时间窗口 + entry.count = 0 + entry.windowEnd = now.Add(time.Minute) + } + entry.count++ + currentCount := entry.count + entry.mu.Unlock() + + if currentCount > keyBean.RateLimit { + response.FailWithMessage("请求频率超限,请稍后再试", c) + return model.OPlatformKey{}, false + } + } + + // 7. 设置 OpenAPI 标记(用于 response 跳过 AES 加密) + c.Set("is_openapi", true) + c.Set("openapi_key_id", keyBean.Id) + c.Set("openapi_key_name", keyBean.KeyName) + + return keyBean, true +} + +// OpenApiLogMiddleware 记录 OpenAPI 调用日志的中间件 +// 在 Auth 之后调用,负责捕获请求/响应并异步写日志 +func OpenApiLogMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + isOpenApi, exists := c.Get("is_openapi") + if !exists || isOpenApi != true { + c.Next() + return + } + + startTime := time.Now() + path := c.Request.URL.Path + method := c.Request.Method + clientIP := c.ClientIP() + + keyId, _ := c.Get("openapi_key_id") + keyName, _ := c.Get("openapi_key_name") + + // 读取请求体(文件类请求不记录 body) + requestBody := "" + contentType := c.GetHeader("Content-Type") + isFileRequest := strings.Contains(contentType, "multipart") || + strings.Contains(path, "/download") || + strings.Contains(path, "/upload") + + if !isFileRequest && c.Request.Body != nil { + bodyBytes, err := io.ReadAll(io.LimitReader(c.Request.Body, 2048)) + if err == nil { + requestBody = string(bodyBytes) + // 恢复 body 供后续 handler 使用 + c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + } + } + + // 包装 ResponseWriter 以捕获响应 + bw := newBodyWriter(c) + c.Writer = bw + + c.Next() + + duration := time.Since(startTime).Milliseconds() + + // 响应体(截断超过 2KB 的部分) + responseBody := "" + if !isFileRequest { + responseBody = bw.body.String() + if len(responseBody) > 2048 { + responseBody = responseBody[:2048] + "...(truncated)" + } + } + + // 异步记录日志 + logEntry := model.OPlatformLog{ + ApiKeyId: keyId.(string), + KeyName: keyName.(string), + RequestPath: path, + RequestMethod: method, + RequestBody: requestBody, + ResponseBody: responseBody, + ClientIP: clientIP, + StatusCode: bw.statusCode, + Duration: duration, + TimeStr: startTime.Format("2006-01-02 15:04:05"), + } + openApiLogService.AddLogAsync(logEntry) + + // 异步更新调用次数 + go func() { + if id, ok := keyId.(string); ok && id != "" { + openApiKeyService.IncrCallCount(id) + } + }() + + zlog.Debug("OpenAPI调用", "path", path, "method", method, "ip", clientIP, "duration_ms", duration) + } +} diff --git a/model/common/response/response.go b/model/common/response/response.go index 337ec58..31ffc12 100644 --- a/model/common/response/response.go +++ b/model/common/response/response.go @@ -23,6 +23,15 @@ const ( ) func Result(code int, data interface{}, msg string, c *gin.Context) { + // OpenAPI 请求直接返回明文 JSON,不做 AES 加密 + if isOpenApi, exists := c.Get("is_openapi"); exists && isOpenApi == true { + c.JSON(http.StatusOK, Response{ + code, + data, + msg, + }) + return + } result, _ := json.Marshal(data) //将数据转换为json encryptStr, _ := wafsec.AesEncrypt(result, global.GWAF_COMMUNICATION_KEY) // 开始时间 diff --git a/model/oplatform_key.go b/model/oplatform_key.go new file mode 100644 index 0000000..18ee52b --- /dev/null +++ b/model/oplatform_key.go @@ -0,0 +1,22 @@ +package model + +import ( + "SamWaf/model/baseorm" +) + +/* +* +开放平台 API Key +*/ +type OPlatformKey struct { + baseorm.BaseOrm + KeyName string `json:"key_name"` //Key 名称 + ApiKey string `json:"api_key"` //API Key(自动生成,用于 X-API-Key 请求头鉴权) + Status int `json:"status"` //状态 1启用 0禁用 + Remark string `json:"remark"` //备注 + RateLimit int64 `json:"rate_limit"` //每分钟限流次数,0不限 + IPWhitelist string `json:"ip_whitelist"` //IP白名单,逗号分隔,空不限 + ExpireTime string `json:"expire_time"` //过期时间,空不过期 + LastUseTime string `json:"last_use_time"` //最后使用时间 + CallCount int64 `json:"call_count"` //累计调用次数 +} diff --git a/model/oplatform_log.go b/model/oplatform_log.go new file mode 100644 index 0000000..cbfc1fa --- /dev/null +++ b/model/oplatform_log.go @@ -0,0 +1,23 @@ +package model + +import ( + "SamWaf/model/baseorm" +) + +/* +* +开放平台调用日志 +*/ +type OPlatformLog struct { + baseorm.BaseOrm + ApiKeyId string `json:"api_key_id"` //关联的Key ID + KeyName string `json:"key_name"` //Key名称(冗余,方便查询) + RequestPath string `json:"request_path"` //请求路径 + RequestMethod string `json:"request_method"` //请求方法 + RequestBody string `json:"request_body"` //请求体(非文件类型记录) + ResponseBody string `json:"response_body"` //响应体 + ClientIP string `json:"client_ip"` //调用者IP + StatusCode int `json:"status_code"` //响应状态码 + Duration int64 `json:"duration"` //耗时(ms) + TimeStr string `json:"time_str"` //调用时间字符串 +} diff --git a/model/request/waf_oplatform_key_req.go b/model/request/waf_oplatform_key_req.go new file mode 100644 index 0000000..cf47000 --- /dev/null +++ b/model/request/waf_oplatform_key_req.go @@ -0,0 +1,38 @@ +package request + +import "SamWaf/model/common/request" + +type WafOPlatformKeyAddReq struct { + KeyName string `json:"key_name"` //Key 名称 + Remark string `json:"remark"` //备注 + RateLimit int64 `json:"rate_limit"` //每分钟限流次数,0不限 + IPWhitelist string `json:"ip_whitelist"` //IP白名单,逗号分隔,空不限 + ExpireTime string `json:"expire_time"` //过期时间,空不过期 +} + +type WafOPlatformKeyDelReq struct { + Id string `json:"id" form:"id"` //唯一键 +} + +type WafOPlatformKeyDetailReq struct { + Id string `json:"id" form:"id"` //唯一键 +} + +type WafOPlatformKeyEditReq struct { + Id string `json:"id"` + KeyName string `json:"key_name"` //Key 名称 + Status int `json:"status"` //状态 1启用 0禁用 + Remark string `json:"remark"` //备注 + RateLimit int64 `json:"rate_limit"` //每分钟限流次数,0不限 + IPWhitelist string `json:"ip_whitelist"` //IP白名单,逗号分隔,空不限 + ExpireTime string `json:"expire_time"` //过期时间,空不过期 +} + +type WafOPlatformKeySearchReq struct { + KeyName string `json:"key_name" form:"key_name"` //Key名称 + request.PageInfo +} + +type WafOPlatformKeyResetSecretReq struct { + Id string `json:"id" form:"id"` //唯一键 +} diff --git a/model/request/waf_oplatform_log_req.go b/model/request/waf_oplatform_log_req.go new file mode 100644 index 0000000..11e5312 --- /dev/null +++ b/model/request/waf_oplatform_log_req.go @@ -0,0 +1,19 @@ +package request + +import "SamWaf/model/common/request" + +type WafOPlatformLogDelReq struct { + Id string `json:"id" form:"id"` //唯一键 +} + +type WafOPlatformLogDetailReq struct { + Id string `json:"id" form:"id"` //唯一键 +} + +type WafOPlatformLogSearchReq struct { + KeyName string `json:"key_name" form:"key_name"` //Key名称 + RequestPath string `json:"request_path" form:"request_path"` //请求路径 + ClientIP string `json:"client_ip" form:"client_ip"` //客户端IP + RequestMethod string `json:"request_method" form:"request_method"` //请求方法 + request.PageInfo +} diff --git a/router/entrance.go b/router/entrance.go index 087af66..08375fa 100644 --- a/router/entrance.go +++ b/router/entrance.go @@ -52,6 +52,9 @@ type ApiGroup struct { PluginRouter LogFileWriteRouter IPLocationRouter + WafOPlatformKeyRouter + WafOPlatformLogRouter + WafOPlatformDocRouter } type PublicApiGroup struct { LoginRouter diff --git a/router/waf_oplatform_doc.go b/router/waf_oplatform_doc.go new file mode 100644 index 0000000..e07fea6 --- /dev/null +++ b/router/waf_oplatform_doc.go @@ -0,0 +1,15 @@ +package router + +import ( + "SamWaf/api" + "github.com/gin-gonic/gin" +) + +type WafOPlatformDocRouter struct{} + +func (receiver *WafOPlatformDocRouter) InitOPlatformDocRouter(group *gin.RouterGroup) { + docApi := api.APIGroupAPP.WafOPlatformDocApi + router := group.Group("") + router.GET("/api/v1/oplatform/doc/api", docApi.GetDocApi) + router.GET("/api/v1/oplatform/doc/status", docApi.GetDocStatusApi) +} diff --git a/router/waf_oplatform_key.go b/router/waf_oplatform_key.go new file mode 100644 index 0000000..e6720e2 --- /dev/null +++ b/router/waf_oplatform_key.go @@ -0,0 +1,19 @@ +package router + +import ( + "SamWaf/api" + "github.com/gin-gonic/gin" +) + +type WafOPlatformKeyRouter struct{} + +func (receiver *WafOPlatformKeyRouter) InitOPlatformKeyRouter(group *gin.RouterGroup) { + oplatformApi := api.APIGroupAPP.WafOPlatformKeyApi + router := group.Group("") + router.POST("/api/v1/oplatform/key/list", oplatformApi.GetListApi) + router.GET("/api/v1/oplatform/key/detail", oplatformApi.GetDetailApi) + router.POST("/api/v1/oplatform/key/add", oplatformApi.AddApi) + router.POST("/api/v1/oplatform/key/edit", oplatformApi.ModifyApi) + router.GET("/api/v1/oplatform/key/del", oplatformApi.DelApi) + router.POST("/api/v1/oplatform/key/resetSecret", oplatformApi.ResetSecretApi) +} diff --git a/router/waf_oplatform_log.go b/router/waf_oplatform_log.go new file mode 100644 index 0000000..5231f9c --- /dev/null +++ b/router/waf_oplatform_log.go @@ -0,0 +1,16 @@ +package router + +import ( + "SamWaf/api" + "github.com/gin-gonic/gin" +) + +type WafOPlatformLogRouter struct{} + +func (receiver *WafOPlatformLogRouter) InitOPlatformLogRouter(group *gin.RouterGroup) { + oplatformApi := api.APIGroupAPP.WafOPlatformLogApi + router := group.Group("") + router.POST("/api/v1/oplatform/log/list", oplatformApi.GetListApi) + router.GET("/api/v1/oplatform/log/detail", oplatformApi.GetDetailApi) + router.GET("/api/v1/oplatform/log/del", oplatformApi.DelApi) +} diff --git a/service/waf_service/waf_oplatform_key.go b/service/waf_service/waf_oplatform_key.go new file mode 100644 index 0000000..2d3d69a --- /dev/null +++ b/service/waf_service/waf_oplatform_key.go @@ -0,0 +1,138 @@ +package waf_service + +import ( + "SamWaf/common/uuid" + "SamWaf/customtype" + "SamWaf/global" + "SamWaf/model" + "SamWaf/model/baseorm" + "SamWaf/model/request" + "crypto/rand" + "encoding/hex" + "errors" + "time" +) + +type WafOPlatformKeyService struct{} + +var WafOPlatformKeyServiceApp = new(WafOPlatformKeyService) + +// genRandomHex 生成随机十六进制字符串 +func genRandomHex(n int) string { + b := make([]byte, n) + _, _ = rand.Read(b) + return hex.EncodeToString(b) +} + +// maskApiKey 对 API Key 做脱敏,保留前6位和后4位,中间替换为 **** +func maskApiKey(key string) string { + if len(key) <= 10 { + return key + } + return key[:6] + "****" + key[len(key)-4:] +} + +func (receiver *WafOPlatformKeyService) AddApi(req request.WafOPlatformKeyAddReq) (string, string, error) { + apiKey := "sk-" + genRandomHex(16) + id := uuid.GenUUID() + var bean = &model.OPlatformKey{ + BaseOrm: baseorm.BaseOrm{ + Id: id, + USER_CODE: global.GWAF_USER_CODE, + Tenant_ID: global.GWAF_TENANT_ID, + CREATE_TIME: customtype.JsonTime(time.Now()), + UPDATE_TIME: customtype.JsonTime(time.Now()), + }, + KeyName: req.KeyName, + ApiKey: apiKey, + Status: 1, + Remark: req.Remark, + RateLimit: req.RateLimit, + IPWhitelist: req.IPWhitelist, + ExpireTime: req.ExpireTime, + CallCount: 0, + } + err := global.GWAF_LOCAL_DB.Create(bean).Error + return id, apiKey, err +} + +func (receiver *WafOPlatformKeyService) ModifyApi(req request.WafOPlatformKeyEditReq) error { + editMap := map[string]interface{}{ + "KeyName": req.KeyName, + "Status": req.Status, + "Remark": req.Remark, + "RateLimit": req.RateLimit, + "IPWhitelist": req.IPWhitelist, + "ExpireTime": req.ExpireTime, + "UPDATE_TIME": customtype.JsonTime(time.Now()), + } + return global.GWAF_LOCAL_DB.Model(model.OPlatformKey{}).Where("id = ?", req.Id).Updates(editMap).Error +} + +func (receiver *WafOPlatformKeyService) DelApi(req request.WafOPlatformKeyDelReq) error { + var bean model.OPlatformKey + err := global.GWAF_LOCAL_DB.Where("id = ?", req.Id).First(&bean).Error + if err != nil { + return err + } + return global.GWAF_LOCAL_DB.Where("id = ?", req.Id).Delete(model.OPlatformKey{}).Error +} + +func (receiver *WafOPlatformKeyService) GetDetailApi(req request.WafOPlatformKeyDetailReq) model.OPlatformKey { + var bean model.OPlatformKey + global.GWAF_LOCAL_DB.Where("id = ?", req.Id).Find(&bean) + bean.ApiKey = maskApiKey(bean.ApiKey) + return bean +} + +func (receiver *WafOPlatformKeyService) GetListApi(req request.WafOPlatformKeySearchReq) ([]model.OPlatformKey, int64, error) { + var list []model.OPlatformKey + var total int64 = 0 + + whereField := "" + var whereValues []interface{} + if len(req.KeyName) > 0 { + whereField = "key_name like ?" + whereValues = append(whereValues, "%"+req.KeyName+"%") + } + + global.GWAF_LOCAL_DB.Model(&model.OPlatformKey{}).Where(whereField, whereValues...). + Limit(req.PageSize).Offset(req.PageSize * (req.PageIndex - 1)).Find(&list) + global.GWAF_LOCAL_DB.Model(&model.OPlatformKey{}).Where(whereField, whereValues...).Count(&total) + + for i := range list { + list[i].ApiKey = maskApiKey(list[i].ApiKey) + } + return list, total, nil +} + +// ResetApiKey 重置 API Key,旧 Key 立即失效 +func (receiver *WafOPlatformKeyService) ResetApiKey(req request.WafOPlatformKeyResetSecretReq) (string, error) { + var bean model.OPlatformKey + err := global.GWAF_LOCAL_DB.Where("id = ?", req.Id).First(&bean).Error + if err != nil { + return "", errors.New("Key不存在") + } + newApiKey := "sk-" + genRandomHex(16) + err = global.GWAF_LOCAL_DB.Model(model.OPlatformKey{}).Where("id = ?", req.Id).Updates(map[string]interface{}{ + "ApiKey": newApiKey, + "UPDATE_TIME": customtype.JsonTime(time.Now()), + }).Error + return newApiKey, err +} + +// GetByApiKey 通过 ApiKey 字符串获取 Key 记录 +func (receiver *WafOPlatformKeyService) GetByApiKey(apiKey string) (model.OPlatformKey, error) { + var bean model.OPlatformKey + err := global.GWAF_LOCAL_DB.Where("api_key = ?", apiKey).First(&bean).Error + return bean, err +} + +// IncrCallCount 增加调用次数并更新最后使用时间(异步调用) +func (receiver *WafOPlatformKeyService) IncrCallCount(id string) { + now := time.Now() + global.GWAF_LOCAL_DB.Exec( + "UPDATE o_platform_keys SET call_count = call_count + 1, last_use_time = ?, update_time = ? WHERE id = ?", + now.Format("2006-01-02 15:04:05"), customtype.JsonTime(now), id, + ) +} diff --git a/service/waf_service/waf_oplatform_log.go b/service/waf_service/waf_oplatform_log.go new file mode 100644 index 0000000..79a0049 --- /dev/null +++ b/service/waf_service/waf_oplatform_log.go @@ -0,0 +1,88 @@ +package waf_service + +import ( + "SamWaf/common/uuid" + "SamWaf/customtype" + "SamWaf/global" + "SamWaf/model" + "SamWaf/model/request" + "time" +) + +type WafOPlatformLogService struct{} + +var WafOPlatformLogServiceApp = new(WafOPlatformLogService) + +// AddLog 记录一条调用日志 +func (receiver *WafOPlatformLogService) AddLog(logEntry model.OPlatformLog) error { + logEntry.Id = uuid.GenUUID() + logEntry.USER_CODE = global.GWAF_USER_CODE + logEntry.Tenant_ID = global.GWAF_TENANT_ID + logEntry.CREATE_TIME = customtype.JsonTime(time.Now()) + logEntry.UPDATE_TIME = customtype.JsonTime(time.Now()) + if logEntry.TimeStr == "" { + logEntry.TimeStr = time.Now().Format("2006-01-02 15:04:05") + } + return global.GWAF_LOCAL_LOG_DB.Create(&logEntry).Error +} + +func (receiver *WafOPlatformLogService) GetDetailApi(req request.WafOPlatformLogDetailReq) model.OPlatformLog { + var bean model.OPlatformLog + global.GWAF_LOCAL_LOG_DB.Where("id = ?", req.Id).Find(&bean) + return bean +} + +func (receiver *WafOPlatformLogService) GetListApi(req request.WafOPlatformLogSearchReq) ([]model.OPlatformLog, int64, error) { + var list []model.OPlatformLog + var total int64 = 0 + + whereField := "" + var whereValues []interface{} + + if len(req.KeyName) > 0 { + if len(whereField) > 0 { + whereField += " and " + } + whereField += "key_name like ?" + whereValues = append(whereValues, "%"+req.KeyName+"%") + } + if len(req.RequestPath) > 0 { + if len(whereField) > 0 { + whereField += " and " + } + whereField += "request_path like ?" + whereValues = append(whereValues, "%"+req.RequestPath+"%") + } + if len(req.ClientIP) > 0 { + if len(whereField) > 0 { + whereField += " and " + } + whereField += "client_ip = ?" + whereValues = append(whereValues, req.ClientIP) + } + if len(req.RequestMethod) > 0 { + if len(whereField) > 0 { + whereField += " and " + } + whereField += "request_method = ?" + whereValues = append(whereValues, req.RequestMethod) + } + + global.GWAF_LOCAL_LOG_DB.Model(&model.OPlatformLog{}).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.OPlatformLog{}).Where(whereField, whereValues...).Count(&total) + + return list, total, nil +} + +func (receiver *WafOPlatformLogService) DelApi(req request.WafOPlatformLogDelReq) error { + return global.GWAF_LOCAL_LOG_DB.Where("id = ?", req.Id).Delete(model.OPlatformLog{}).Error +} + +// AddLogAsync 异步记录日志,不阻塞主流程 +func (receiver *WafOPlatformLogService) AddLogAsync(logEntry model.OPlatformLog) { + go func() { + _ = receiver.AddLog(logEntry) + }() +} diff --git a/wafdb/migrations_core.go b/wafdb/migrations_core.go index de03b23..5f88154 100644 --- a/wafdb/migrations_core.go +++ b/wafdb/migrations_core.go @@ -607,6 +607,51 @@ func RunCoreDBMigrations(db *gorm.DB) error { return nil }, }, + // 迁移16: 创建开放平台 API Key 表(不含 api_secret) + { + ID: "202603060001_add_oplatform_key_table", + Migrate: func(tx *gorm.DB) error { + zlog.Info("迁移 202603060001: 创建开放平台 API Key 表") + + if tx.Migrator().HasTable(&model.OPlatformKey{}) { + zlog.Info("o_platform_keys 表已存在,执行结构同步") + if err := tx.AutoMigrate(&model.OPlatformKey{}); err != nil { + return fmt.Errorf("同步 o_platform_keys 表结构失败: %w", err) + } + return nil + } + + if err := tx.AutoMigrate(&model.OPlatformKey{}); err != nil { + return fmt.Errorf("创建 o_platform_keys 表失败: %w", err) + } + + zlog.Info("o_platform_keys 表创建成功") + return nil + }, + Rollback: func(tx *gorm.DB) error { + zlog.Info("回滚 202603060001: 删除开放平台 API Key 表") + return tx.Migrator().DropTable(&model.OPlatformKey{}) + }, + }, + // 迁移17: 移除开放平台 API Key 表中冗余的 api_secret 列 + { + ID: "202603060002_drop_api_secret_from_oplatform_key", + Migrate: func(tx *gorm.DB) error { + zlog.Info("迁移 202603060002: 移除 o_platform_keys.api_secret 列") + if tx.Migrator().HasColumn(&model.OPlatformKey{}, "api_secret") { + if err := tx.Migrator().DropColumn(&model.OPlatformKey{}, "api_secret"); err != nil { + return fmt.Errorf("删除 api_secret 列失败: %w", err) + } + zlog.Info("api_secret 列已删除") + } else { + zlog.Info("api_secret 列不存在,跳过") + } + return nil + }, + Rollback: func(tx *gorm.DB) error { + return nil + }, + }, }) // 执行迁移 diff --git a/wafdb/migrations_log.go b/wafdb/migrations_log.go index 8068c5e..996087b 100644 --- a/wafdb/migrations_log.go +++ b/wafdb/migrations_log.go @@ -136,6 +136,32 @@ func RunLogDBMigrations(db *gorm.DB) error { return nil }, }, + // 迁移5: 创建开放平台调用日志表 + { + ID: "202603060001_add_oplatform_log_table", + Migrate: func(tx *gorm.DB) error { + zlog.Info("迁移 202603060001: 创建开放平台调用日志表") + + if tx.Migrator().HasTable(&model.OPlatformLog{}) { + zlog.Info("o_platform_logs 表已存在,执行结构同步") + if err := tx.AutoMigrate(&model.OPlatformLog{}); err != nil { + return fmt.Errorf("同步 o_platform_logs 表结构失败: %w", err) + } + return nil + } + + if err := tx.AutoMigrate(&model.OPlatformLog{}); err != nil { + return fmt.Errorf("创建 o_platform_logs 表失败: %w", err) + } + + zlog.Info("o_platform_logs 表创建成功") + return nil + }, + Rollback: func(tx *gorm.DB) error { + zlog.Info("回滚 202603060001: 删除开放平台调用日志表") + return tx.Migrator().DropTable(&model.OPlatformLog{}) + }, + }, }) // 执行迁移 diff --git a/wafmangeweb/localserver.go b/wafmangeweb/localserver.go index a9a026e..16bf172 100644 --- a/wafmangeweb/localserver.go +++ b/wafmangeweb/localserver.go @@ -1,6 +1,7 @@ package wafmangeweb import ( + "SamWaf/api" "SamWaf/common/zlog" "SamWaf/global" "SamWaf/middleware" @@ -41,7 +42,7 @@ func (web *WafWebManager) initRouter(r *gin.Engine) { router.PublicApiGroupApp.InitCenterRouter(PublicRouterGroup) //注册中心接收接口 RouterGroup := r.Group("") - RouterGroup.Use(middleware.Auth(), middleware.CenterApi(), middleware.SecApi(), middleware.GinGlobalExceptionMiddleWare(), middleware.IPWhitelist()) //TODO 中心管控 特定 + RouterGroup.Use(middleware.Auth(), middleware.OpenApiLogMiddleware(), middleware.CenterApi(), middleware.SecApi(), middleware.GinGlobalExceptionMiddleWare(), middleware.IPWhitelist()) //TODO 中心管控 特定 { router.ApiGroupApp.InitHostRouter(RouterGroup) router.ApiGroupApp.InitLogRouter(RouterGroup) @@ -93,8 +94,14 @@ func (web *WafWebManager) initRouter(r *gin.Engine) { router.ApiGroupApp.InitFirewallIPBlockRouter(RouterGroup) router.ApiGroupApp.InitLogFileWriteRouter(RouterGroup) router.ApiGroupApp.InitIPLocationRouter(RouterGroup) + router.ApiGroupApp.InitOPlatformKeyRouter(RouterGroup) + router.ApiGroupApp.InitOPlatformLogRouter(RouterGroup) + router.ApiGroupApp.InitOPlatformDocRouter(RouterGroup) } + // 保存 gin.Engine 引用供 API 文档生成使用 + api.GinEngineRef = r + if global.GWAF_RELEASE == "true" { static.Static(r, func(handlers ...gin.HandlerFunc) { r.NoRoute(handlers...) @@ -133,7 +140,7 @@ func (web *WafWebManager) cors() gin.HandlerFunc { // 将该域添加到allow-origin中 c.Header("Access-Control-Allow-Origin", origin) // c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE") - c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization,X-Token,Remote-Waf-User-Id,OPEN-X-Token,X-Login-Type,X-Mobile-Token") + c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization,X-Token,Remote-Waf-User-Id,OPEN-X-Token,X-Login-Type,X-Mobile-Token,X-API-Key") c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type") //允许客户端传递校验信息比如 cookie c.Header("Access-Control-Allow-Credentials", "true") diff --git a/waftask/task_config.go b/waftask/task_config.go index 4b284f8..fc92a50 100644 --- a/waftask/task_config.go +++ b/waftask/task_config.go @@ -171,6 +171,8 @@ func setConfigIntValue(name string, value int64, change int) { case "log_file_write_compress": global.GCONFIG_LOG_FILE_WRITE_COMPRESS = value syncLogFileWriterConfig() + case "open_platform_enabled": + global.GCONFIG_OPEN_PLATFORM_ENABLED = value default: zlog.Warn("Unknown config item:", name) } @@ -432,4 +434,7 @@ func TaskLoadSetting(initLoad bool) { updateConfigIntItem(initLoad, "logfile", "log_file_write_max_backups", global.GCONFIG_LOG_FILE_WRITE_MAX_BACKUPS, "保留的历史文件数量", "int", "", configMap) updateConfigIntItem(initLoad, "logfile", "log_file_write_max_days", global.GCONFIG_LOG_FILE_WRITE_MAX_DAYS, "保留天数", "int", "", configMap) updateConfigIntItem(initLoad, "logfile", "log_file_write_compress", global.GCONFIG_LOG_FILE_WRITE_COMPRESS, "是否压缩历史文件(0关闭 1开启)", "options", "0|关闭,1|开启", configMap) + + // 开放平台配置 + updateConfigIntItem(initLoad, "openplatform", "open_platform_enabled", global.GCONFIG_OPEN_PLATFORM_ENABLED, "开放平台开关(1启用 0禁用)", "options", "0|禁用,1|启用", configMap) }