Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 66 additions & 65 deletions pkg/acquisition/modules/appsec/appsec_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,66 +132,66 @@ 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)
return nil
}

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
Expand All @@ -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
Expand All @@ -214,70 +214,67 @@ 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)
return
}

//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[[]string, string](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)
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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)
}
Expand All @@ -371,35 +371,36 @@ 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
if len(r.AppsecRuntime.OutOfBandRules) > 0 {
//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)
}
Expand All @@ -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)
}
Expand Down
Loading