diff --git a/README.md b/README.md index 6d18d32..9f6ca11 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,17 @@ Events are stored in a `Redis` server with [`Redisearch`](https://github.com/Red #### Precedence: flag value -> environment variable value -> default value ```shell - -a string - Listen Address (default "0.0.0.0", environment "FALCOSIDEKICK_UI_ADDR") - -d Enable dark mode as default - -p int - Listen Port (default 2802, environment "FALCOSIDEKICK_UI_PORT") - -r string - Redis server address (default "localhost:6379", environment "FALCOSIDEKICK_UI_REDIS_URL") - -x Allow CORS for development (environment "FALCOSIDEKICK_UI_DEV") +Usage of Falcosidekick-UI: +-a string + Listen Address (default "0.0.0.0", environment "FALCOSIDEKICK_UI_ADDR") +-d Enable dark mode as default +-p int + Listen Port (default 2802, environment "FALCOSIDEKICK_UI_PORT") +-r string + Redis server address (default "localhost:6379", environment "FALCOSIDEKICK_UI_REDIS_URL") +-t int + TTL for keys (default 0, environment "FALCOSIDEKICK_UI_TTL") +-x Allow CORS for development (environment "FALCOSIDEKICK_UI_DEV") ``` ### Run with docker diff --git a/configuration/configuration.go b/configuration/configuration.go index ae0fc76..2dee02d 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -6,6 +6,7 @@ type Configuration struct { ListenPort int `json:"listen-port"` RedisServer string `json:"redis-server"` DevMode bool `json:"dev-mode"` + TTL int `json:"ttl"` } var config *Configuration diff --git a/go.mod b/go.mod index e7ec8fd..a86b6c2 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( ) require ( + github.com/Issif/redisearch-go v1.1.2-0.20220629142418-f66689e2ff5c // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect diff --git a/go.sum b/go.sum index 6ed4633..3886099 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Issif/redisearch-go v1.1.2-0.20220629142418-f66689e2ff5c h1:k6V2ixK2I6NykJMiCj+O8+2xsfBttF2QxQjlPpzPTNg= +github.com/Issif/redisearch-go v1.1.2-0.20220629142418-f66689e2ff5c/go.mod h1:UQGs8ZvEw5/QCKkFEMMiarV7yALg32yVqwB0tq0x7y8= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= diff --git a/internal/api/api.go b/internal/api/api.go index a0cf195..08973e4 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -13,7 +13,7 @@ import ( func returnResult(c echo.Context, r models.Results, err error) error { if err != nil { - utils.WriteLog("error", err.Error(), false) + utils.WriteLog("error", err.Error()) return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } if c.QueryParam("pretty") == "true" { @@ -34,7 +34,7 @@ func returnResult(c echo.Context, r models.Results, err error) error { func AddEvent(c echo.Context) error { payload := new(models.Payload) if err := c.Bind(payload); err != nil { - utils.WriteLog("error", err.Error(), false) + utils.WriteLog("error", err.Error()) err2 := echo.NewHTTPError(http.StatusInternalServerError, err.Error()) if err2 != nil { return err @@ -65,7 +65,7 @@ func AddEvent(c echo.Context) error { // @Router /events/count [get] func CountEvent(c echo.Context) error { a := models.GetArguments(c) - utils.WriteLog("info", fmt.Sprintf("GET count (source='%v', priority='%v', rule='%v', since='%v', filter='%v', tags='%v')", a.Source, a.Priority, a.Rule, a.Since, a.Filter, a.Tags), false) + utils.WriteLog("info", fmt.Sprintf("GET count (source='%v', priority='%v', rule='%v', since='%v', filter='%v', tags='%v')", a.Source, a.Priority, a.Rule, a.Since, a.Filter, a.Tags)) r, err := events.Count(a) return returnResult(c, r, err) @@ -88,7 +88,7 @@ func CountEvent(c echo.Context) error { // @Router /events/count/:groupby [get] func CountByEvent(c echo.Context) error { a := models.GetArguments(c) - utils.WriteLog("info", fmt.Sprintf("GET count by %v (source='%v', priority='%v', rule='%v', since='%v', filter='%v', tags='%v')", a.GroupBy, a.Source, a.Priority, a.Rule, a.Since, a.Filter, a.Tags), false) + utils.WriteLog("info", fmt.Sprintf("GET count by %v (source='%v', priority='%v', rule='%v', since='%v', filter='%v', tags='%v')", a.GroupBy, a.Source, a.Priority, a.Rule, a.Since, a.Filter, a.Tags)) r, err := events.CountBy(a) return returnResult(c, r, err) @@ -110,7 +110,7 @@ func CountByEvent(c echo.Context) error { // @Router /events/search [get] func Search(c echo.Context) error { a := models.GetArguments(c) - utils.WriteLog("info", fmt.Sprintf("GET search (source='%v', priority='%v', rule='%v', since='%v', filter='%v', tags='%v', page='%v', limit='%v')", a.Source, a.Priority, a.Rule, a.Since, a.Filter, a.Tags, a.Page, a.Limit), false) + utils.WriteLog("info", fmt.Sprintf("GET search (source='%v', priority='%v', rule='%v', since='%v', filter='%v', tags='%v', page='%v', limit='%v')", a.Source, a.Priority, a.Rule, a.Since, a.Filter, a.Tags, a.Page, a.Limit)) r, err := events.Search(a) return returnResult(c, r, err) @@ -137,7 +137,7 @@ func Healthz(c echo.Context) error { // @Failure 500 {string} http.StatusInternalServerError "Internal Server Error" // @Router /outputs [get] func GetOutputs(c echo.Context) error { - utils.WriteLog("info", "GET outputs", false) + utils.WriteLog("info", "GET outputs") return c.JSON(http.StatusOK, models.GetOutputs()) } @@ -149,7 +149,7 @@ func GetOutputs(c echo.Context) error { // @Failure 500 {string} http.StatusInternalServerError "Internal Server Error" // @Router /configuration [get] func GetConfiguration(c echo.Context) error { - utils.WriteLog("info", "GET config", false) + utils.WriteLog("info", "GET config") return c.JSON(http.StatusOK, configuration.GetConfiguration()) } @@ -161,6 +161,6 @@ func GetConfiguration(c echo.Context) error { // @Failure 500 {string} http.StatusInternalServerError "Internal Server Error" // @Router /version [get] func GetVersionInfo(c echo.Context) error { - utils.WriteLog("info", "GET version", false) + utils.WriteLog("info", "GET version") return c.JSON(http.StatusOK, configuration.GetVersionInfo()) } diff --git a/internal/broadcast/broadcast.go b/internal/broadcast/broadcast.go index 8766d61..8e547a0 100644 --- a/internal/broadcast/broadcast.go +++ b/internal/broadcast/broadcast.go @@ -26,7 +26,7 @@ func (b *Broadcast) BroadcastMessage() { } func WebSocketBroadcast(c echo.Context) error { - utils.WriteLog("info", "New WebSocket Connection", false) + utils.WriteLog("info", "New WebSocket Connection") websocket.Handler(func(ws *websocket.Conn) { defer ws.Close() b := GetBroadcast() diff --git a/internal/database/redis/client.go b/internal/database/redis/client.go index a82641f..999b68c 100644 --- a/internal/database/redis/client.go +++ b/internal/database/redis/client.go @@ -3,7 +3,7 @@ package redis import ( "github.com/falcosecurity/falcosidekick-ui/configuration" - "github.com/RediSearch/redisearch-go/redisearch" + "github.com/Issif/redisearch-go/redisearch" ) var client *redisearch.Client diff --git a/internal/database/redis/count.go b/internal/database/redis/count.go index 76a57c0..4380f6a 100644 --- a/internal/database/redis/count.go +++ b/internal/database/redis/count.go @@ -2,16 +2,16 @@ package redis import ( "errors" + "fmt" "strconv" - "strings" - "github.com/RediSearch/redisearch-go/redisearch" + "github.com/Issif/redisearch-go/redisearch" "github.com/falcosecurity/falcosidekick-ui/internal/models" ) func CountKey(client *redisearch.Client, args *models.Arguments) (models.Results, error) { query := redisearch.NewQuery(newQuery(args)) - _, count, err := client.Aggregate(redisearch.NewAggregateQuery().SetQuery(query)) + count, _, err := client.AggregateQuery(redisearch.NewAggregateQuery().SetQuery(query)) if err != nil { return models.Results{}, err } @@ -31,24 +31,20 @@ func CountKeyBy(client *redisearch.Client, args *models.Arguments) (models.Resul groupBy := redisearch.NewGroupBy().AddFields("@" + args.GroupBy).Reduce(*reducer) query := redisearch.NewQuery(newQuery(args)) - results, _, err := client.Aggregate(redisearch.NewAggregateQuery().SetQuery(query).GroupBy(*groupBy)) + _, results, err := client.AggregateQuery(redisearch.NewAggregateQuery().SetQuery(query).GroupBy(*groupBy)) if err != nil { return models.Results{}, err } + fmt.Printf("%#v\n", results) + ag := make(models.Aggregation) var all int64 for _, i := range results { - key := i[1] - if key == "" { - continue - } - keys := strings.Split(key, ",") - for _, j := range keys { - count, _ := strconv.ParseInt(i[3], 10, 64) - ag[j] += count - all += count - } + key := i[args.GroupBy] + count, _ := strconv.ParseInt(i["__generated_aliascount"].(string), 10, 64) + ag[key.(string)] += count + all += count } return models.Results{ diff --git a/internal/database/redis/index.go b/internal/database/redis/index.go index 2d5c6b5..1f27e4b 100644 --- a/internal/database/redis/index.go +++ b/internal/database/redis/index.go @@ -1,14 +1,14 @@ package redis import ( - "github.com/RediSearch/redisearch-go/redisearch" + "github.com/Issif/redisearch-go/redisearch" utils "github.com/falcosecurity/falcosidekick-ui/internal/utils" ) func isIndexExit(client *redisearch.Client) bool { _, err := client.Info() if err != nil { - utils.WriteLog("info", "Index does not exist", false) + utils.WriteLog("info", "Index does not exist") return false } return true @@ -33,7 +33,7 @@ func CreateIndex(client *redisearch.Client) { // client.Drop() // Create the index with the given schema - utils.WriteLog("info", "Create Index", false) + utils.WriteLog("info", "Create Index") err := client.CreateIndex(schema) utils.CheckErr(err) } diff --git a/internal/database/redis/search.go b/internal/database/redis/search.go index f7e4644..b885d23 100644 --- a/internal/database/redis/search.go +++ b/internal/database/redis/search.go @@ -3,7 +3,7 @@ package redis import ( "encoding/json" - "github.com/RediSearch/redisearch-go/redisearch" + "github.com/Issif/redisearch-go/redisearch" "github.com/falcosecurity/falcosidekick-ui/internal/models" ) diff --git a/internal/database/redis/set.go b/internal/database/redis/set.go index b76b773..2e55d1a 100644 --- a/internal/database/redis/set.go +++ b/internal/database/redis/set.go @@ -5,23 +5,26 @@ import ( "fmt" "strings" - "github.com/RediSearch/redisearch-go/redisearch" + "github.com/Issif/redisearch-go/redisearch" + "github.com/falcosecurity/falcosidekick-ui/configuration" "github.com/falcosecurity/falcosidekick-ui/internal/models" ) func SetKey(client *redisearch.Client, event *models.Event) error { + c := configuration.GetConfiguration() timestamp := event.Time.UnixNano() / 1e3 jsonString, _ := json.Marshal(event) - doc := redisearch.NewDocument(fmt.Sprintf("event:%v", timestamp), 1.0) - doc.Set("rule", event.Rule). + doc := redisearch.NewDocument(fmt.Sprintf("event:%v", timestamp), 1.0). + Set("rule", event.Rule). Set("priority", event.Priority). Set("output", event.Output). Set("source", event.Source). Set("timestamp", timestamp). Set("tags", strings.Join(event.Tags, ",")). - Set("json", string(jsonString)) + Set("json", string(jsonString)). + SetTTL(c.TTL) err := client.Index([]redisearch.Document{doc}...) return err diff --git a/internal/events/add.go b/internal/events/add.go index 97c969d..312303c 100644 --- a/internal/events/add.go +++ b/internal/events/add.go @@ -14,11 +14,11 @@ import ( func Add(e *models.Event) error { client := redis.GetClient() if err := redis.SetKey(client, e); err != nil { - utils.WriteLog("error", err.Error(), false) + utils.WriteLog("error", err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - utils.WriteLog("info", fmt.Sprintf("NEW event 'event:%v'", e.Time.UnixNano()/1e3), false) + utils.WriteLog("info", fmt.Sprintf("NEW event 'event:%v'", e.Time.UnixNano()/1e3)) go broadcast.GetBroadcast().BroadcastMessage() diff --git a/internal/events/count.go b/internal/events/count.go index 3e10798..14bad31 100644 --- a/internal/events/count.go +++ b/internal/events/count.go @@ -13,7 +13,7 @@ func Count(a *models.Arguments) (models.Results, error) { c := redis.GetClient() r, err := redis.CountKey(c, a) if err != nil { - utils.WriteLog("error", err.Error(), false) + utils.WriteLog("error", err.Error()) return models.Results{}, echo.NewHTTPError(http.StatusBadRequest, err.Error()) } return r, nil @@ -23,7 +23,7 @@ func CountBy(a *models.Arguments) (models.Results, error) { c := redis.GetClient() r, err := redis.CountKeyBy(c, a) if err != nil { - utils.WriteLog("error", err.Error(), false) + utils.WriteLog("error", err.Error()) return models.Results{}, echo.NewHTTPError(http.StatusBadRequest, err.Error()) } return r, nil diff --git a/internal/events/search.go b/internal/events/search.go index 104dac7..69a927d 100644 --- a/internal/events/search.go +++ b/internal/events/search.go @@ -10,7 +10,7 @@ func Search(a *models.Arguments) (models.Results, error) { client := redis.GetClient() results, err := redis.SearchKey(client, a) if err != nil { - utils.WriteLog("error", err.Error(), false) + utils.WriteLog("error", err.Error()) return models.Results{}, err } return results, nil diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 5ffe6b7..00ede41 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -28,15 +28,15 @@ func CheckErr(e error) { } } -func WriteLog(level, message string, fatal bool) { +func WriteLog(level, message string) { var prefix string switch level { - case "error": + case "error", "fatal": prefix = "[ERROR]:" case "info": prefix = "[INFO] :" } - if fatal { + if level == "fatal" { log.Fatalf(fmt.Sprintf("%v %v\n", prefix, message)) } log.Printf("%v %v", prefix, message) @@ -80,15 +80,21 @@ func RemoveDuplicate(input []string) []string { return singleKeys } -func GetFlagOrEnvParam(flagString string, envVar string, defaultValue string, usage string) *string { - res := flag.String(flagString, "NON-SET", usage) - if *res == "NON-SET" { - str, present := os.LookupEnv(envVar) - if present { - *res = str - } else { - *res = defaultValue +func GetStringFlagOrEnvParam(flagString string, envVar string, defaultValue string, usage string) *string { + envvar, present := os.LookupEnv(envVar) + if present { + defaultValue = envvar + } + return flag.String(flagString, defaultValue, usage) +} + +func GetIntFlagOrEnvParam(flagString string, envVar string, defaultValue int, usage string) *int { + envvar, present := os.LookupEnv(envVar) + if present { + val, err := strconv.Atoi(envvar) + if err == nil { + defaultValue = val } } - return res + return flag.Int(flagString, defaultValue, usage) } diff --git a/main.go b/main.go index 0a83576..a730c09 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "net" "net/http" "os" - "strconv" "github.com/falcosecurity/falcosidekick-ui/configuration" "github.com/falcosecurity/falcosidekick-ui/internal/api" @@ -35,20 +34,33 @@ type CustomValidator struct { } func init() { - addr := utils.GetFlagOrEnvParam("a", "FALCOSIDEKICK_UI_ADDR", "0.0.0.0", "Listen Address") - portString := utils.GetFlagOrEnvParam("p", "FALCOSIDEKICK_UI_PORT", "2802", "Listen Port") - port, err := strconv.Atoi(*portString) - if err != nil { - utils.WriteLog("error", "Failed to parse Listen Port", true) - } - redisserver := utils.GetFlagOrEnvParam("r", "FALCOSIDEKICK_UI_REDIS_URL", "localhost:6379", "Redis server address") + addr := utils.GetStringFlagOrEnvParam("a", "FALCOSIDEKICK_UI_ADDR", "0.0.0.0", "Listen Address") + redisserver := utils.GetStringFlagOrEnvParam("r", "FALCOSIDEKICK_UI_REDIS_URL", "localhost:6379", "Redis server address") + port := utils.GetIntFlagOrEnvParam("p", "FALCOSIDEKICK_UI_PORT", 2802, "Listen Port") + ttl := utils.GetIntFlagOrEnvParam("t", "FALCOSIDEKICK_UI_TTL", 0, "TTL for keys") + version := flag.Bool("v", false, "Print version") dev := flag.Bool("x", false, "Allow CORS for development") if !*dev { _, *dev = os.LookupEnv("FALCOSIDEKICK_UI_DEV") } + flag.Usage = func() { + help := `Usage of Falcosidekick-UI: +-a string + Listen Address (default "0.0.0.0", environment "FALCOSIDEKICK_UI_ADDR") +-d Enable dark mode as default +-p int + Listen Port (default "2802", environment "FALCOSIDEKICK_UI_PORT") +-r string + Redis server address (default "localhost:6379", environment "FALCOSIDEKICK_UI_REDIS_URL") +-t int + TTL for keys (default "0", environment "FALCOSIDEKICK_UI_TTL") +-x Allow CORS for development (environment "FALCOSIDEKICK_UI_DEV") +` + fmt.Println(help) + } + // darkmod := flag.Bool("d", false, "Enable dark mode as default") - version := flag.Bool("v", false, "Print version") flag.Parse() if *version { @@ -60,16 +72,17 @@ func init() { configuration.CreateConfiguration() config := configuration.GetConfiguration() if ip := net.ParseIP(*addr); ip == nil { - utils.WriteLog("error", "Failed to parse Listen Address", true) + utils.WriteLog("fatal", "Failed to parse Listen Address") } config.ListenAddress = *addr - config.ListenPort = port + config.ListenPort = *port // config.DisplayMode = light // if *darkmod { // config.DisplayMode = dark // } config.RedisServer = *redisserver config.DevMode = *dev + config.TTL = *ttl redis.CreateClient() redis.CreateIndex(redis.GetClient()) @@ -103,7 +116,7 @@ func main() { e.Validator = v if c.DevMode { - utils.WriteLog("info", "DEV mode enabled", false) + utils.WriteLog("info", "DEV mode enabled") e.Use(middleware.CORS()) } @@ -115,6 +128,7 @@ func main() { return nil }) e.POST("/", api.AddEvent).Name = "add-event" + e.GET("/ws", broadcast.WebSocketBroadcast).Name = "websocket" // e.Use(middleware.BodyDump(func(c echo.Context, reqBody, resBody []byte) { // })) @@ -125,7 +139,6 @@ func main() { apiRoute.GET("/healthz", api.Healthz).Name = "healthz" apiRoute.GET("/outputs", api.GetOutputs).Name = "list-outputs" apiRoute.GET("/config", api.GetConfiguration).Name = "display-cnfig" - apiRoute.GET("/ws", broadcast.WebSocketBroadcast).Name = "websocket" eventsRoute := apiRoute.Group("/events") eventsRoute.POST("/add", api.AddEvent).Name = "add-event"