Commit eb384897 authored by qiuqunfeng's avatar qiuqunfeng
Browse files

Refactor API router and WAF handling to support additional parameters and...

Refactor API router and WAF handling to support additional parameters and improve configuration management

This update modifies the SetRouters and SetWafRouter functions to accept new parameters, including a debug flag and a region URL map, enhancing the flexibility of the API routing. Additionally, a new SetApiRouters function is introduced for better organization of API routes. The configuration management has been streamlined by moving configuration imports to the internal package, ensuring a more consistent structure across the application.
parent 26d33762
......@@ -7,16 +7,17 @@ import (
"github.com/gin-gonic/gin"
"github.com/olivere/elastic/v7"
"github.com/rs/zerolog/log"
"gitlab.com/tensorsecurity-rd/waf-console/internal/config"
"gitlab.com/tensorsecurity-rd/waf-console/cmd/api-server/config"
"gitlab.com/tensorsecurity-rd/waf-console/internal/middleware"
"gitlab.com/tensorsecurity-rd/waf-console/internal/utils"
"gorm.io/gorm"
)
func SetRouters(db *gorm.DB, clusterClientManager *utils.ClusterClientManager, gatewayUrl string, ssoUrl string, elasticClient *elastic.Client) *gin.Engine {
func SetRouters(db *gorm.DB, clusterClientManager *utils.ClusterClientManager, gatewayUrl string, ssoUrl string,
elasticClient *elastic.Client, debug bool, regionUrlMap map[string]string) *gin.Engine {
var engine *gin.Engine
if !config.Conf.Debug {
if !debug {
// 生产模式
log.Info().Msg("release mode")
engine = ReleaseRouter()
......@@ -65,7 +66,8 @@ func SetRouters(db *gorm.DB, clusterClientManager *utils.ClusterClientManager, g
// },
// // BearerToken: "1234567890",
// })
SetWafRouter(engine, clusterClientManager, db, gatewayUrl, elasticClient)
SetWafRouter(engine, clusterClientManager, db, gatewayUrl, elasticClient, regionUrlMap)
// 统一处理 404
engine.NoRoute(func(c *gin.Context) {
......@@ -76,6 +78,48 @@ func SetRouters(db *gorm.DB, clusterClientManager *utils.ClusterClientManager, g
return engine
}
func SetApiRouters(config *config.Config, ssoUrl string, elasticClient *elastic.Client) *gin.Engine {
var engine *gin.Engine
if !config.Debug {
// 生产模式
log.Info().Msg("release mode")
engine = ReleaseRouter()
engine.Use(
middleware.AuthMiddleware(ssoUrl),
// middleware.RequestCostHandler(),
// middleware.CustomLogger(),
// middleware.CustomRecovery(),
// middleware.CorsHandler(),
// middleware.CorsHandler(),
)
} else {
// 开发调试模式
log.Info().Msg("debug mode")
engine = gin.New()
engine.Use(
middleware.AuthMiddleware(ssoUrl),
gin.Logger(),
// middleware.CustomRecovery(),
// middleware.CorsHandler(),
)
}
// set up trusted agents
err := engine.SetTrustedProxies([]string{"127.0.0.1"})
if err != nil {
panic(err)
}
// ping
engine.GET("/ping", func(c *gin.Context) {
c.AbortWithStatusJSON(http.StatusOK, gin.H{
"message": "pong!",
})
})
SetWafProxyRouter(engine, config.RegionConfigs)
return engine
}
// ReleaseRouter 生产模式使用官方建议设置为 release 模式
func ReleaseRouter() *gin.Engine {
// 切换到生产模式
......
......@@ -8,7 +8,7 @@ import (
"gorm.io/gorm"
)
func SetWafRouter(e *gin.Engine, clusterClientManager *utils.ClusterClientManager, db *gorm.DB, gatewayUrl string, elasticClient *elastic.Client) {
func SetWafRouter(e *gin.Engine, clusterClientManager *utils.ClusterClientManager, db *gorm.DB, gatewayUrl string, elasticClient *elastic.Client, regionUrlMap map[string]string) {
v1 := e.Group("v1/api/waf")
wafController := controller.NewWafController(clusterClientManager, db, gatewayUrl, elasticClient)
......@@ -26,9 +26,12 @@ func SetWafRouter(e *gin.Engine, clusterClientManager *utils.ClusterClientManage
v1.GET("listener/history", wafController.ListListenerHistory)
v2 := e.Group("api/v2/containerSec/waf")
// wafLogController := controller.NewWafLogController(regionUrlMap)
// v2.Any("attack/log/*", wafLogController.WafLogProxy)
v2.GET("attack/log/list", wafController.ListAttackLogs)
v2.GET("attack/log/details", wafController.GetAttackLogDetails)
v2.GET("attack/log/rspPkg", wafController.GetAttackLogRsp)
v2.GET("rules", wafController.ListRules)
v2.PUT("rules", wafController.UpdateRule)
v2.POST("blackwhitelist", wafController.CreateBlackWhiteList)
......@@ -40,3 +43,12 @@ func SetWafRouter(e *gin.Engine, clusterClientManager *utils.ClusterClientManage
v2.GET("attack/classes", wafController.AttackClassesList)
}
func SetApiWafRouter(e *gin.Engine, clusterClientManager *utils.ClusterClientManager, db *gorm.DB, gatewayUrl string, elasticClient *elastic.Client) {
v1 := e.Group("api/v2/waf")
wafController := controller.NewWafController(clusterClientManager, db, gatewayUrl, elasticClient)
v1.GET("attack/log/list", wafController.ListAttackLogs)
v1.GET("attack/log/details", wafController.GetAttackLogDetails)
v1.GET("attack/log/rspPkg", wafController.GetAttackLogRsp)
}
package api
import (
"github.com/gin-gonic/gin"
"gitlab.com/tensorsecurity-rd/waf-console/cmd/api-server/config"
"gitlab.com/tensorsecurity-rd/waf-console/internal/controller"
)
func SetWafProxyRouter(e *gin.Engine, regionConfigs []config.RegionConfig) {
v2 := e.Group("api/v2/containerSec/waf")
regionUrlMap := make(map[string]string)
for _, regionConfig := range regionConfigs {
regionUrlMap[regionConfig.RegionCode] = regionConfig.ApiServer
}
wafLogController := controller.NewWafLogController(regionUrlMap)
v2.GET("/waf/log", wafLogController.WafLogProxy)
}
package api_server
import (
"os"
"time"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"gitlab.com/tensorsecurity-rd/waf-console/api"
"gitlab.com/tensorsecurity-rd/waf-console/cmd/api-server/config"
// "gitlab.com/tensorsecurity-rd/waf-console/cmd/config"
"gitlab.com/tensorsecurity-rd/waf-console/internal/service"
es "gitlab.com/tensorsecurity-rd/waf-console/internal/store"
)
func NewApiServer() *gin.Engine {
router := gin.Default()
return router
}
func NewRootCommand() *cobra.Command {
return &cobra.Command{
Use: "api-server",
Short: "Start api-server service.",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
config := config.LoadConfig()
debugMode := os.Getenv("DEBUG_MODE")
log.Info().Msgf("DEBUG_MODE: %s", debugMode)
if debugMode == "true" {
config.Debug = true
// config.Conf.Debug = true
}
esClient, err := es.CreateEsClientFromConfig(config.ElasticsearchConfig)
if err != nil {
panic(err)
}
e := api.SetApiRouters(config, config.SSOUrl, esClient)
esStore := es.NewESStore(es.Config{
ESBatchSize: 100,
ESConcurrency: 10,
ESTimeout: 10 * time.Second,
}, esClient)
esStore.Init()
logConsumerService := service.NewLogConsumerService(nil, esStore, config.KafkaConfig)
go logConsumerService.Consume()
return e.Run(":8080")
},
}
}
package config
import (
"encoding/json"
"os"
"github.com/rs/zerolog/log"
"gitlab.com/tensorsecurity-rd/waf-console/internal/config"
)
type RegionConfig struct {
RegionCode string `json:"region_code"`
ApiServer string `json:"api_server"`
}
type Config struct {
RegionConfigs []RegionConfig `json:"region_configs"`
Debug bool `json:"debug"`
SSOUrl string `json:"sso_url"`
ElasticsearchConfig *config.ElasticsearchConfig `json:"elasticsearch_config"`
KafkaConfig *config.KafkaConfig `json:"kafka_config"`
}
// type ElasticsearchConfig struct {
// Url string `json:"url"`
// Username string `json:"username"`
// Password string `json:"password"`
// Sniff bool `json:"sniff"`
// }
// type KafkaConfig struct {
// Brokers []string `json:"brokers"`
// Topic string `json:"topic"`
// Group string `json:"group"`
// AuthMethod string `json:"auth_method"`
// Username string `json:"username"`
// Password string `json:"password"`
// ScramAlgo string `json:"scram_algo"`
// }
func LoadConfig() *Config {
configFile := "config/config.json"
if envFile := os.Getenv("CONFIG_FILE"); envFile != "" {
configFile = envFile
}
data, err := os.ReadFile(configFile)
if err != nil {
log.Err(err).Msgf("Failed to read config file: %s", configFile)
return nil
}
var config Config
if err := json.Unmarshal(data, &config); err != nil {
log.Err(err).Msg("Failed to parse config file")
return nil
}
return &config
}
package main
import (
"os"
"github.com/rs/zerolog/log"
api_server "gitlab.com/tensorsecurity-rd/waf-console/cmd/api-server/app"
)
func main() {
rootCmd := api_server.NewRootCommand()
if err := rootCmd.Execute(); err != nil {
log.Err(err)
os.Exit(-1)
}
}
......@@ -74,7 +74,11 @@ func NewRootCommand() *cobra.Command {
if err != nil {
panic(err)
}
e := api.SetRouters(db, clusterClientManager, config.GatewayUrl, config.SSOUrl, esClient)
regionUrlMap := make(map[string]string)
for _, regionConfig := range config.RegionConfigs {
regionUrlMap[regionConfig.RegionCode] = regionConfig.ApiServer
}
e := api.SetRouters(db, clusterClientManager, config.GatewayUrl, config.SSOUrl, esClient, config.Debug, regionUrlMap)
esStore := es.NewESStore(es.Config{
ESBatchSize: 100,
ESConcurrency: 10,
......
......@@ -6,6 +6,7 @@ import (
"strings"
"github.com/rs/zerolog/log"
"gitlab.com/tensorsecurity-rd/waf-console/internal/config"
)
const (
......@@ -22,8 +23,8 @@ type Config struct {
Debug bool `json:"debug"`
GatewayUrl string `json:"gateway_url"`
SSOUrl string `json:"sso_url"`
ElasticsearchConfig *ElasticsearchConfig `json:"elasticsearch_config"`
KafkaConfig *KafkaConfig `json:"kafka_config"`
ElasticsearchConfig *config.ElasticsearchConfig `json:"elasticsearch_config"`
KafkaConfig *config.KafkaConfig `json:"kafka_config"`
}
type DBConfig struct {
......@@ -44,22 +45,22 @@ type RegionConfig struct {
Insecure bool `json:"insecure"`
}
type ElasticsearchConfig struct {
Url string `json:"url"`
Username string `json:"username"`
Password string `json:"password"`
Sniff bool `json:"sniff"`
}
// type ElasticsearchConfig struct {
// Url string `json:"url"`
// Username string `json:"username"`
// Password string `json:"password"`
// Sniff bool `json:"sniff"`
// }
type KafkaConfig struct {
Brokers []string `json:"brokers"`
Topic string `json:"topic"`
Group string `json:"group"`
AuthMethod string `json:"auth_method"`
Username string `json:"username"`
Password string `json:"password"`
ScramAlgo string `json:"scram_algo"`
}
// type KafkaConfig struct {
// Brokers []string `json:"brokers"`
// Topic string `json:"topic"`
// Group string `json:"group"`
// AuthMethod string `json:"auth_method"`
// Username string `json:"username"`
// Password string `json:"password"`
// ScramAlgo string `json:"scram_algo"`
// }
func LoadConfig() *Config {
configFile := "config/config.json"
......
......@@ -4,12 +4,28 @@ const (
WeibuUrl = "https://api.threatbook.cn/v3/ip/query?apikey=d625206f5fdb49eeb98b0c30f46f4310a444f76d392540e8a0bb160d8a7d02c4&resource="
)
var Conf Config = Config{
Debug: true,
// var Conf Config = Config{
// Debug: true,
// }
// type Config struct {
// Debug bool
// }
type ElasticsearchConfig struct {
Url string `json:"url"`
Username string `json:"username"`
Password string `json:"password"`
Sniff bool `json:"sniff"`
}
type Config struct {
Debug bool
type KafkaConfig struct {
Brokers []string `json:"brokers"`
Topic string `json:"topic"`
Group string `json:"group"`
AuthMethod string `json:"auth_method"`
Username string `json:"username"`
Password string `json:"password"`
ScramAlgo string `json:"scram_algo"`
}
func init() {}
package controller
import (
"net/http"
"net/http/httputil"
"net/url"
"strings"
"github.com/gin-gonic/gin"
)
type WafLogController struct {
remote string
regionUrlMap map[string]string
}
func NewWafLogController(regionUrlMap map[string]string) *WafLogController {
return &WafLogController{
regionUrlMap: regionUrlMap,
}
}
func (c *WafLogController) WafLogProxy(ctx *gin.Context) {
region := ctx.Query("region")
remoteUrl, err := url.Parse(c.regionUrlMap[region])
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
proxy := httputil.NewSingleHostReverseProxy(remoteUrl)
proxy.Director = func(req *http.Request) {
req.URL.Path = strings.Replace(req.URL.Path, "/api/v2/containerSec/waf", "/api/v2/waf", 1)
req.URL.RawQuery = ctx.Request.URL.RawQuery
}
proxy.ServeHTTP(ctx.Writer, ctx.Request)
}
package model
import (
"encoding/json"
"time"
)
const (
ScopeKindCluster = "cluster"
)
type WafDetectionMessage struct {
WafDetectionMessageBasic
AttackedLog []WafDetectionAttackedLog `json:"attacked_log"`
......@@ -36,3 +45,105 @@ type WafDetection struct {
WafDetectionAttackedLog
CreatedAt int64 `json:"created_at"`
}
type Event struct {
ID string `json:"id"`
Type string `json:"type"` // 事件类型(关联类型)
Description string `json:"description"` // 事件描述
RuleKeys []RuleKey `json:"ruleKeys"` // 该event涉及到的signals,触发的rule列表,去重
Scopes map[string][]Scope `json:"scopes"` // 关联后,需要保留每个signal的scope,去重
Resources []map[string]Scope `json:"resources"` // 资源列表,关联信号的所有scope,保留了scope内部的关系,去重
Relation Relation `json:"relation"` // 事件的关联表达,可能是图、时间轴
Severity int `json:"severity"` // 严重程度 枚举;算法见下方
Tags []string `json:"tags"` // 事件标签,一期只有系统生成(规则子标签),二期用户可自定义(标签系统)
SignalsCount map[int]int `json:"signalsCount"` // 关联的信号数量,按严重程度拆分
UpdatedAt int64 `json:"updatedAt"` // 更新时间
CreatedAt int64 `json:"createdAt"` // 创建时间
Timestamp time.Time `json:"timestamp"`
Context map[string]interface{} `json:"context"` // 只有规则关联才有,取交集
Process *Process `json:"process,omitempty"`
}
type Process struct {
Status int `json:"status"`
Processor string `json:"processor"`
Remark string `json:"remark"`
Timestamp int64 `json:"timestamp"`
}
const (
ProcessStatusUnProcessed = 0
ProcessStatusProcessed = 1
ProcessStatusOmitted = 2
ProcessStatusPendingProcessed = 3
)
type Relation struct {
Type string `json:"type"` // 关系类型,可能是图、时间轴
Graph *Graph `json:"graph,omitempty"` // 图
}
type Graph struct {
Nodes map[string]*Node `json:"nodes"` // 节点,map类型用于快速确定node是否存在
Edges []*Edge `json:"edges"` // 边
}
type Node struct {
ID string `json:"id"` // 根据规则生成,不同的关联方式会不同,如进程树可能是pid+pname
Label string `json:"label"` // 节点标签,即类型,如:signal、process
Properties map[string]interface{} `json:"properties"` // 其他信息
}
type Edge struct {
Type int `json:"type"` // 边的类型,0-无向边,1-有向边。无向边时,Source Destination仅表示节点
Relation string `json:"relation"` // 关系名称,如:父进程关系、网络调用关系、关联信号关系
Source *Node `json:"source"` // 边的起点节点
Destination *Node `json:"destination"` // 边的终点节点
Properties map[string]interface{} `json:"properties"` // 其他信息
}
type AliasRuleKey RuleKey
type RuleKey struct {
Version1 uint16 `json:"version1"`
Name string `json:"name"`
Category string `json:"category"`
}
func (r RuleKey) MarshalJSON() ([]byte, error) {
path := ""
if r.Category != "" {
path = r.Category
if r.Name != "" {
path = path + "/" + r.Name
}
}
x := struct {
AliasRuleKey
Path string `json:"path"`
}{AliasRuleKey(r), path}
return json.Marshal(x)
}
func (r *RuleKey) GenKey() string {
return r.Category + "$" + r.Name
}
type Scope struct {
Kind string `json:"kind"` // required
ID string `json:"id"` // optional,视具体情况
Name string `json:"name"` // required
}
func (s *Scope) GenKey() string {
return s.Kind + "$" + s.ID + "$" + s.Name
}
// GenSuggestionKey
// 集群以id+name去重,其他以name去重
func (s *Scope) GenSuggestionKey() string {
if s.Kind == ScopeKindCluster {
return s.Kind + "$" + s.ID + "$" + s.Name
}
return s.Kind + "$" + s.Name
}
......@@ -9,7 +9,7 @@ import (
"github.com/segmentio/kafka-go/sasl"
"github.com/segmentio/kafka-go/sasl/plain"
"github.com/segmentio/kafka-go/sasl/scram"
"gitlab.com/tensorsecurity-rd/waf-console/cmd/config"
"gitlab.com/tensorsecurity-rd/waf-console/internal/config"
)
func getSASLMechanism(config *config.KafkaConfig) (sasl.Mechanism, bool, error) {
......
......@@ -4,11 +4,13 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/rs/zerolog/log"
"github.com/segmentio/kafka-go"
"github.com/segmentio/kafka-go/sasl/scram"
"gitlab.com/tensorsecurity-rd/waf-console/cmd/config"
"gitlab.com/tensorsecurity-rd/waf-console/internal/config"
"gitlab.com/tensorsecurity-rd/waf-console/internal/model"
"gitlab.com/tensorsecurity-rd/waf-console/internal/utils/id"
......@@ -130,6 +132,83 @@ func (s *LogConsumerService) processMessage(m kafka.Message) {
s.Handle(context.Background(), m.Value)
}
func (s *LogConsumerService) genWafDetection(wafDetectionMessage model.WafDetectionMessage, attackedLog model.WafDetectionAttackedLog) (model.WafDetection, error) {
if attackedLog.AttackIP == "" {
return model.WafDetection{}, errors.New("attack_ip is empty")
}
if attackedLog.Action != "pass" {
return model.WafDetection{}, errors.New("action is not pass")
}
wafDetection := model.WafDetection{
WafDetectionMessageBasic: wafDetectionMessage.WafDetectionMessageBasic,
WafDetectionAttackedLog: attackedLog,
CreatedAt: wafDetectionMessage.CreatedAt,
}
wafDetection.WafDetectionAttackedLog.ID = id.Str()
return wafDetection, nil
}
func (s *LogConsumerService) genWafDetectionEvent(wafDetectionMessage model.WafDetectionMessage) (model.Event, error) {
event := model.Event{
ID: id.Str(),
Type: "waf_detection",
Description: "waf detection",
RuleKeys: []model.RuleKey{},
Scopes: map[string][]model.Scope{
"cluster": {
{
Kind: "cluster",
ID: wafDetectionMessage.WafDetectionMessageBasic.ClusterKey,
Name: wafDetectionMessage.WafDetectionMessageBasic.ClusterKey,
},
},
"namespace": {
{
Kind: "namespace",
ID: wafDetectionMessage.WafDetectionMessageBasic.Namespace,
Name: wafDetectionMessage.WafDetectionMessageBasic.Namespace,
},
},
"resource": {
{
Kind: "resource",
ID: "",
Name: fmt.Sprintf("%s(%s)", wafDetectionMessage.WafDetectionMessageBasic.ResName, wafDetectionMessage.WafDetectionMessageBasic.ResKind),
},
},
},
Resources: []map[string]model.Scope{
{
"cluster": {
Kind: "cluster",
ID: wafDetectionMessage.WafDetectionMessageBasic.ClusterKey,
Name: wafDetectionMessage.WafDetectionMessageBasic.ClusterKey,
},
"namespace": {
Kind: "namespace",
ID: wafDetectionMessage.WafDetectionMessageBasic.Namespace,
Name: wafDetectionMessage.WafDetectionMessageBasic.Namespace,
},
"resource": {
Kind: "resource",
ID: "",
Name: fmt.Sprintf("%s(%s)", wafDetectionMessage.WafDetectionMessageBasic.ResName, wafDetectionMessage.WafDetectionMessageBasic.ResKind),
},
},
},
Relation: model.Relation{
Type: "timeline",
},
CreatedAt: wafDetectionMessage.CreatedAt,
UpdatedAt: wafDetectionMessage.CreatedAt,
Timestamp: time.Now(),
Context: map[string]interface{}{
"waf_detection_message": wafDetectionMessage,
},
}
return event, nil
}
func (s *LogConsumerService) Handle(ctx context.Context, message []byte) error {
WafDetectionMessage := model.WafDetectionMessage{}
err := json.Unmarshal(message, &WafDetectionMessage)
......@@ -161,11 +240,11 @@ func (s *LogConsumerService) Handle(ctx context.Context, message []byte) error {
s.esStore.Save(ctx, bulkableRequests)
err = s.db.WithContext(ctx).Model(&model.WafService{}).Where("id = ?", WafDetectionMessage.ServiceID).Update("attack_num", gorm.Expr("attack_num + ?", 1)).Error
if err != nil {
log.Err(err).Int64("WafDetectionMessage.ServiceID", WafDetectionMessage.ServiceID).Msg("update waf_service attack_number fails")
return err
}
// err = s.db.WithContext(ctx).Model(&model.WafService{}).Where("id = ?", WafDetectionMessage.ServiceID).Update("attack_num", gorm.Expr("attack_num + ?", 1)).Error
// if err != nil {
// log.Err(err).Int64("WafDetectionMessage.ServiceID", WafDetectionMessage.ServiceID).Msg("update waf_service attack_number fails")
// return err
// }
return nil
}
......@@ -11,7 +11,7 @@ import (
json "github.com/json-iterator/go"
"github.com/rs/zerolog/log"
"gitlab.com/tensorsecurity-rd/waf-console/cmd/config"
"gitlab.com/tensorsecurity-rd/waf-console/internal/config"
es "github.com/olivere/elastic/v7"
)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment