From c05b7e81ac5406f39fcefecaa635da232d174228 Mon Sep 17 00:00:00 2001 From: Jacob Moore Date: Thu, 11 Apr 2024 14:48:24 -0400 Subject: [PATCH] Added request validation filtering --- cmd/root_command.go | 10 ++++ config/paths.go | 18 +++++++ config/paths_test.go | 2 + daemon/handle_request.go | 14 +++-- daemon/validate.go | 12 +---- shared/config.go | 112 ++++++++++++++++++++++++--------------- 6 files changed, 109 insertions(+), 59 deletions(-) diff --git a/cmd/root_command.go b/cmd/root_command.go index ab2c853..6d00c60 100644 --- a/cmd/root_command.go +++ b/cmd/root_command.go @@ -359,6 +359,16 @@ var ( printLoadedWebsockets(config.WebsocketConfigs) } + if len(config.ValidationAllowList) > 0 { + config.CompileIgnoreValidations() + // TODO: add print command + } + + if len(config.IgnoreValidation) > 0 { + config.CompileIgnoreValidations() + // TODO: add print command + } + // static headers if config.Headers != nil && len(config.Headers.DropHeaders) > 0 { pterm.Info.Printf("Dropping the following %d %s globally:\n", len(config.Headers.DropHeaders), diff --git a/config/paths.go b/config/paths.go index 2def94b..be3dd85 100644 --- a/config/paths.go +++ b/config/paths.go @@ -47,6 +47,24 @@ func PathRedirectAllowListed(path string, configuration *shared.WiretapConfigura return false } +func IgnoreValidationOnPath(path string, configuration *shared.WiretapConfiguration) bool { + for _, redirectPath := range configuration.CompiledIgnoreValidations { + if redirectPath.CompiledPath.Match(path) { + return true + } + } + return false +} + +func PathValidationAllowListed(path string, configuration *shared.WiretapConfiguration) bool { + for _, redirectPath := range configuration.CompiledIgnoreValidations { + if redirectPath.CompiledPath.Match(path) { + return true + } + } + return false +} + func RewritePath(path string, configuration *shared.WiretapConfiguration) string { paths := FindPaths(path, configuration) var replaced string = path diff --git a/config/paths_test.go b/config/paths_test.go index 78fd018..a9467f4 100644 --- a/config/paths_test.go +++ b/config/paths_test.go @@ -308,3 +308,5 @@ func TestRedirectAllowList_NoPathsRegistered(t *testing.T) { assert.False(t, ignore) } + +// TODO: add tests for new validation functions diff --git a/daemon/handle_request.go b/daemon/handle_request.go index d10278b..149596c 100644 --- a/daemon/handle_request.go +++ b/daemon/handle_request.go @@ -131,17 +131,21 @@ func (ws *WiretapService) handleHttpRequest(request *model.Request) { ws.config.Logger.Info("[wiretap] handling API request", "url", request.HttpRequest.URL.String()) - // check if we're going to fail hard on validation errors. (default is to skip this) - if ws.config.HardErrors && !ws.config.MockMode { + if ws.config.MockMode { + // TODO: Add logging + + } else if configModel.IgnoreValidationOnPath(apiRequest.URL.Path, ws.config) && !configModel.PathValidationAllowListed(apiRequest.URL.Path, ws.config) { + // TODO: Add logging + + // check if we're going to fail hard on validation errors. (default is to skip this) + } else if ws.config.HardErrors { // validate the request synchronously requestErrors = ws.ValidateRequest(request, newReq) } else { // validate the request asynchronously - if !ws.config.MockMode { - go ws.ValidateRequest(request, newReq) - } + go ws.ValidateRequest(request, newReq) } // short-circuit if we're using mock mode, there is no API call to make. diff --git a/daemon/validate.go b/daemon/validate.go index de74bd7..8491833 100644 --- a/daemon/validate.go +++ b/daemon/validate.go @@ -53,16 +53,8 @@ func (ws *WiretapService) ValidateRequest( _, validationErrors = validator.ValidateHttpRequest(httpRequest) } - pm := false - for i := range validationErrors { - if validationErrors[i].IsPathMissingError() { - if !pm { - cleanedErrors = append(cleanedErrors, validationErrors[i]) - pm = true - } - } else { - cleanedErrors = append(cleanedErrors, validationErrors[i]) - } + for _, validationError := range validationErrors { + cleanedErrors = append(cleanedErrors, validationError) } // record results buildTransConfig := HttpTransactionConfig{ diff --git a/shared/config.go b/shared/config.go index e7552cb..6c8aabb 100644 --- a/shared/config.go +++ b/shared/config.go @@ -13,51 +13,55 @@ import ( ) type WiretapConfiguration struct { - Contract string `json:"-" yaml:"-"` - RedirectHost string `json:"redirectHost,omitempty" yaml:"redirectHost,omitempty"` - RedirectPort string `json:"redirectPort,omitempty" yaml:"redirectPort,omitempty"` - RedirectBasePath string `json:"redirectBasePath,omitempty" yaml:"redirectBasePath,omitempty"` - RedirectProtocol string `json:"redirectProtocol,omitempty" yaml:"redirectProtocol,omitempty"` - RedirectURL string `json:"redirectURL,omitempty" yaml:"redirectURL,omitempty"` - Port string `json:"port,omitempty" yaml:"port,omitempty"` - MonitorPort string `json:"monitorPort,omitempty" yaml:"monitorPort,omitempty"` - WebSocketHost string `json:"webSocketHost,omitempty" yaml:"webSocketHost,omitempty"` - WebSocketPort string `json:"webSocketPort,omitempty" yaml:"webSocketPort,omitempty"` - GlobalAPIDelay int `json:"globalAPIDelay,omitempty" yaml:"globalAPIDelay,omitempty"` - StaticDir string `json:"staticDir,omitempty" yaml:"staticDir,omitempty"` - StaticIndex string `json:"staticIndex,omitempty" yaml:"staticIndex,omitempty"` - PathConfigurations map[string]*WiretapPathConfig `json:"paths,omitempty" yaml:"paths,omitempty"` - Headers *WiretapHeaderConfig `json:"headers,omitempty" yaml:"headers,omitempty"` - StaticPaths []string `json:"staticPaths,omitempty" yaml:"staticPaths,omitempty"` - Variables map[string]string `json:"variables,omitempty" yaml:"variables,omitempty"` - Spec string `json:"contract,omitempty" yaml:"contract,omitempty"` - Certificate string `json:"certificate,omitempty" yaml:"certificate,omitempty"` - CertificateKey string `json:"certificateKey,omitempty" yaml:"certificateKey,omitempty"` - HardErrors bool `json:"hardValidation,omitempty" yaml:"hardValidation,omitempty"` - HardErrorCode int `json:"hardValidationCode,omitempty" yaml:"hardValidationCode,omitempty"` - HardErrorReturnCode int `json:"hardValidationReturnCode,omitempty" yaml:"hardValidationReturnCode,omitempty"` - PathDelays map[string]int `json:"pathDelays,omitempty" yaml:"pathDelays,omitempty"` - MockMode bool `json:"mockMode,omitempty" yaml:"mockMode,omitempty"` - MockModePretty bool `json:"mockModePretty,omitempty" yaml:"mockModePretty,omitempty"` - Base string `json:"base,omitempty" yaml:"base,omitempty"` - HAR string `json:"har,omitempty" yaml:"har,omitempty"` - HARValidate bool `json:"harValidate,omitempty" yaml:"harValidate,omitempty"` - HARPathAllowList []string `json:"harPathAllowList,omitempty" yaml:"harPathAllowList,omitempty"` - StreamReport bool `json:"streamReport,omitempty" yaml:"streamReport,omitempty"` - ReportFile string `json:"reportFilename,omitempty" yaml:"reportFilename,omitempty"` - IgnoreRedirects []string `json:"ignoreRedirects,omitempty" yaml:"ignoreRedirects,omitempty"` - RedirectAllowList []string `json:"redirectAllowList,omitempty" yaml:"redirectAllowList,omitempty"` + Contract string `json:"-" yaml:"-"` + RedirectHost string `json:"redirectHost,omitempty" yaml:"redirectHost,omitempty"` + RedirectPort string `json:"redirectPort,omitempty" yaml:"redirectPort,omitempty"` + RedirectBasePath string `json:"redirectBasePath,omitempty" yaml:"redirectBasePath,omitempty"` + RedirectProtocol string `json:"redirectProtocol,omitempty" yaml:"redirectProtocol,omitempty"` + RedirectURL string `json:"redirectURL,omitempty" yaml:"redirectURL,omitempty"` + Port string `json:"port,omitempty" yaml:"port,omitempty"` + MonitorPort string `json:"monitorPort,omitempty" yaml:"monitorPort,omitempty"` + WebSocketHost string `json:"webSocketHost,omitempty" yaml:"webSocketHost,omitempty"` + WebSocketPort string `json:"webSocketPort,omitempty" yaml:"webSocketPort,omitempty"` + GlobalAPIDelay int `json:"globalAPIDelay,omitempty" yaml:"globalAPIDelay,omitempty"` + StaticDir string `json:"staticDir,omitempty" yaml:"staticDir,omitempty"` + StaticIndex string `json:"staticIndex,omitempty" yaml:"staticIndex,omitempty"` + PathConfigurations map[string]*WiretapPathConfig `json:"paths,omitempty" yaml:"paths,omitempty"` + Headers *WiretapHeaderConfig `json:"headers,omitempty" yaml:"headers,omitempty"` + StaticPaths []string `json:"staticPaths,omitempty" yaml:"staticPaths,omitempty"` + Variables map[string]string `json:"variables,omitempty" yaml:"variables,omitempty"` + Spec string `json:"contract,omitempty" yaml:"contract,omitempty"` + Certificate string `json:"certificate,omitempty" yaml:"certificate,omitempty"` + CertificateKey string `json:"certificateKey,omitempty" yaml:"certificateKey,omitempty"` + HardErrors bool `json:"hardValidation,omitempty" yaml:"hardValidation,omitempty"` + HardErrorCode int `json:"hardValidationCode,omitempty" yaml:"hardValidationCode,omitempty"` + HardErrorReturnCode int `json:"hardValidationReturnCode,omitempty" yaml:"hardValidationReturnCode,omitempty"` + PathDelays map[string]int `json:"pathDelays,omitempty" yaml:"pathDelays,omitempty"` + MockMode bool `json:"mockMode,omitempty" yaml:"mockMode,omitempty"` + MockModePretty bool `json:"mockModePretty,omitempty" yaml:"mockModePretty,omitempty"` + Base string `json:"base,omitempty" yaml:"base,omitempty"` + HAR string `json:"har,omitempty" yaml:"har,omitempty"` + HARValidate bool `json:"harValidate,omitempty" yaml:"harValidate,omitempty"` + HARPathAllowList []string `json:"harPathAllowList,omitempty" yaml:"harPathAllowList,omitempty"` + StreamReport bool `json:"streamReport,omitempty" yaml:"streamReport,omitempty"` + ReportFile string `json:"reportFilename,omitempty" yaml:"reportFilename,omitempty"` + IgnoreRedirects []string `json:"ignoreRedirects,omitempty" yaml:"ignoreRedirects,omitempty"` + RedirectAllowList []string `json:"redirectAllowList,omitempty" yaml:"redirectAllowList,omitempty"` WebsocketConfigs map[string]*WiretapWebsocketConfig `json:"websockets" yaml:"websockets"` - HARFile *harhar.HAR `json:"-" yaml:"-"` - CompiledPathDelays map[string]*CompiledPathDelay `json:"-" yaml:"-"` - CompiledVariables map[string]*CompiledVariable `json:"-" yaml:"-"` - Version string `json:"-" yaml:"-"` - StaticPathsCompiled []glob.Glob `json:"-" yaml:"-"` - CompiledPaths map[string]*CompiledPath `json:"-"` - CompiledIgnoreRedirects []*CompiledRedirect `json:"-" yaml:"-"` - CompiledRedirectAllowList []*CompiledRedirect `json:"-" yaml:"-"` - FS embed.FS `json:"-"` - Logger *slog.Logger + IgnoreValidation []string `json:"ignoreValidation,omitempty" yaml:"ignoreValidation,omitempty"` + ValidationAllowList []string `json:"validationAllowList,omitempty" yaml:"validationAllowList,omitempty"` + HARFile *harhar.HAR `json:"-" yaml:"-"` + CompiledPathDelays map[string]*CompiledPathDelay `json:"-" yaml:"-"` + CompiledVariables map[string]*CompiledVariable `json:"-" yaml:"-"` + Version string `json:"-" yaml:"-"` + StaticPathsCompiled []glob.Glob `json:"-" yaml:"-"` + CompiledPaths map[string]*CompiledPath `json:"-"` + CompiledIgnoreRedirects []*CompiledRedirect `json:"-" yaml:"-"` + CompiledRedirectAllowList []*CompiledRedirect `json:"-" yaml:"-"` + CompiledIgnoreValidations []*CompiledRedirect `json:"-" yaml:"-"` + CompiledValidationAllowList []*CompiledRedirect `json:"-" yaml:"-"` + FS embed.FS `json:"-"` + Logger *slog.Logger } func (wtc *WiretapConfiguration) CompilePaths() { @@ -116,6 +120,26 @@ func (wtc *WiretapConfiguration) CompileRedirectAllowList() { } } +func (wtc *WiretapConfiguration) CompileIgnoreValidations() { + wtc.CompiledIgnoreValidations = make([]*CompiledRedirect, 0) + for _, x := range wtc.IgnoreValidation { + compiled := &CompiledRedirect{ + CompiledPath: glob.MustCompile(wtc.ReplaceWithVariables(x)), + } + wtc.CompiledIgnoreValidations = append(wtc.CompiledIgnoreValidations, compiled) + } +} + +func (wtc *WiretapConfiguration) CompileValidationAllowList() { + wtc.CompiledValidationAllowList = make([]*CompiledRedirect, 0) + for _, x := range wtc.ValidationAllowList { + compiled := &CompiledRedirect{ + CompiledPath: glob.MustCompile(wtc.ReplaceWithVariables(x)), + } + wtc.CompiledValidationAllowList = append(wtc.CompiledValidationAllowList, compiled) + } +} + func (wtc *WiretapConfiguration) ReplaceWithVariables(input string) string { for x := range wtc.Variables { if wtc.Variables[x] != "" && wtc.CompiledVariables[x] != nil {