diff --git a/pkg/acquisition/modules/appsec/appsec_runner.go b/pkg/acquisition/modules/appsec/appsec_runner.go index 59f218310a5..b9e23173b9c 100644 --- a/pkg/acquisition/modules/appsec/appsec_runner.go +++ b/pkg/acquisition/modules/appsec/appsec_runner.go @@ -132,58 +132,58 @@ func (r *AppsecRunner) Init(datadir string) error { return nil } -func (r *AppsecRunner) processRequest(tx appsec.ExtendedTransaction, request *appsec.ParsedRequest) error { +func (r *AppsecRunner) processRequest(state *appsec.AppsecRequestState, request *appsec.ParsedRequest) error { var in *corazatypes.Interruption var err error - if request.Tx.IsRuleEngineOff() { + if state.Tx.IsRuleEngineOff() { r.logger.Debugf("rule engine is off, skipping") return nil } defer func() { - request.Tx.ProcessLogging() + state.Tx.ProcessLogging() //We don't close the transaction here, as it will reset coraza internal state and break variable tracking - err := r.AppsecRuntime.ProcessPostEvalRules(request) + err := r.AppsecRuntime.ProcessPostEvalRules(state, request) if err != nil { r.logger.Errorf("unable to process PostEval rules: %s", err) } }() //pre eval (expr) rules - err = r.AppsecRuntime.ProcessPreEvalRules(request) + err = r.AppsecRuntime.ProcessPreEvalRules(state, request) if err != nil { r.logger.Errorf("unable to process PreEval rules: %s", err) //FIXME: should we abort here ? } - request.Tx.ProcessConnection(request.ClientIP, 0, "", 0) + state.Tx.ProcessConnection(request.ClientIP, 0, "", 0) for k, v := range request.Args { for _, vv := range v { - request.Tx.AddGetRequestArgument(k, vv) + state.Tx.AddGetRequestArgument(k, vv) } } - request.Tx.ProcessURI(request.URI, request.Method, request.Proto) + state.Tx.ProcessURI(request.URI, request.Method, request.Proto) for k, vr := range request.Headers { for _, v := range vr { - request.Tx.AddRequestHeader(k, v) + state.Tx.AddRequestHeader(k, v) } } if request.ClientHost != "" { - request.Tx.AddRequestHeader("Host", request.ClientHost) - request.Tx.SetServerName(request.ClientHost) + state.Tx.AddRequestHeader("Host", request.ClientHost) + state.Tx.SetServerName(request.ClientHost) } if request.TransferEncoding != nil { - request.Tx.AddRequestHeader("Transfer-Encoding", request.TransferEncoding[0]) + state.Tx.AddRequestHeader("Transfer-Encoding", request.TransferEncoding[0]) } - in = request.Tx.ProcessRequestHeaders() + in = state.Tx.ProcessRequestHeaders() if in != nil { r.logger.Infof("inband rules matched for headers : %s", in.Action) @@ -191,7 +191,7 @@ func (r *AppsecRunner) processRequest(tx appsec.ExtendedTransaction, request *ap } if len(request.Body) > 0 { - in, _, err = request.Tx.WriteRequestBody(request.Body) + in, _, err = state.Tx.WriteRequestBody(request.Body) if err != nil { r.logger.Errorf("unable to write request body : %s", err) return err @@ -201,7 +201,7 @@ func (r *AppsecRunner) processRequest(tx appsec.ExtendedTransaction, request *ap } } - in, err = request.Tx.ProcessRequestBody() + in, err = state.Tx.ProcessRequestBody() if err != nil { r.logger.Errorf("unable to process request body : %s", err) return err @@ -214,29 +214,27 @@ func (r *AppsecRunner) processRequest(tx appsec.ExtendedTransaction, request *ap return nil } -func (r *AppsecRunner) ProcessInBandRules(request *appsec.ParsedRequest) error { +func (r *AppsecRunner) ProcessInBandRules(state *appsec.AppsecRequestState, request *appsec.ParsedRequest) error { tx := appsec.NewExtendedTransaction(r.AppsecInbandEngine, request.UUID) - r.AppsecRuntime.InBandTx = tx - request.Tx = tx + state.Tx = tx if len(r.AppsecRuntime.InBandRules) == 0 { return nil } - err := r.processRequest(tx, request) + err := r.processRequest(state, request) return err } -func (r *AppsecRunner) ProcessOutOfBandRules(request *appsec.ParsedRequest) error { +func (r *AppsecRunner) ProcessOutOfBandRules(state *appsec.AppsecRequestState, request *appsec.ParsedRequest) error { tx := appsec.NewExtendedTransaction(r.AppsecOutbandEngine, request.UUID) - r.AppsecRuntime.OutOfBandTx = tx - request.Tx = tx + state.Tx = tx if len(r.AppsecRuntime.OutOfBandRules) == 0 { return nil } - err := r.processRequest(tx, request) + err := r.processRequest(state, request) return err } -func (r *AppsecRunner) handleInBandInterrupt(request *appsec.ParsedRequest) { +func (r *AppsecRunner) handleInBandInterrupt(state *appsec.AppsecRequestState, request *appsec.ParsedRequest) { if allowed, reason := r.appsecAllowlistsClient.IsAllowlisted(request.ClientIP); allowed { r.logger.Infof("%s is allowlisted by %s, skipping", request.ClientIP, reason) @@ -244,40 +242,39 @@ func (r *AppsecRunner) handleInBandInterrupt(request *appsec.ParsedRequest) { } //create the associated event for crowdsec itself - evt, err := EventFromRequest(request, r.Labels) + evt, err := EventFromRequest(request, r.Labels, state.Tx.ID()) if err != nil { //let's not interrupt the pipeline for this r.logger.Errorf("unable to create event from request : %s", err) } - err = r.AccumulateTxToEvent(&evt, request) - if err != nil { - r.logger.Errorf("unable to accumulate tx to event : %s", err) - } - if in := request.Tx.Interruption(); in != nil { + + r.AccumulateTxToEvent(&evt, state, request) + + if in := state.Tx.Interruption(); in != nil { r.logger.Debugf("inband rules matched : %d", in.RuleID) - r.AppsecRuntime.Response.InBandInterrupt = true - r.AppsecRuntime.Response.BouncerHTTPResponseCode = r.AppsecRuntime.Config.BouncerBlockedHTTPCode - r.AppsecRuntime.Response.UserHTTPResponseCode = r.AppsecRuntime.Config.UserBlockedHTTPCode - r.AppsecRuntime.Response.Action = r.AppsecRuntime.DefaultRemediation + state.Response.InBandInterrupt = true + state.Response.BouncerHTTPResponseCode = r.AppsecRuntime.Config.BouncerBlockedHTTPCode + state.Response.UserHTTPResponseCode = r.AppsecRuntime.Config.UserBlockedHTTPCode + state.Response.Action = r.AppsecRuntime.DefaultRemediation if _, ok := r.AppsecRuntime.RemediationById[in.RuleID]; ok { - r.AppsecRuntime.Response.Action = r.AppsecRuntime.RemediationById[in.RuleID] + state.Response.Action = r.AppsecRuntime.RemediationById[in.RuleID] } for tag, remediation := range r.AppsecRuntime.RemediationByTag { if slices.Contains(in.Tags, tag) { - r.AppsecRuntime.Response.Action = remediation + state.Response.Action = remediation } } - err = r.AppsecRuntime.ProcessOnMatchRules(request, evt) + err = r.AppsecRuntime.ProcessOnMatchRules(state, request, evt) if err != nil { r.logger.Errorf("unable to process OnMatch rules: %s", err) return } // Should the in band match trigger an overflow ? - if r.AppsecRuntime.Response.SendAlert { + if state.Response.SendAlert { appsecOvlfw, err := AppsecEventGeneration(evt, request.HTTPRequest) if err != nil { r.logger.Errorf("unable to generate appsec event : %s", err) @@ -288,34 +285,33 @@ func (r *AppsecRunner) handleInBandInterrupt(request *appsec.ParsedRequest) { } } // Should the in band match trigger an event ? - if r.AppsecRuntime.Response.SendEvent { + if state.Response.SendEvent { r.outChan <- evt } } } -func (r *AppsecRunner) handleOutBandInterrupt(request *appsec.ParsedRequest) { +func (r *AppsecRunner) handleOutBandInterrupt(state *appsec.AppsecRequestState, request *appsec.ParsedRequest) { if allowed, reason := r.appsecAllowlistsClient.IsAllowlisted(request.ClientIP); allowed { r.logger.Infof("%s is allowlisted by %s, skipping", request.ClientIP, reason) return } - evt, err := EventFromRequest(request, r.Labels) + evt, err := EventFromRequest(request, r.Labels, state.Tx.ID()) if err != nil { //let's not interrupt the pipeline for this r.logger.Errorf("unable to create event from request : %s", err) } - err = r.AccumulateTxToEvent(&evt, request) - if err != nil { - r.logger.Errorf("unable to accumulate tx to event : %s", err) - } - if in := request.Tx.Interruption(); in != nil { + + r.AccumulateTxToEvent(&evt, state, request) + + if in := state.Tx.Interruption(); in != nil { r.logger.Debugf("outband rules matched : %d", in.RuleID) - r.AppsecRuntime.Response.OutOfBandInterrupt = true + state.Response.OutOfBandInterrupt = true - err = r.AppsecRuntime.ProcessOnMatchRules(request, evt) + err = r.AppsecRuntime.ProcessOnMatchRules(state, request, evt) if err != nil { r.logger.Errorf("unable to process OnMatch rules: %s", err) return @@ -325,7 +321,7 @@ func (r *AppsecRunner) handleOutBandInterrupt(request *appsec.ParsedRequest) { // The event and the alert share the same internal map (parsed, meta, ...) // The event can be modified by the parsers, which might cause a concurrent map read/write // Should the match trigger an overflow ? - if r.AppsecRuntime.Response.SendAlert { + if state.Response.SendAlert { appsecOvlfw, err := AppsecEventGeneration(evt, request.HTTPRequest) if err != nil { r.logger.Errorf("unable to generate appsec event : %s", err) @@ -337,17 +333,19 @@ func (r *AppsecRunner) handleOutBandInterrupt(request *appsec.ParsedRequest) { } // Should the match trigger an event ? - if r.AppsecRuntime.Response.SendEvent { + if state.Response.SendEvent { r.outChan <- evt } } } func (r *AppsecRunner) handleRequest(request *appsec.ParsedRequest) { - r.AppsecRuntime.Logger = r.AppsecRuntime.Logger.WithField("request_uuid", request.UUID) + state := r.AppsecRuntime.NewRequestState() + stateLogger := r.AppsecRuntime.Logger.WithField("request_uuid", request.UUID) + r.AppsecRuntime.Logger = stateLogger logger := r.logger.WithField("request_uuid", request.UUID) logger.Debug("Request received in runner") - r.AppsecRuntime.ClearResponse() + r.AppsecRuntime.ClearResponse(&state) request.IsInBand = true request.IsOutBand = false @@ -356,11 +354,13 @@ func (r *AppsecRunner) handleRequest(request *appsec.ParsedRequest) { startInBandParsing := time.Now() startGlobalParsing := time.Now() + state.CurrentPhase = appsec.PhaseInBand + //inband appsec rules - err := r.ProcessInBandRules(request) + err := r.ProcessInBandRules(&state, request) if err != nil { logger.Errorf("unable to process InBand rules: %s", err) - err = request.Tx.Close() + err = state.Tx.Close() if err != nil { logger.Errorf("unable to close inband transaction: %s", err) } @@ -371,24 +371,25 @@ func (r *AppsecRunner) handleRequest(request *appsec.ParsedRequest) { inBandParsingElapsed := time.Since(startInBandParsing) metrics.AppsecInbandParsingHistogram.With(prometheus.Labels{"source": request.RemoteAddrNormalized, "appsec_engine": request.AppsecEngine}).Observe(inBandParsingElapsed.Seconds()) - if request.Tx.IsInterrupted() { - r.handleInBandInterrupt(request) + if state.Tx.IsInterrupted() { + r.handleInBandInterrupt(&state, request) } - err = request.Tx.Close() + err = state.Tx.Close() if err != nil { r.logger.Errorf("unable to close inband transaction: %s", err) } // send back the result to the HTTP handler for the InBand part - request.ResponseChannel <- r.AppsecRuntime.Response + request.ResponseChannel <- state.Response //Now let's process the out of band rules request.IsInBand = false request.IsOutBand = true - r.AppsecRuntime.Response.SendAlert = false - r.AppsecRuntime.Response.SendEvent = true + state.Response.SendAlert = false + state.Response.SendEvent = true + state.CurrentPhase = appsec.PhaseOutOfBand //FIXME: This is a bit of a hack to avoid confusion with the transaction if we do not have any inband rules. //We should probably have different transaction (or even different request object) for inband and out of band rules @@ -396,10 +397,10 @@ func (r *AppsecRunner) handleRequest(request *appsec.ParsedRequest) { //to measure the time spent in the Application Security Engine for OutOfBand rules startOutOfBandParsing := time.Now() - err = r.ProcessOutOfBandRules(request) + err = r.ProcessOutOfBandRules(&state, request) if err != nil { logger.Errorf("unable to process OutOfBand rules: %s", err) - err = request.Tx.Close() + err = state.Tx.Close() if err != nil { logger.Errorf("unable to close outband transaction: %s", err) } @@ -409,11 +410,11 @@ func (r *AppsecRunner) handleRequest(request *appsec.ParsedRequest) { // time spent to process out of band rules outOfBandParsingElapsed := time.Since(startOutOfBandParsing) metrics.AppsecOutbandParsingHistogram.With(prometheus.Labels{"source": request.RemoteAddrNormalized, "appsec_engine": request.AppsecEngine}).Observe(outOfBandParsingElapsed.Seconds()) - if request.Tx.IsInterrupted() { - r.handleOutBandInterrupt(request) + if state.Tx.IsInterrupted() { + r.handleOutBandInterrupt(&state, request) } } - err = request.Tx.Close() + err = state.Tx.Close() if err != nil { r.logger.Errorf("unable to close outband transaction: %s", err) } diff --git a/pkg/acquisition/modules/appsec/utils.go b/pkg/acquisition/modules/appsec/utils.go index 7a37c2016ed..fdac89b070a 100644 --- a/pkg/acquisition/modules/appsec/utils.go +++ b/pkg/acquisition/modules/appsec/utils.go @@ -275,7 +275,7 @@ func containsAll(excludedZones []string, matchedZones []string) bool { return true } -func EventFromRequest(r *appsec.ParsedRequest, labels map[string]string) (pipeline.Event, error) { +func EventFromRequest(r *appsec.ParsedRequest, labels map[string]string, txUuid string) (pipeline.Event, error) { evt := pipeline.MakeEvent(false, pipeline.LOG, true) // def needs fixing evt.Stage = "s00-raw" @@ -284,7 +284,7 @@ func EventFromRequest(r *appsec.ParsedRequest, labels map[string]string) (pipeli "target_host": r.Host, "target_uri": r.URI, "method": r.Method, - "req_uuid": r.Tx.ID(), + "req_uuid": txUuid, "source": "crowdsec-appsec", "remediation_cmpt_ip": r.RemoteAddrNormalized, // TBD: @@ -333,15 +333,14 @@ func LogAppsecEvent(evt *pipeline.Event, logger *log.Entry) { } } -func (r *AppsecRunner) AccumulateTxToEvent(evt *pipeline.Event, req *appsec.ParsedRequest) error { +func (r *AppsecRunner) AccumulateTxToEvent(evt *pipeline.Event, state *appsec.AppsecRequestState, req *appsec.ParsedRequest) { if evt == nil { - // an error was already emitted, let's not spam the logs - return nil + return } - if !req.Tx.IsInterrupted() { + if !state.Tx.IsInterrupted() { // if the phase didn't generate an interruption, we don't have anything to add to the event - return nil + return } // if one interruption was generated, event is good for processing :) evt.Process = true @@ -354,21 +353,21 @@ func (r *AppsecRunner) AccumulateTxToEvent(evt *pipeline.Event, req *appsec.Pars evt.Parsed = map[string]string{} } - if req.IsInBand { + if state.CurrentPhase == appsec.PhaseInBand { evt.Meta["appsec_interrupted"] = "true" - evt.Meta["appsec_action"] = req.Tx.Interruption().Action + evt.Meta["appsec_action"] = state.Tx.Interruption().Action evt.Parsed["inband_interrupted"] = "true" - evt.Parsed["inband_action"] = req.Tx.Interruption().Action + evt.Parsed["inband_action"] = state.Tx.Interruption().Action } else { evt.Parsed["outofband_interrupted"] = "true" - evt.Parsed["outofband_action"] = req.Tx.Interruption().Action + evt.Parsed["outofband_action"] = state.Tx.Interruption().Action } if evt.Appsec.Vars == nil { evt.Appsec.Vars = map[string]string{} } - txCollection := req.Tx.Variables().TX() + txCollection := state.Tx.Variables().TX() txMatchedData := txCollection.FindAll() @@ -379,7 +378,7 @@ func (r *AppsecRunner) AccumulateTxToEvent(evt *pipeline.Event, req *appsec.Pars } if len(r.AppsecRuntime.CompiledVariablesTracking) > 0 { - req.Tx.Variables().All(func(v variables.RuleVariable, col collection.Collection) bool { + state.Tx.Variables().All(func(v variables.RuleVariable, col collection.Collection) bool { for _, variable := range col.FindAll() { r.logger.Tracef("variable: %s.%s = %s\n", variable.Variable().Name(), variable.Key(), variable.Value()) key := variable.Variable().Name() @@ -406,14 +405,14 @@ func (r *AppsecRunner) AccumulateTxToEvent(evt *pipeline.Event, req *appsec.Pars }) } - for _, rule := range req.Tx.MatchedRules() { + for _, rule := range state.Tx.MatchedRules() { // Drop the rule if it has no message (it's likely a CRS setup rule) if rule.Message() == "" { r.logger.Tracef("discarding rule %d (action: %s)", rule.Rule().ID(), rule.DisruptiveAction()) continue } kind := "outofband" - if req.IsInBand { + if state.CurrentPhase == appsec.PhaseInBand { kind = "inband" evt.Appsec.HasInBandMatches = true } else { @@ -486,6 +485,4 @@ func (r *AppsecRunner) AccumulateTxToEvent(evt *pipeline.Event, req *appsec.Pars evt.Appsec.MatchedRules = append(evt.Appsec.MatchedRules, corazaRule) } - - return nil } diff --git a/pkg/appsec/appsec.go b/pkg/appsec/appsec.go index 65a89afe2bb..c47615b6f41 100644 --- a/pkg/appsec/appsec.go +++ b/pkg/appsec/appsec.go @@ -38,6 +38,13 @@ const ( AllowRemediation = "allow" ) +type phase int + +const ( + PhaseInBand phase = iota + PhaseOutOfBand +) + func (h *Hook) Build(hookStage int) error { ctx := map[string]any{} @@ -45,11 +52,11 @@ func (h *Hook) Build(hookStage int) error { case hookOnLoad: ctx = GetOnLoadEnv(&AppsecRuntimeConfig{}) case hookPreEval: - ctx = GetPreEvalEnv(&AppsecRuntimeConfig{}, &ParsedRequest{}) + ctx = GetPreEvalEnv(&AppsecRuntimeConfig{}, nil, &ParsedRequest{}) case hookPostEval: - ctx = GetPostEvalEnv(&AppsecRuntimeConfig{}, &ParsedRequest{}) + ctx = GetPostEvalEnv(&AppsecRuntimeConfig{}, nil, &ParsedRequest{}) case hookOnMatch: - ctx = GetOnMatchEnv(&AppsecRuntimeConfig{}, &ParsedRequest{}, pipeline.Event{}) + ctx = GetOnMatchEnv(&AppsecRuntimeConfig{}, nil, &ParsedRequest{}, pipeline.Event{}) } opts := exprhelpers.GetExprOptions(ctx) @@ -84,6 +91,26 @@ type AppsecTempResponse struct { SendAlert bool // do we send an alert on rule match } +type AppsecRequestState struct { + Tx ExtendedTransaction + Response AppsecTempResponse + CurrentPhase phase +} + +func (s *AppsecRequestState) ResetResponse(cfg *AppsecConfig) { + if cfg == nil { + s.Response = AppsecTempResponse{} + return + } + + s.Response = AppsecTempResponse{} + s.Response.Action = cfg.DefaultPassAction + s.Response.BouncerHTTPResponseCode = cfg.BouncerPassedHTTPCode + s.Response.UserHTTPResponseCode = cfg.UserPassedHTTPCode + s.Response.SendEvent = true + s.Response.SendAlert = true +} + type AppsecSubEngineOpts struct { DisableBodyInspection bool `yaml:"disable_body_inspection"` RequestBodyInMemoryLimit *int `yaml:"request_body_in_memory_limit"` @@ -107,12 +134,6 @@ type AppsecRuntimeConfig struct { Config *AppsecConfig // CorazaLogger debuglog.Logger - // those are ephemeral, created/destroyed with every req - OutOfBandTx ExtendedTransaction // is it a good idea ? - InBandTx ExtendedTransaction // is it a good idea ? - Response AppsecTempResponse - // should we store matched rules here ? - Logger *log.Entry // Set by on_load to ignore some rules on loading @@ -146,13 +167,14 @@ type AppsecConfig struct { Logger *log.Entry `yaml:"-"` } -func (w *AppsecRuntimeConfig) ClearResponse() { - w.Response = AppsecTempResponse{} - w.Response.Action = w.Config.DefaultPassAction - w.Response.BouncerHTTPResponseCode = w.Config.BouncerPassedHTTPCode - w.Response.UserHTTPResponseCode = w.Config.UserPassedHTTPCode - w.Response.SendEvent = true - w.Response.SendAlert = true +func (w *AppsecRuntimeConfig) NewRequestState() AppsecRequestState { + state := AppsecRequestState{} + state.ResetResponse(w.Config) + return state +} + +func (w *AppsecRuntimeConfig) ClearResponse(state *AppsecRequestState) { + state.ResetResponse(w.Config) } func (wc *AppsecConfig) SetUpLogger() { @@ -445,12 +467,12 @@ func (w *AppsecRuntimeConfig) ProcessOnLoadRules() error { return nil } -func (w *AppsecRuntimeConfig) ProcessOnMatchRules(request *ParsedRequest, evt pipeline.Event) error { +func (w *AppsecRuntimeConfig) ProcessOnMatchRules(state *AppsecRequestState, request *ParsedRequest, evt pipeline.Event) error { has_match := false for _, rule := range w.CompiledOnMatch { if rule.FilterExpr != nil { - output, err := exprhelpers.Run(rule.FilterExpr, GetOnMatchEnv(w, request, evt), w.Logger, w.Logger.Level >= log.DebugLevel) + output, err := exprhelpers.Run(rule.FilterExpr, GetOnMatchEnv(w, state, request, evt), w.Logger, w.Logger.Level >= log.DebugLevel) if err != nil { return fmt.Errorf("unable to run appsec on_match filter %s : %w", rule.Filter, err) } @@ -470,7 +492,7 @@ func (w *AppsecRuntimeConfig) ProcessOnMatchRules(request *ParsedRequest, evt pi } for _, applyExpr := range rule.ApplyExpr { - o, err := exprhelpers.Run(applyExpr, GetOnMatchEnv(w, request, evt), w.Logger, w.Logger.Level >= log.DebugLevel) + o, err := exprhelpers.Run(applyExpr, GetOnMatchEnv(w, state, request, evt), w.Logger, w.Logger.Level >= log.DebugLevel) if err != nil { w.Logger.Errorf("unable to apply appsec on_match expr: %s", err) continue @@ -492,12 +514,12 @@ func (w *AppsecRuntimeConfig) ProcessOnMatchRules(request *ParsedRequest, evt pi return nil } -func (w *AppsecRuntimeConfig) ProcessPreEvalRules(request *ParsedRequest) error { +func (w *AppsecRuntimeConfig) ProcessPreEvalRules(state *AppsecRequestState, request *ParsedRequest) error { has_match := false for _, rule := range w.CompiledPreEval { if rule.FilterExpr != nil { - output, err := exprhelpers.Run(rule.FilterExpr, GetPreEvalEnv(w, request), w.Logger, w.Logger.Level >= log.DebugLevel) + output, err := exprhelpers.Run(rule.FilterExpr, GetPreEvalEnv(w, state, request), w.Logger, w.Logger.Level >= log.DebugLevel) if err != nil { return fmt.Errorf("unable to run appsec pre_eval filter %s : %w", rule.Filter, err) } @@ -517,7 +539,7 @@ func (w *AppsecRuntimeConfig) ProcessPreEvalRules(request *ParsedRequest) error } // here means there is no filter or the filter matched for _, applyExpr := range rule.ApplyExpr { - o, err := exprhelpers.Run(applyExpr, GetPreEvalEnv(w, request), w.Logger, w.Logger.Level >= log.DebugLevel) + o, err := exprhelpers.Run(applyExpr, GetPreEvalEnv(w, state, request), w.Logger, w.Logger.Level >= log.DebugLevel) if err != nil { w.Logger.Errorf("unable to apply appsec pre_eval expr: %s", err) continue @@ -539,12 +561,12 @@ func (w *AppsecRuntimeConfig) ProcessPreEvalRules(request *ParsedRequest) error return nil } -func (w *AppsecRuntimeConfig) ProcessPostEvalRules(request *ParsedRequest) error { +func (w *AppsecRuntimeConfig) ProcessPostEvalRules(state *AppsecRequestState, request *ParsedRequest) error { has_match := false for _, rule := range w.CompiledPostEval { if rule.FilterExpr != nil { - output, err := exprhelpers.Run(rule.FilterExpr, GetPostEvalEnv(w, request), w.Logger, w.Logger.Level >= log.DebugLevel) + output, err := exprhelpers.Run(rule.FilterExpr, GetPostEvalEnv(w, state, request), w.Logger, w.Logger.Level >= log.DebugLevel) if err != nil { return fmt.Errorf("unable to run appsec post_eval filter %s : %w", rule.Filter, err) } @@ -564,7 +586,7 @@ func (w *AppsecRuntimeConfig) ProcessPostEvalRules(request *ParsedRequest) error } // here means there is no filter or the filter matched for _, applyExpr := range rule.ApplyExpr { - o, err := exprhelpers.Run(applyExpr, GetPostEvalEnv(w, request), w.Logger, w.Logger.Level >= log.DebugLevel) + o, err := exprhelpers.Run(applyExpr, GetPostEvalEnv(w, state, request), w.Logger, w.Logger.Level >= log.DebugLevel) if err != nil { w.Logger.Errorf("unable to apply appsec post_eval expr: %s", err) continue @@ -586,41 +608,67 @@ func (w *AppsecRuntimeConfig) ProcessPostEvalRules(request *ParsedRequest) error return nil } -func (w *AppsecRuntimeConfig) RemoveInbandRuleByID(id int) error { +func (w *AppsecRuntimeConfig) RemoveInbandRuleByID(state *AppsecRequestState, id int) error { + if state.CurrentPhase != PhaseInBand { + w.Logger.Warnf("cannot remove inband rule %d when not in inband phase", id) + return nil + } w.Logger.Debugf("removing inband rule %d", id) - return w.InBandTx.RemoveRuleByIDWithError(id) + return state.Tx.RemoveRuleByIDWithError(id) } -func (w *AppsecRuntimeConfig) RemoveOutbandRuleByID(id int) error { +func (w *AppsecRuntimeConfig) RemoveOutbandRuleByID(state *AppsecRequestState, id int) error { + if state.CurrentPhase != PhaseOutOfBand { + w.Logger.Warnf("cannot remove outband rule %d when not in outband phase", id) + return nil + } w.Logger.Debugf("removing outband rule %d", id) - return w.OutOfBandTx.RemoveRuleByIDWithError(id) + return state.Tx.RemoveRuleByIDWithError(id) } -func (w *AppsecRuntimeConfig) RemoveInbandRuleByTag(tag string) error { +func (w *AppsecRuntimeConfig) RemoveInbandRuleByTag(state *AppsecRequestState, tag string) error { + if state.CurrentPhase != PhaseInBand { + w.Logger.Warnf("cannot remove inband rule with tag %s when not in inband phase", tag) + return nil + } + w.Logger.Debugf("removing inband rule with tag %s", tag) - return w.InBandTx.RemoveRuleByTagWithError(tag) + return state.Tx.RemoveRuleByTagWithError(tag) } -func (w *AppsecRuntimeConfig) RemoveOutbandRuleByTag(tag string) error { +func (w *AppsecRuntimeConfig) RemoveOutbandRuleByTag(state *AppsecRequestState, tag string) error { + if state.CurrentPhase != PhaseOutOfBand { + w.Logger.Warnf("cannot remove outband rule with tag %s when not in outband phase", tag) + return nil + } + w.Logger.Debugf("removing outband rule with tag %s", tag) - return w.OutOfBandTx.RemoveRuleByTagWithError(tag) + return state.Tx.RemoveRuleByTagWithError(tag) } -func (w *AppsecRuntimeConfig) RemoveInbandRuleByName(name string) error { +func (w *AppsecRuntimeConfig) RemoveInbandRuleByName(state *AppsecRequestState, name string) error { + if state.CurrentPhase != PhaseInBand { + w.Logger.Warnf("cannot remove inband rule with name %s when not in inband phase", name) + return nil + } tag := fmt.Sprintf("crowdsec-%s", name) w.Logger.Debugf("removing inband rule %s", tag) - return w.InBandTx.RemoveRuleByTagWithError(tag) + return w.RemoveInbandRuleByTag(state, tag) } -func (w *AppsecRuntimeConfig) RemoveOutbandRuleByName(name string) error { +func (w *AppsecRuntimeConfig) RemoveOutbandRuleByName(state *AppsecRequestState, name string) error { + if state.CurrentPhase != PhaseOutOfBand { + w.Logger.Warnf("cannot remove outband rule with name %s when not in outband phase", name) + return nil + } tag := fmt.Sprintf("crowdsec-%s", name) w.Logger.Debugf("removing outband rule %s", tag) - return w.OutOfBandTx.RemoveRuleByTagWithError(tag) + return w.RemoveOutbandRuleByTag(state, tag) } -func (w *AppsecRuntimeConfig) CancelEvent() error { +func (w *AppsecRuntimeConfig) CancelEvent(state *AppsecRequestState) error { w.Logger.Debugf("canceling event") - w.Response.SendEvent = false + state.Response.SendEvent = false return nil } @@ -663,21 +711,21 @@ func (w *AppsecRuntimeConfig) DisableOutBandRuleByTag(tag string) error { return nil } -func (w *AppsecRuntimeConfig) SendEvent() error { +func (w *AppsecRuntimeConfig) SendEvent(state *AppsecRequestState) error { w.Logger.Debugf("sending event") - w.Response.SendEvent = true + state.Response.SendEvent = true return nil } -func (w *AppsecRuntimeConfig) SendAlert() error { +func (w *AppsecRuntimeConfig) SendAlert(state *AppsecRequestState) error { w.Logger.Debugf("sending alert") - w.Response.SendAlert = true + state.Response.SendAlert = true return nil } -func (w *AppsecRuntimeConfig) CancelAlert() error { +func (w *AppsecRuntimeConfig) CancelAlert(state *AppsecRequestState) error { w.Logger.Debugf("canceling alert") - w.Response.SendAlert = false + state.Response.SendAlert = false return nil } @@ -709,16 +757,15 @@ func (w *AppsecRuntimeConfig) SetActionByName(name string, action string) error return nil } -func (w *AppsecRuntimeConfig) SetAction(action string) error { - // log.Infof("setting to %s", action) +func (w *AppsecRuntimeConfig) SetAction(state *AppsecRequestState, action string) error { w.Logger.Debugf("setting action to %s", action) - w.Response.Action = action + state.Response.Action = action return nil } -func (w *AppsecRuntimeConfig) SetHTTPCode(code int) error { +func (w *AppsecRuntimeConfig) SetHTTPCode(state *AppsecRequestState, code int) error { w.Logger.Debugf("setting http code to %d", code) - w.Response.UserHTTPResponseCode = code + state.Response.UserHTTPResponseCode = code return nil } diff --git a/pkg/appsec/request.go b/pkg/appsec/request.go index 72a5d365010..0d85c1b2d1a 100644 --- a/pkg/appsec/request.go +++ b/pkg/appsec/request.go @@ -41,7 +41,6 @@ type ParsedRequest struct { Body []byte `json:"body,omitempty"` TransferEncoding []string `json:"transfer_encoding,omitempty"` UUID string `json:"uuid,omitempty"` - Tx ExtendedTransaction `json:"-"` ResponseChannel chan AppsecTempResponse `json:"-"` IsInBand bool `json:"-"` IsOutBand bool `json:"-"` diff --git a/pkg/appsec/waf_helpers.go b/pkg/appsec/waf_helpers.go index 146a6faea30..3125db6f539 100644 --- a/pkg/appsec/waf_helpers.go +++ b/pkg/appsec/waf_helpers.go @@ -18,24 +18,24 @@ func GetOnLoadEnv(w *AppsecRuntimeConfig) map[string]interface{} { } } -func GetPreEvalEnv(w *AppsecRuntimeConfig, request *ParsedRequest) map[string]interface{} { +func GetPreEvalEnv(w *AppsecRuntimeConfig, state *AppsecRequestState, request *ParsedRequest) map[string]interface{} { return map[string]interface{}{ "IsInBand": request.IsInBand, "IsOutBand": request.IsOutBand, "req": request.HTTPRequest, - "RemoveInBandRuleByID": w.RemoveInbandRuleByID, - "RemoveInBandRuleByName": w.RemoveInbandRuleByName, - "RemoveInBandRuleByTag": w.RemoveInbandRuleByTag, - "RemoveOutBandRuleByID": w.RemoveOutbandRuleByID, - "RemoveOutBandRuleByTag": w.RemoveOutbandRuleByTag, - "RemoveOutBandRuleByName": w.RemoveOutbandRuleByName, + "RemoveInBandRuleByID": func(id int) error { return w.RemoveInbandRuleByID(state, id) }, + "RemoveInBandRuleByName": func(name string) error { return w.RemoveInbandRuleByName(state, name) }, + "RemoveInBandRuleByTag": func(tag string) error { return w.RemoveInbandRuleByTag(state, tag) }, + "RemoveOutBandRuleByID": func(id int) error { return w.RemoveOutbandRuleByID(state, id) }, + "RemoveOutBandRuleByTag": func(tag string) error { return w.RemoveOutbandRuleByTag(state, tag) }, + "RemoveOutBandRuleByName": func(name string) error { return w.RemoveOutbandRuleByName(state, name) }, "SetRemediationByTag": w.SetActionByTag, "SetRemediationByID": w.SetActionByID, "SetRemediationByName": w.SetActionByName, } } -func GetPostEvalEnv(w *AppsecRuntimeConfig, request *ParsedRequest) map[string]interface{} { +func GetPostEvalEnv(w *AppsecRuntimeConfig, state *AppsecRequestState, request *ParsedRequest) map[string]interface{} { return map[string]interface{}{ "IsInBand": request.IsInBand, "IsOutBand": request.IsOutBand, @@ -44,18 +44,18 @@ func GetPostEvalEnv(w *AppsecRuntimeConfig, request *ParsedRequest) map[string]i } } -func GetOnMatchEnv(w *AppsecRuntimeConfig, request *ParsedRequest, evt pipeline.Event) map[string]interface{} { +func GetOnMatchEnv(w *AppsecRuntimeConfig, state *AppsecRequestState, request *ParsedRequest, evt pipeline.Event) map[string]interface{} { return map[string]interface{}{ "evt": evt, "req": request.HTTPRequest, "IsInBand": request.IsInBand, "IsOutBand": request.IsOutBand, - "SetRemediation": w.SetAction, - "SetReturnCode": w.SetHTTPCode, - "CancelEvent": w.CancelEvent, - "SendEvent": w.SendEvent, - "CancelAlert": w.CancelAlert, - "SendAlert": w.SendAlert, + "SetRemediation": func(action string) error { return w.SetAction(state, action) }, + "SetReturnCode": func(code int) error { return w.SetHTTPCode(state, code) }, + "CancelEvent": func() error { return w.CancelEvent(state) }, + "SendEvent": func() error { return w.SendEvent(state) }, + "CancelAlert": func() error { return w.CancelAlert(state) }, + "SendAlert": func() error { return w.SendAlert(state) }, "DumpRequest": request.DumpRequest, } }