From 2e60e8021c7b38f0e028fa5ffe8652e482b59939 Mon Sep 17 00:00:00 2001 From: bui Date: Wed, 13 Sep 2023 17:12:09 +0200 Subject: [PATCH] up wip --- pkg/acquisition/modules/waap/waap.go | 19 +-- pkg/acquisition/modules/waap/waap_runner.go | 5 +- pkg/waf/env.go | 23 ++-- pkg/waf/request.go | 43 +++---- pkg/waf/waap.go | 122 ++++++++++++++++---- pkg/waf/waf_helpers.go | 9 -- 6 files changed, 144 insertions(+), 77 deletions(-) diff --git a/pkg/acquisition/modules/waap/waap.go b/pkg/acquisition/modules/waap/waap.go index 68200b32a..43d6fcc8a 100644 --- a/pkg/acquisition/modules/waap/waap.go +++ b/pkg/acquisition/modules/waap/waap.go @@ -52,6 +52,11 @@ type WaapSource struct { WaapRunners []WaapRunner //one for each go-routine } +// @tko + @sbl : we might want to get rid of that or improve it +type BodyResponse struct { + Action string `json:"action"` +} + func (wc *WaapSource) UnmarshalConfig(yamlConfig []byte) error { err := yaml.UnmarshalStrict(yamlConfig, &wc.config) @@ -165,10 +170,13 @@ func (w *WaapSource) Configure(yamlConfig []byte, logger *log.Entry) error { }) } + //we copy WaapRutime for each runner + wrt := *w.WaapRuntime runner := WaapRunner{ - inChan: w.InChan, - UUID: wafUUID, - logger: wafLogger, + inChan: w.InChan, + UUID: wafUUID, + logger: wafLogger, + WaapRuntime: &wrt, } w.WaapRunners[nbRoutine] = runner //most likely missign somethign here to actually start the runner :) @@ -247,10 +255,6 @@ func (w *WaapSource) Dump() interface{} { return w } -type BodyResponse struct { - Action string `json:"action"` -} - // should this be in the runner ? func (w *WaapSource) waapHandler(rw http.ResponseWriter, r *http.Request) { // parse the request only once @@ -264,6 +268,7 @@ func (w *WaapSource) waapHandler(rw http.ResponseWriter, r *http.Request) { message := <-parsedRequest.ResponseChannel + //@tko this parts needs to be redone if message.Err != nil { log.Errorf("Error while processing InBAND: %s", err) rw.WriteHeader(http.StatusInternalServerError) diff --git a/pkg/acquisition/modules/waap/waap_runner.go b/pkg/acquisition/modules/waap/waap_runner.go index 979a958b9..06c5a78a7 100644 --- a/pkg/acquisition/modules/waap/waap_runner.go +++ b/pkg/acquisition/modules/waap/waap_runner.go @@ -31,10 +31,10 @@ func (r *WaapRunner) Run(t *tomb.Tomb) error { return nil case request := <-r.inChan: r.logger.Infof("Requests handled by runner %s", request.UUID) + r.WaapRuntime.ClearResponse() - //tx := waf.NewExtendedTransaction(r.WaapInbandEngine, r.UUID) WafReqCounter.With(prometheus.Labels{"source": request.RemoteAddr}).Inc() - //measure the time spent in the WAF + //to measure the time spent in the WAF startParsing := time.Now() //pre eval (expr) rules @@ -43,7 +43,6 @@ func (r *WaapRunner) Run(t *tomb.Tomb) error { r.logger.Errorf("unable to process PreEval rules: %s", err) continue } - //inband WAAP rules interrupt, err := r.WaapRuntime.ProcessInBandRules(request) elapsed := time.Since(startParsing) diff --git a/pkg/waf/env.go b/pkg/waf/env.go index 196a5532a..7e5f78c35 100644 --- a/pkg/waf/env.go +++ b/pkg/waf/env.go @@ -25,18 +25,17 @@ func (t *ExtendedTransaction) RemoveRuleByIDWithError(id int) error { return nil } -func GetEnv() map[string]interface{} { - ResponseRequest := ResponseRequest{} - ParsedRequest := ParsedRequest{} - Rules := &WaapCollection{} - Tx := ExtendedTransaction{} - +// simply used to ease the compilation & runtime of the hooks +func GetHookEnv(w WaapRuntimeConfig, request ParsedRequest) map[string]interface{} { return map[string]interface{}{ - "rules": Rules, - "req": ParsedRequest, - "SetRemediation": ResponseRequest.SetRemediation, - "SetRemediationByID": ResponseRequest.SetRemediationByID, - "CancelEvent": ResponseRequest.CancelEvent, - "RemoveRuleByID": Tx.RemoveRuleByIDWithError, + "inband_rules": w.InBandRules, + "outband_rules": w.OutOfBandRules, + "req": request, + "RemoveInbandRuleByID": w.RemoveInbandRuleByID, + "RemoveOutbandRuleByID": w.RemoveOutbandRuleByID, + "SetAction": w.SetAction, + "SetHTTPCode": w.SetHTTPCode, + "SetActionByID": w.SetActionnByID, + "CancelEvent": w.CancelEvent, } } diff --git a/pkg/waf/request.go b/pkg/waf/request.go index bed7a6ef9..1ba53cc79 100644 --- a/pkg/waf/request.go +++ b/pkg/waf/request.go @@ -36,29 +36,29 @@ func NewResponseRequest(Tx experimental.FullTransaction, in *corazatypes.Interru } } -func (r *ResponseRequest) SetRemediation(remediation string) error { - if r.Interruption == nil { - return nil - } - r.Interruption.Action = remediation - return nil -} +// func (r *ResponseRequest) SetRemediation(remediation string) error { +// if r.Interruption == nil { +// return nil +// } +// r.Interruption.Action = remediation +// return nil +// } -func (r *ResponseRequest) SetRemediationByID(ID int, remediation string) error { - if r.Interruption == nil { - return nil - } - if r.Interruption.RuleID == ID { - r.Interruption.Action = remediation - } - return nil -} +// func (r *ResponseRequest) SetRemediationByID(ID int, remediation string) error { +// if r.Interruption == nil { +// return nil +// } +// if r.Interruption.RuleID == ID { +// r.Interruption.Action = remediation +// } +// return nil +// } -func (r *ResponseRequest) CancelEvent() error { - // true by default - r.SendEvents = false - return nil -} +// func (r *ResponseRequest) CancelEvent() error { +// // true by default +// r.SendEvents = false +// return nil +// } type ParsedRequest struct { RemoteAddr string @@ -77,6 +77,7 @@ type ParsedRequest struct { ResponseChannel chan ResponseRequest } +// Generate a ParsedRequest from a http.Request. ParsedRequest can be consumed by the Waap Engine func NewParsedRequestFromRequest(r *http.Request) (ParsedRequest, error) { var err error body := make([]byte, 0) diff --git a/pkg/waf/waap.go b/pkg/waf/waap.go index 325da850f..526cbac88 100644 --- a/pkg/waf/waap.go +++ b/pkg/waf/waap.go @@ -21,6 +21,7 @@ type Hook struct { ApplyExpr []*vm.Program `yaml:"-"` } +// @tko : todo - debug mode func (h *Hook) Build() error { if h.Filter != "" { @@ -31,7 +32,7 @@ func (h *Hook) Build() error { h.FilterExpr = program } for _, apply := range h.Apply { - program, err := expr.Compile(apply, GetExprWAFOptions(GetEnv())...) + program, err := expr.Compile(apply, GetExprWAFOptions(GetHookEnv(WaapRuntimeConfig{}, ParsedRequest{}))...) if err != nil { return fmt.Errorf("unable to compile apply %s : %w", apply, err) } @@ -40,18 +41,30 @@ func (h *Hook) Build() error { return nil } +type WaapTempResponse struct { + InBandInterrupt bool + OutOfBandInterrupt bool + Action string //allow, deny, captcha, log + HTTPResponseCode int + SendEvent bool //do we send an internal event on rule match +} + // runtime version of WaapConfig type WaapRuntimeConfig struct { Name string OutOfBandRules []WaapCollection - OutOfBandTx ExtendedTransaction //is it a good idea ? InBandRules []WaapCollection - InBandTx ExtendedTransaction //is it a good idea ? DefaultRemediation string CompiledOnLoad []Hook CompiledPreEval []Hook CompiledOnMatch []Hook CompiledVariablesTracking []*regexp.Regexp + Config *WaapConfig + + //those are ephemeral, created/destroyed with every req + OutOfBandTx ExtendedTransaction //is it a good idea ? + InBandTx ExtendedTransaction //is it a good idea ? + Response WaapTempResponse } type WaapConfig struct { @@ -59,12 +72,24 @@ type WaapConfig struct { OutOfBandRules []string `yaml:"outofband_rules"` InBandRules []string `yaml:"inband_rules"` DefaultRemediation string `yaml:"default_remediation"` + DefaultPassAction string `yaml:"default_pass_action"` + BlockedHTTPCode int `yaml:"blocked_http_code"` + PassedHTTPCode int `yaml:"passed_http_code"` OnLoad []Hook `yaml:"on_load"` PreEval []Hook `yaml:"pre_eval"` OnMatch []Hook `yaml:"on_match"` VariablesTracking []string `yaml:"variables_tracking"` } +func (w *WaapRuntimeConfig) ClearResponse() { + log.Infof("#-> %p", w) + w.Response = WaapTempResponse{} + log.Infof("-> %p", w.Config) + w.Response.Action = w.Config.DefaultPassAction + w.Response.HTTPResponseCode = w.Config.PassedHTTPCode + w.Response.SendEvent = true +} + func (wc *WaapConfig) Load(file string) error { yamlFile, err := os.ReadFile(file) if err != nil { @@ -74,6 +99,21 @@ func (wc *WaapConfig) Load(file string) error { if err != nil { return fmt.Errorf("unable to parse yaml file %s : %s", file, err) } + if wc.Name == "" { + return fmt.Errorf("name cannot be empty") + } + if wc.DefaultRemediation == "" { + return fmt.Errorf("default_remediation cannot be empty") + } + if wc.BlockedHTTPCode == 0 { + wc.BlockedHTTPCode = 403 + } + if wc.PassedHTTPCode == 0 { + wc.PassedHTTPCode = 200 + } + if wc.DefaultPassAction == "" { + wc.DefaultPassAction = "allow" + } return nil } @@ -81,6 +121,7 @@ func (wc *WaapConfig) Load(file string) error { func (wc *WaapConfig) Build() (*WaapRuntimeConfig, error) { ret := &WaapRuntimeConfig{} ret.Name = wc.Name + ret.Config = wc ret.DefaultRemediation = wc.DefaultRemediation //load rules @@ -160,13 +201,12 @@ func (w *WaapRuntimeConfig) ProcessOnMatchRules(request ParsedRequest, response } for _, applyExpr := range rule.ApplyExpr { _, err := expr.Run(applyExpr, map[string]interface{}{ - //"rules": w.InBandTx.Tx.Rules, //what is it supposed to be ? matched rules ? - "req": request, - "RemoveInbandRuleByID": w.RemoveInbandRuleByID, - "RemoveOutbandRuleByID": w.RemoveOutbandRuleByID, - "SetRemediation": response.SetRemediation, - "SetRemediationByID": response.SetRemediationByID, - "CancelEvent": response.CancelEvent, + // "req": request, + // "RemoveInbandRuleByID": w.RemoveInbandRuleByID, + // "RemoveOutbandRuleByID": w.RemoveOutbandRuleByID, + // "SetAction": response.SetAction, + // "SetRemediationByID": response.SetRemediationByID, + // "CancelEvent": response.CancelEvent, }) if err != nil { log.Errorf("unable to apply filter: %s", err) @@ -180,10 +220,7 @@ func (w *WaapRuntimeConfig) ProcessOnMatchRules(request ParsedRequest, response func (w *WaapRuntimeConfig) ProcessPreEvalRules(request ParsedRequest) error { for _, rule := range w.CompiledPreEval { if rule.FilterExpr != nil { - output, err := expr.Run(rule.FilterExpr, map[string]interface{}{ - //"rules": rules, //is it still useful ? - "req": request, - }) + output, err := expr.Run(rule.FilterExpr, GetHookEnv(*w, request)) if err != nil { return fmt.Errorf("unable to run filter %s : %w", rule.Filter, err) } @@ -200,13 +237,7 @@ func (w *WaapRuntimeConfig) ProcessPreEvalRules(request ParsedRequest) error { } // here means there is no filter or the filter matched for _, applyExpr := range rule.ApplyExpr { - _, err := expr.Run(applyExpr, map[string]interface{}{ - "inband_rules": w.InBandRules, - "outband_rules": w.OutOfBandRules, - "req": request, - "RemoveInbandRuleByID": w.RemoveInbandRuleByID, - "RemoveOutbandRuleByID": w.RemoveOutbandRuleByID, - }) + _, err := expr.Run(applyExpr, GetHookEnv(*w, request)) if err != nil { log.Errorf("unable to apply filter: %s", err) continue @@ -221,15 +252,37 @@ func (w *WaapRuntimeConfig) ProcessPreEvalRules(request ParsedRequest) error { add the helpers to: - remove by id-range - remove by tag + - set remediation by tag/id-range */ -func (w *WaapRuntimeConfig) RemoveInbandRuleByID(id int) { - w.InBandTx.RemoveRuleByIDWithError(id) +func (w *WaapRuntimeConfig) RemoveInbandRuleByID(id int) error { + return w.InBandTx.RemoveRuleByIDWithError(id) } -func (w *WaapRuntimeConfig) RemoveOutbandRuleByID(id int) { - w.OutOfBandTx.RemoveRuleByIDWithError(id) +func (w *WaapRuntimeConfig) CancelEvent() error { + w.Response.SendEvent = false + return nil +} + +func (w *WaapRuntimeConfig) SetActionnByID(id int, action string) error { + panic("not implemented") + return nil +} + +func (w *WaapRuntimeConfig) RemoveOutbandRuleByID(id int) error { + return w.OutOfBandTx.RemoveRuleByIDWithError(id) +} + +func (w *WaapRuntimeConfig) SetAction(action string) error { + w.Response.Action = action + return nil + +} + +func (w *WaapRuntimeConfig) SetHTTPCode(code int) error { + w.Response.HTTPResponseCode = code + return nil } func (w *WaapRuntimeConfig) ProcessInBandRules(request ParsedRequest) (*corazatypes.Interruption, error) { @@ -257,3 +310,22 @@ func (w *WaapRuntimeConfig) ProcessOutOfBandRules(request ParsedRequest) (*coraz } return nil, nil } + +type BodyResponse struct { + Action string `json:"action"` + HTTPStatus int `json:"http_status"` +} + +func (w *WaapRuntimeConfig) GenerateResponse(interrupted bool) (BodyResponse, error) { + resp := BodyResponse{} + //if there is no interrupt, we should allow with default code + if !interrupted { + resp.Action = w.Config.DefaultPassAction + resp.HTTPStatus = w.Config.PassedHTTPCode + return resp, nil + } + resp.Action = w.Config.DefaultRemediation + resp.HTTPStatus = w.Config.BlockedHTTPCode + + return resp, nil +} diff --git a/pkg/waf/waf_helpers.go b/pkg/waf/waf_helpers.go index b99732ca3..97b1f30f0 100644 --- a/pkg/waf/waf_helpers.go +++ b/pkg/waf/waf_helpers.go @@ -30,12 +30,3 @@ func GetExprWAFOptions(ctx map[string]interface{}) []expr.Option { } return baseHelpers } - -func SetRulesToInband(params ...any) (any, error) { - - return nil, nil -} - -func SetRulesToOutOfBand(params ...any) (any, error) { - return nil, nil -}