From 68b9dd52ad7b0afd3037d6a7616b5f0063d0b167 Mon Sep 17 00:00:00 2001 From: Tarun Koyalwar <45962551+tarunKoyalwar@users.noreply.github.com> Date: Wed, 17 Jan 2024 23:16:57 +0530 Subject: [PATCH] error handling + support offlinehttp in flow templates (#4653) --- pkg/templates/compile.go | 14 ++++++++++---- pkg/tmplexec/exec.go | 25 ++++++++++++++++--------- pkg/tmplexec/flow/flow_executor.go | 10 ++++++---- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/pkg/templates/compile.go b/pkg/templates/compile.go index 759f9ab510..3bffe7b9f5 100644 --- a/pkg/templates/compile.go +++ b/pkg/templates/compile.go @@ -183,8 +183,9 @@ func (template *Template) compileProtocolRequests(options *protocols.ExecutorOpt requests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsJavascript)...) } } - template.Executer = tmplexec.NewTemplateExecuter(requests, options) - return nil + var err error + template.Executer, err = tmplexec.NewTemplateExecuter(requests, options) + return err } // convertRequestToProtocolsRequest is a convenience wrapper to convert @@ -228,8 +229,13 @@ mainLoop: } if len(operatorsList) > 0 { options.Operators = operatorsList - template.Executer = tmplexec.NewTemplateExecuter([]protocols.Request{&offlinehttp.Request{}}, options) - return nil + var err error + template.Executer, err = tmplexec.NewTemplateExecuter([]protocols.Request{&offlinehttp.Request{}}, options) + if err != nil { + // it seems like flow executor cannot be used for offline http matching (ex:http(1) && http(2)) + return ErrIncompatibleWithOfflineMatching + } + return err } return ErrIncompatibleWithOfflineMatching diff --git a/pkg/tmplexec/exec.go b/pkg/tmplexec/exec.go index 04235d5016..c5e378396a 100644 --- a/pkg/tmplexec/exec.go +++ b/pkg/tmplexec/exec.go @@ -31,7 +31,7 @@ type TemplateExecuter struct { var _ protocols.Executer = &TemplateExecuter{} // NewTemplateExecuter creates a new request TemplateExecuter for list of requests -func NewTemplateExecuter(requests []protocols.Request, options *protocols.ExecutorOptions) *TemplateExecuter { +func NewTemplateExecuter(requests []protocols.Request, options *protocols.ExecutorOptions) (*TemplateExecuter, error) { isMultiProto := false lastProto := "" for _, request := range requests { @@ -47,7 +47,11 @@ func NewTemplateExecuter(requests []protocols.Request, options *protocols.Execut // we use a dummy input here because goal of flow executor at this point is to just check // syntax and other things are correct before proceeding to actual execution // during execution new instance of flow will be created as it is tightly coupled with lot of executor options - e.engine = flow.NewFlowExecutor(requests, scan.NewScanContext(contextargs.NewWithInput("dummy")), options, e.results) + var err error + e.engine, err = flow.NewFlowExecutor(requests, scan.NewScanContext(contextargs.NewWithInput("dummy")), options, e.results) + if err != nil { + return nil, fmt.Errorf("could not create flow executor: %s", err) + } } else { // Review: // multiproto engine is only used if there is more than one protocol in template @@ -58,8 +62,7 @@ func NewTemplateExecuter(requests []protocols.Request, options *protocols.Execut e.engine = generic.NewGenericEngine(requests, options, e.results) } } - - return e + return e, nil } // Compile compiles the execution generators preparing any requests possible. @@ -146,7 +149,7 @@ func (e *TemplateExecuter) Execute(ctx *scan.ScanContext) (bool, error) { } } } - var err error + var errx error // Note: this is required for flow executor // flow executer is tightly coupled with lot of executor options @@ -155,20 +158,24 @@ func (e *TemplateExecuter) Execute(ctx *scan.ScanContext) (bool, error) { // so in compile step earlier we compile it to validate javascript syntax and other things // and while executing we create new instance of flow executor everytime if e.options.Flow != "" { - flowexec := flow.NewFlowExecutor(e.requests, ctx, e.options, results) + flowexec, err := flow.NewFlowExecutor(e.requests, ctx, e.options, results) + if err != nil { + ctx.LogError(err) + return false, fmt.Errorf("could not create flow executor: %s", err) + } if err := flowexec.Compile(); err != nil { ctx.LogError(err) return false, err } - err = flowexec.ExecuteWithResults(ctx) + errx = flowexec.ExecuteWithResults(ctx) } else { - err = e.engine.ExecuteWithResults(ctx) + errx = e.engine.ExecuteWithResults(ctx) } if lastMatcherEvent != nil { writeFailureCallback(lastMatcherEvent, e.options.Options.MatcherStatus) } - return results.Load(), err + return results.Load(), errx } // ExecuteWithResults executes the protocol requests and returns results instead of writing them. diff --git a/pkg/tmplexec/flow/flow_executor.go b/pkg/tmplexec/flow/flow_executor.go index 4457d8e529..bfa35095cf 100644 --- a/pkg/tmplexec/flow/flow_executor.go +++ b/pkg/tmplexec/flow/flow_executor.go @@ -55,7 +55,7 @@ type FlowExecutor struct { // NewFlowExecutor creates a new flow executor from a list of requests // Note: Unlike other engine for every target x template flow needs to be compiled and executed everytime // unlike other engines where we compile once and execute multiple times -func NewFlowExecutor(requests []protocols.Request, ctx *scan.ScanContext, options *protocols.ExecutorOptions, results *atomic.Bool) *FlowExecutor { +func NewFlowExecutor(requests []protocols.Request, ctx *scan.ScanContext, options *protocols.ExecutorOptions, results *atomic.Bool) (*FlowExecutor, error) { allprotos := make(map[string][]protocols.Request) for _, req := range requests { switch req.Type() { @@ -79,9 +79,11 @@ func NewFlowExecutor(requests []protocols.Request, ctx *scan.ScanContext, option allprotos[templateTypes.CodeProtocol.String()] = append(allprotos[templateTypes.CodeProtocol.String()], req) case templateTypes.JavascriptProtocol: allprotos[templateTypes.JavascriptProtocol.String()] = append(allprotos[templateTypes.JavascriptProtocol.String()], req) + case templateTypes.OfflineHTTPProtocol: + // offlinehttp is run in passive mode but templates are same so instead of using offlinehttp() we use http() in flow + allprotos[templateTypes.HTTPProtocol.String()] = append(allprotos[templateTypes.OfflineHTTPProtocol.String()], req) default: - ctx.LogError(fmt.Errorf("invalid request type %s", req.Type().String())) - return nil + return nil, fmt.Errorf("invalid request type %s", req.Type().String()) } } f := &FlowExecutor{ @@ -96,7 +98,7 @@ func NewFlowExecutor(requests []protocols.Request, ctx *scan.ScanContext, option jsVM: protocolstate.NewJSRuntime(), ctx: ctx, } - return f + return f, nil } // Compile compiles js program and registers all functions