From 828aeef9a7333aaabeaf9324a85aac56348b3805 Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Sun, 11 Oct 2020 22:50:20 +0200 Subject: Add SearchController --- application_context.go | 4 +-- application_router.go | 71 +++++++++++++---------------------------------- connections_controller.go | 47 +++++++++++++++++++------------ search_controller.go | 44 ++++++++++++++++++++--------- storage.go | 2 +- 5 files changed, 83 insertions(+), 85 deletions(-) diff --git a/application_context.go b/application_context.go index fc76d00..8abb6f4 100644 --- a/application_context.go +++ b/application_context.go @@ -112,9 +112,9 @@ func (sm *ApplicationContext) configure() { sm.RulesManager = rulesManager sm.PcapImporter = NewPcapImporter(sm.Storage, *serverNet, sm.RulesManager) sm.ServicesController = NewServicesController(sm.Storage) - sm.ConnectionsController = NewConnectionsController(sm.Storage, sm.ServicesController) - sm.ConnectionStreamsController = NewConnectionStreamsController(sm.Storage) sm.SearchController = NewSearchController(sm.Storage) + sm.ConnectionsController = NewConnectionsController(sm.Storage, sm.SearchController, sm.ServicesController) + sm.ConnectionStreamsController = NewConnectionStreamsController(sm.Storage) sm.StatisticsController = NewStatisticsController(sm.Storage) sm.IsConfigured = true } diff --git a/application_router.go b/application_router.go index dc9f9d4..656b63e 100644 --- a/application_router.go +++ b/application_router.go @@ -22,8 +22,6 @@ import ( "fmt" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/binding" - "github.com/go-playground/validator/v10" log "github.com/sirupsen/logrus" "net/http" "os" @@ -297,71 +295,42 @@ func CreateApplicationRouter(applicationContext *ApplicationContext, }) api.GET("/searches", func(c *gin.Context) { - success(c, applicationContext.SearchController.PerformedSearches()) + success(c, applicationContext.SearchController.GetPerformedSearches()) }) api.POST("/searches/perform", func(c *gin.Context) { var options SearchOptions - - parentIsZero := func (fl validator.FieldLevel) bool { - log.Println(fl.FieldName()) - log.Println("noooooo") - return fl.Parent().IsZero() - } - eitherWith := func (fl validator.FieldLevel) bool { - otherField := fl.Parent().FieldByName(fl.Param()) - log.Println(fl.Param()) - log.Println("bbbbbbbbbb") - return (fl.Field().IsZero() && !otherField.IsZero()) || (!fl.Field().IsZero() && otherField.IsZero()) - } - aaa := func (fl validator.FieldLevel) bool { - - log.Println("awww") - return fl.Field().IsZero() + if err := c.ShouldBindJSON(&options); err != nil { + badRequest(c, err) + return } - bbb := func (fl validator.FieldLevel) bool { - - log.Println("iiiii") - return true + // stupid checks because validator library is a shit + var badContentError error + if options.TextSearch.isZero() == options.RegexSearch.isZero() { + badContentError = errors.New("specify either 'text_search' or 'regex_search'") } - - if validate, ok := binding.Validator.Engine().(*validator.Validate); ok { - if err := validate.RegisterValidation("parent_is_zero", parentIsZero); err != nil { - log.WithError(err).Panic("cannot register 'topzero' validator") + if !options.TextSearch.isZero() { + if (options.TextSearch.Terms == nil) == (options.TextSearch.ExactPhrase == "") { + badContentError = errors.New("specify either 'terms' or 'exact_phrase'") } - if err := validate.RegisterValidation("either_with", eitherWith); err != nil { - log.WithError(err).Panic("cannot register 'either_with' validator") + if (options.TextSearch.Terms == nil) && (options.TextSearch.ExcludedTerms != nil) { + badContentError = errors.New("'excluded_terms' must be specified only with 'terms'") } - if err := validate.RegisterValidation("aaa", aaa); err != nil { - log.WithError(err).Panic("cannot register 'either_with' validator") - } - if err := validate.RegisterValidation("bbb", bbb); err != nil { - log.WithError(err).Panic("cannot register 'either_with' validator") + } + if !options.RegexSearch.isZero() { + if (options.RegexSearch.Pattern == "") == (options.RegexSearch.NotPattern == "") { + badContentError = errors.New("specify either 'pattern' or 'not_pattern'") } - } else { - log.Panic("cannot ") } - - if err := c.ShouldBindJSON(&options); err != nil { - badRequest(c, err) + if badContentError != nil { + badRequest(c, badContentError) return } - log.Println(options) - - - success(c, "ok") - - - - - - - - //success(c, applicationContext.SearchController.PerformSearch(c, options)) + success(c, applicationContext.SearchController.PerformSearch(c, options)) }) api.GET("/streams/:id", func(c *gin.Context) { diff --git a/connections_controller.go b/connections_controller.go index 30a5ee5..a293a80 100644 --- a/connections_controller.go +++ b/connections_controller.go @@ -48,33 +48,37 @@ type Connection struct { } type ConnectionsFilter struct { - From string `form:"from" binding:"omitempty,hexadecimal,len=24"` - To string `form:"to" binding:"omitempty,hexadecimal,len=24"` - ServicePort uint16 `form:"service_port"` - ClientAddress string `form:"client_address" binding:"omitempty,ip"` - ClientPort uint16 `form:"client_port"` - MinDuration uint `form:"min_duration"` - MaxDuration uint `form:"max_duration" binding:"omitempty,gtefield=MinDuration"` - MinBytes uint `form:"min_bytes"` - MaxBytes uint `form:"max_bytes" binding:"omitempty,gtefield=MinBytes"` - StartedAfter int64 `form:"started_after" ` - StartedBefore int64 `form:"started_before" binding:"omitempty,gtefield=StartedAfter"` - ClosedAfter int64 `form:"closed_after" ` - ClosedBefore int64 `form:"closed_before" binding:"omitempty,gtefield=ClosedAfter"` - Hidden bool `form:"hidden"` - Marked bool `form:"marked"` - MatchedRules []string `form:"matched_rules" binding:"dive,hexadecimal,len=24"` - Limit int64 `form:"limit"` + From string `form:"from" binding:"omitempty,hexadecimal,len=24"` + To string `form:"to" binding:"omitempty,hexadecimal,len=24"` + ServicePort uint16 `form:"service_port"` + ClientAddress string `form:"client_address" binding:"omitempty,ip"` + ClientPort uint16 `form:"client_port"` + MinDuration uint `form:"min_duration"` + MaxDuration uint `form:"max_duration" binding:"omitempty,gtefield=MinDuration"` + MinBytes uint `form:"min_bytes"` + MaxBytes uint `form:"max_bytes" binding:"omitempty,gtefield=MinBytes"` + StartedAfter int64 `form:"started_after" ` + StartedBefore int64 `form:"started_before" binding:"omitempty,gtefield=StartedAfter"` + ClosedAfter int64 `form:"closed_after" ` + ClosedBefore int64 `form:"closed_before" binding:"omitempty,gtefield=ClosedAfter"` + Hidden bool `form:"hidden"` + Marked bool `form:"marked"` + MatchedRules []string `form:"matched_rules" binding:"dive,hexadecimal,len=24"` + PerformedSearch string `form:"performed_search" binding:"omitempty,hexadecimal,len=24"` + Limit int64 `form:"limit"` } type ConnectionsController struct { storage Storage + searchController *SearchController servicesController *ServicesController } -func NewConnectionsController(storage Storage, servicesController *ServicesController) ConnectionsController { +func NewConnectionsController(storage Storage, searchesController *SearchController, + servicesController *ServicesController) ConnectionsController { return ConnectionsController{ storage: storage, + searchController: searchesController, servicesController: servicesController, } } @@ -144,6 +148,13 @@ func (cc ConnectionsController) GetConnections(c context.Context, filter Connect query = query.Filter(OrderedDocument{{"matched_rules", UnorderedDocument{"$all": matchedRules}}}) } + performedSearchID, _ := RowIDFromHex(filter.PerformedSearch) + if !performedSearchID.IsZero() { + performedSearch := cc.searchController.GetPerformedSearch(performedSearchID) + if !performedSearch.ID.IsZero() { + query = query.Filter(OrderedDocument{{"_id", UnorderedDocument{"$in": performedSearch.AffectedConnections}}}) + } + } if filter.Limit > 0 && filter.Limit <= MaxQueryLimit { query = query.Limit(filter.Limit) } else { diff --git a/search_controller.go b/search_controller.go index ad47dbc..723cd93 100644 --- a/search_controller.go +++ b/search_controller.go @@ -26,14 +26,15 @@ import ( ) const ( - secondsToNano = 1000 * 1000 * 1000 - maxSearchTimeout = 60 * secondsToNano + secondsToNano = 1000 * 1000 * 1000 + maxSearchTimeout = 10 * secondsToNano + maxRecentSearches = 200 ) type PerformedSearch struct { ID RowID `bson:"_id" json:"id"` SearchOptions SearchOptions `bson:"search_options" json:"search_options"` - AffectedConnections []RowID `bson:"affected_connections" json:"affected_connections,omitempty"` + AffectedConnections []RowID `bson:"affected_connections" json:"-"` AffectedConnectionsCount int `bson:"affected_connections_count" json:"affected_connections_count"` StartedAt time.Time `bson:"started_at" json:"started_at"` FinishedAt time.Time `bson:"finished_at" json:"finished_at"` @@ -42,21 +43,21 @@ type PerformedSearch struct { } type SearchOptions struct { - TextSearch TextSearch `bson:"text_search" json:"text_search" validate:"either_with=RegexSearch"` - RegexSearch RegexSearch `bson:"regex_search" json:"regex_search" validate:"either_with=TextSearch"` + TextSearch TextSearch `bson:"text_search" json:"text_search"` + RegexSearch RegexSearch `bson:"regex_search" json:"regex_search"` Timeout time.Duration `bson:"timeout" json:"timeout" binding:"max=60"` } type TextSearch struct { - Terms []string `bson:"terms" json:"terms" binding:"parent_is_zero|either_with=ExactPhrase,isdefault|min=1,dive,min=3"` - ExcludedTerms []string `bson:"excluded_terms" json:"excluded_terms" binding:"required_with=Terms,dive,isdefault|min=1"` - ExactPhrase string `bson:"exact_phrase" json:"exact_phrase" binding:"isdefault|min=3,parent_is_zero|either_with=Terms"` + Terms []string `bson:"terms" json:"terms" binding:"isdefault|min=1,dive,min=3"` + ExcludedTerms []string `bson:"excluded_terms" json:"excluded_terms" binding:"isdefault|min=1,dive,min=3"` + ExactPhrase string `bson:"exact_phrase" json:"exact_phrase" binding:"isdefault|min=3"` CaseSensitive bool `bson:"case_sensitive" json:"case_sensitive"` } type RegexSearch struct { - Pattern string `bson:"pattern" json:"pattern" binding:"parent_is_zero|either_with=NotPattern,isdefault|min=3"` - NotPattern string `bson:"not_pattern" json:"not_pattern" binding:"parent_is_zero|either_with=Pattern,isdefault|min=3"` + Pattern string `bson:"pattern" json:"pattern" binding:"isdefault|min=3"` + NotPattern string `bson:"not_pattern" json:"not_pattern" binding:"isdefault|min=3"` CaseInsensitive bool `bson:"case_insensitive" json:"case_insensitive"` MultiLine bool `bson:"multi_line" json:"multi_line"` IgnoreWhitespaces bool `bson:"ignore_whitespaces" json:"ignore_whitespaces"` @@ -71,8 +72,8 @@ type SearchController struct { func NewSearchController(storage Storage) *SearchController { var searches []PerformedSearch - if err := storage.Find(Searches).All(&searches); err != nil { - // log.WithError(err).Panic("failed to retrieve performed searches") + if err := storage.Find(Searches).Limit(maxRecentSearches).All(&searches); err != nil { + log.WithError(err).Panic("failed to retrieve performed searches") } return &SearchController{ @@ -81,13 +82,27 @@ func NewSearchController(storage Storage) *SearchController { } } -func (sc *SearchController) PerformedSearches() []PerformedSearch { +func (sc *SearchController) GetPerformedSearches() []PerformedSearch { sc.mutex.Lock() defer sc.mutex.Unlock() return sc.performedSearches } +func (sc *SearchController) GetPerformedSearch(id RowID) PerformedSearch { + sc.mutex.Lock() + defer sc.mutex.Unlock() + + var performedSearch PerformedSearch + for _, search := range sc.performedSearches { + if search.ID == id { + performedSearch = search + } + } + + return performedSearch +} + func (sc *SearchController) PerformSearch(c context.Context, options SearchOptions) PerformedSearch { findQuery := sc.storage.Find(ConnectionStreams).Projection(OrderedDocument{{"connection_id", 1}}).Context(c) timeout := options.Timeout * secondsToNano @@ -163,6 +178,9 @@ func (sc *SearchController) PerformSearch(c context.Context, options SearchOptio sc.mutex.Lock() sc.performedSearches = append([]PerformedSearch{performedSearch}, sc.performedSearches...) + if len(sc.performedSearches) > maxRecentSearches { + sc.performedSearches = sc.performedSearches[:200] + } sc.mutex.Unlock() return performedSearch diff --git a/storage.go b/storage.go index 304e88c..8505bfe 100644 --- a/storage.go +++ b/storage.go @@ -77,7 +77,7 @@ func NewMongoStorage(uri string, port int, database string) (*MongoStorage, erro ConnectionStreams: db.Collection(ConnectionStreams), ImportingSessions: db.Collection(ImportingSessions), Rules: db.Collection(Rules), - Searches: db.Collection(Services), + Searches: db.Collection(Searches), Settings: db.Collection(Settings), Services: db.Collection(Services), Statistics: db.Collection(Statistics), -- cgit v1.2.3-70-g09d2