diff --git a/go.mod b/go.mod index 159ec2e11..549117e5e 100644 --- a/go.mod +++ b/go.mod @@ -59,6 +59,7 @@ require ( github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect + github.com/andygrunwald/go-jira v1.16.0 // indirect github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 // indirect github.com/apache/thrift v0.20.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect @@ -69,6 +70,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/emicklei/go-restful/v3 v3.12.0 // indirect + github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -77,6 +79,7 @@ require ( github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -111,6 +114,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.53.0 // indirect @@ -122,6 +126,7 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/trivago/tgo v1.0.7 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect @@ -145,6 +150,8 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b // indirect google.golang.org/grpc v1.64.1 // indirect google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 2a2028433..fc79b287c 100644 --- a/go.sum +++ b/go.sum @@ -166,6 +166,8 @@ github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjH github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4L0zgAOR8lTQK9VlyBVVd7G4omaOQs= github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/andygrunwald/go-jira v1.16.0 h1:PU7C7Fkk5L96JvPc6vDVIrd99vdPnYudHu4ju2c2ikQ= +github.com/andygrunwald/go-jira v1.16.0/go.mod h1:UQH4IBVxIYWbgagc0LF/k9FRs9xjIiQ8hIcC6HfLwFU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/arrow v0.0.0-20200730104253-651201b0f516/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0= github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 h1:q4dksr6ICHXqG5hm0ZW5IHyeEJXoIJSOZeBLmWPNeIQ= @@ -304,6 +306,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= @@ -359,11 +363,14 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= @@ -701,6 +708,7 @@ github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzL github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -778,6 +786,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM= +github.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= @@ -1127,6 +1137,7 @@ golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -1438,11 +1449,15 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= diff --git a/handlers.go b/handlers.go deleted file mode 100644 index 326087fd3..000000000 --- a/handlers.go +++ /dev/null @@ -1,478 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 - -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "sort" - "strings" - "text/template" - "time" - - "github.com/falcosecurity/falcosidekick/types" - "github.com/google/uuid" - "golang.org/x/text/cases" - "golang.org/x/text/language" -) - -const ( - testRule string = "Test rule" - syscalls string = "syscalls" - syscall string = "syscall" -) - -// mainHandler is Falcosidekick main handler (default). -func mainHandler(w http.ResponseWriter, r *http.Request) { - stats.Requests.Add("total", 1) - nullClient.CountMetric("total", 1, []string{}) - - if r.Body == nil { - http.Error(w, "Please send a valid request body", http.StatusBadRequest) - stats.Requests.Add("rejected", 1) - promStats.Inputs.With(map[string]string{"source": "requests", "status": "rejected"}).Inc() - nullClient.CountMetric("inputs.requests.rejected", 1, []string{"error:nobody"}) - - return - } - - if r.Method != http.MethodPost { - http.Error(w, "Please send with post http method", http.StatusBadRequest) - stats.Requests.Add("rejected", 1) - promStats.Inputs.With(map[string]string{"source": "requests", "status": "rejected"}).Inc() - nullClient.CountMetric("inputs.requests.rejected", 1, []string{"error:nobody"}) - - return - } - - falcopayload, err := newFalcoPayload(r.Body) - if err != nil || !falcopayload.Check() { - http.Error(w, "Please send a valid request body", http.StatusBadRequest) - stats.Requests.Add("rejected", 1) - promStats.Inputs.With(map[string]string{"source": "requests", "status": "rejected"}).Inc() - nullClient.CountMetric("inputs.requests.rejected", 1, []string{"error:invalidjson"}) - - return - } - - nullClient.CountMetric("inputs.requests.accepted", 1, []string{}) - stats.Requests.Add("accepted", 1) - promStats.Inputs.With(map[string]string{"source": "requests", "status": "accepted"}).Inc() - forwardEvent(falcopayload) -} - -// pingHandler is a simple handler to test if daemon is UP. -func pingHandler(w http.ResponseWriter, r *http.Request) { - // #nosec G104 nothing to be done if the following fails - w.Write([]byte("pong\n")) -} - -// healthHandler is a simple handler to test if daemon is UP. -func healthHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Content-Type", "application/json") - // #nosec G104 nothing to be done if the following fails - w.Write([]byte(`{"status": "ok"}`)) -} - -// testHandler sends a test event to all enabled outputs. -func testHandler(w http.ResponseWriter, r *http.Request) { - r.Body = io.NopCloser(bytes.NewReader([]byte(`{"output":"This is a test from falcosidekick","priority":"Debug","hostname": "falcosidekick", "rule":"Test rule", "time":"` + time.Now().UTC().Format(time.RFC3339) + `","output_fields": {"proc.name":"falcosidekick","user.name":"falcosidekick"}, "tags":["test","example"]}`))) - mainHandler(w, r) -} - -func newFalcoPayload(payload io.Reader) (types.FalcoPayload, error) { - var falcopayload types.FalcoPayload - - d := json.NewDecoder(payload) - d.UseNumber() - - err := d.Decode(&falcopayload) - if err != nil { - return types.FalcoPayload{}, err - } - - var customFields string - if len(config.Customfields) > 0 { - if falcopayload.OutputFields == nil { - falcopayload.OutputFields = make(map[string]interface{}) - } - for key, value := range config.Customfields { - customFields += key + "=" + value + " " - falcopayload.OutputFields[key] = value - } - } - - if falcopayload.Rule == "Test rule" { - falcopayload.Source = "internal" - } - - if falcopayload.Source == "" { - falcopayload.Source = syscalls - } - - falcopayload.UUID = uuid.New().String() - - var kn, kp string - for i, j := range falcopayload.OutputFields { - if j != nil { - if i == "k8s.ns.name" { - kn = j.(string) - } - if i == "k8s.pod.name" { - kp = j.(string) - } - } - } - - var templatedFields string - if len(config.Templatedfields) > 0 { - if falcopayload.OutputFields == nil { - falcopayload.OutputFields = make(map[string]interface{}) - } - for key, value := range config.Templatedfields { - tmpl, err := template.New("").Parse(value) - if err != nil { - log.Printf("[ERROR] : Parsing error for templated field '%v': %v\n", key, err) - continue - } - v := new(bytes.Buffer) - if err := tmpl.Execute(v, falcopayload.OutputFields); err != nil { - log.Printf("[ERROR] : Parsing error for templated field '%v': %v\n", key, err) - } - templatedFields += key + "=" + v.String() + " " - falcopayload.OutputFields[key] = v.String() - } - } - - if len(falcopayload.Tags) != 0 { - sort.Strings(falcopayload.Tags) - } - - nullClient.CountMetric("falco.accepted", 1, []string{"priority:" + falcopayload.Priority.String()}) - stats.Falco.Add(strings.ToLower(falcopayload.Priority.String()), 1) - promLabels := map[string]string{"rule": falcopayload.Rule, "priority": falcopayload.Priority.String(), "source": falcopayload.Source, "k8s_ns_name": kn, "k8s_pod_name": kp} - if falcopayload.Hostname != "" { - promLabels["hostname"] = falcopayload.Hostname - } else { - promLabels["hostname"] = "unknown" - } - - for key, value := range config.Customfields { - if regPromLabels.MatchString(key) { - promLabels[key] = value - } - } - for _, i := range config.Prometheus.ExtraLabelsList { - promLabels[strings.ReplaceAll(i, ".", "_")] = "" - for key, value := range falcopayload.OutputFields { - if key == i && regPromLabels.MatchString(strings.ReplaceAll(key, ".", "_")) { - switch value.(type) { - case string: - promLabels[strings.ReplaceAll(key, ".", "_")] = fmt.Sprintf("%v", value) - default: - continue - } - } - } - } - promStats.Falco.With(promLabels).Inc() - - if config.BracketReplacer != "" { - for i, j := range falcopayload.OutputFields { - if strings.Contains(i, "[") { - falcopayload.OutputFields[strings.ReplaceAll(strings.ReplaceAll(i, "]", ""), "[", config.BracketReplacer)] = j - delete(falcopayload.OutputFields, i) - } - } - } - - if config.OutputFieldFormat != "" && regOutputFormat.MatchString(falcopayload.Output) { - outputElements := strings.Split(falcopayload.Output, " ") - if len(outputElements) >= 3 { - t := strings.TrimSuffix(outputElements[0], ":") - p := cases.Title(language.English).String(falcopayload.Priority.String()) - o := strings.Join(outputElements[2:], " ") - n := config.OutputFieldFormat - n = strings.ReplaceAll(n, "", t) - n = strings.ReplaceAll(n, "", p) - n = strings.ReplaceAll(n, "", o) - n = strings.ReplaceAll(n, "", strings.TrimSuffix(customFields, " ")) - n = strings.ReplaceAll(n, "", strings.TrimSuffix(templatedFields, " ")) - n = strings.TrimSuffix(n, " ") - n = strings.TrimSuffix(n, "( )") - n = strings.TrimSuffix(n, "()") - n = strings.TrimSuffix(n, " ") - falcopayload.Output = n - } - } - - if len(falcopayload.String()) > 4096 { - for i, j := range falcopayload.OutputFields { - switch j.(type) { - case string: - if len(j.(string)) > 512 { - k := j.(string)[:507] + "[...]" - falcopayload.Output = strings.ReplaceAll(falcopayload.Output, j.(string), k) - falcopayload.OutputFields[i] = k - } - } - } - } - - if config.Debug { - log.Printf("[DEBUG] : Falco's payload : %v\n", falcopayload.String()) - } - - return falcopayload, nil -} - -func forwardEvent(falcopayload types.FalcoPayload) { - if config.Slack.WebhookURL != "" && (falcopayload.Priority >= types.Priority(config.Slack.MinimumPriority) || falcopayload.Rule == testRule) { - go slackClient.SlackPost(falcopayload) - } - - if config.Cliq.WebhookURL != "" && (falcopayload.Priority >= types.Priority(config.Cliq.MinimumPriority) || falcopayload.Rule == testRule) { - go cliqClient.CliqPost(falcopayload) - } - - if config.Rocketchat.WebhookURL != "" && (falcopayload.Priority >= types.Priority(config.Rocketchat.MinimumPriority) || falcopayload.Rule == testRule) { - go rocketchatClient.RocketchatPost(falcopayload) - } - - if config.Mattermost.WebhookURL != "" && (falcopayload.Priority >= types.Priority(config.Mattermost.MinimumPriority) || falcopayload.Rule == testRule) { - go mattermostClient.MattermostPost(falcopayload) - } - - if config.Teams.WebhookURL != "" && (falcopayload.Priority >= types.Priority(config.Teams.MinimumPriority) || falcopayload.Rule == testRule) { - go teamsClient.TeamsPost(falcopayload) - } - - if config.Datadog.APIKey != "" && (falcopayload.Priority >= types.Priority(config.Datadog.MinimumPriority) || falcopayload.Rule == testRule) { - go datadogClient.DatadogPost(falcopayload) - } - - if config.Discord.WebhookURL != "" && (falcopayload.Priority >= types.Priority(config.Discord.MinimumPriority) || falcopayload.Rule == testRule) { - go discordClient.DiscordPost(falcopayload) - } - - if config.Alertmanager.HostPort != "" && (falcopayload.Priority >= types.Priority(config.Alertmanager.MinimumPriority) || falcopayload.Rule == testRule) { - go alertmanagerClient.AlertmanagerPost(falcopayload) - } - - if config.Elasticsearch.HostPort != "" && (falcopayload.Priority >= types.Priority(config.Elasticsearch.MinimumPriority) || falcopayload.Rule == testRule) { - go elasticsearchClient.ElasticsearchPost(falcopayload) - } - - if config.Quickwit.HostPort != "" && (falcopayload.Priority >= types.Priority(config.Quickwit.MinimumPriority) || falcopayload.Rule == testRule) { - go quickwitClient.QuickwitPost(falcopayload) - } - - if config.Influxdb.HostPort != "" && (falcopayload.Priority >= types.Priority(config.Influxdb.MinimumPriority) || falcopayload.Rule == testRule) { - go influxdbClient.InfluxdbPost(falcopayload) - } - - if config.Loki.HostPort != "" && (falcopayload.Priority >= types.Priority(config.Loki.MinimumPriority) || falcopayload.Rule == testRule) { - go lokiClient.LokiPost(falcopayload) - } - - if config.SumoLogic.ReceiverURL != "" && (falcopayload.Priority >= types.Priority(config.SumoLogic.MinimumPriority) || falcopayload.Rule == testRule) { - go sumologicClient.SumoLogicPost(falcopayload) - } - - if config.Nats.HostPort != "" && (falcopayload.Priority >= types.Priority(config.Nats.MinimumPriority) || falcopayload.Rule == testRule) { - go natsClient.NatsPublish(falcopayload) - } - - if config.Stan.HostPort != "" && config.Stan.ClusterID != "" && config.Stan.ClientID != "" && (falcopayload.Priority >= types.Priority(config.Stan.MinimumPriority) || falcopayload.Rule == testRule) { - go stanClient.StanPublish(falcopayload) - } - - if config.AWS.Lambda.FunctionName != "" && (falcopayload.Priority >= types.Priority(config.AWS.Lambda.MinimumPriority) || falcopayload.Rule == testRule) { - go awsClient.InvokeLambda(falcopayload) - } - - if config.AWS.SQS.URL != "" && (falcopayload.Priority >= types.Priority(config.AWS.SQS.MinimumPriority) || falcopayload.Rule == testRule) { - go awsClient.SendMessage(falcopayload) - } - - if config.AWS.SNS.TopicArn != "" && (falcopayload.Priority >= types.Priority(config.AWS.SNS.MinimumPriority) || falcopayload.Rule == testRule) { - go awsClient.PublishTopic(falcopayload) - } - - if config.AWS.CloudWatchLogs.LogGroup != "" && (falcopayload.Priority >= types.Priority(config.AWS.CloudWatchLogs.MinimumPriority) || falcopayload.Rule == testRule) { - go awsClient.SendCloudWatchLog(falcopayload) - } - - if config.AWS.S3.Bucket != "" && (falcopayload.Priority >= types.Priority(config.AWS.S3.MinimumPriority) || falcopayload.Rule == testRule) { - go awsClient.UploadS3(falcopayload) - } - - if (config.AWS.SecurityLake.Bucket != "" && config.AWS.SecurityLake.Region != "" && config.AWS.SecurityLake.AccountID != "" && config.AWS.SecurityLake.Prefix != "") && (falcopayload.Priority >= types.Priority(config.AWS.SecurityLake.MinimumPriority) || falcopayload.Rule == testRule) { - go awsClient.EnqueueSecurityLake(falcopayload) - } - - if config.AWS.Kinesis.StreamName != "" && (falcopayload.Priority >= types.Priority(config.AWS.Kinesis.MinimumPriority) || falcopayload.Rule == testRule) { - go awsClient.PutRecord(falcopayload) - } - - if config.SMTP.HostPort != "" && (falcopayload.Priority >= types.Priority(config.SMTP.MinimumPriority) || falcopayload.Rule == testRule) { - go smtpClient.SendMail(falcopayload) - } - - if config.Opsgenie.APIKey != "" && (falcopayload.Priority >= types.Priority(config.Opsgenie.MinimumPriority) || falcopayload.Rule == testRule) { - go opsgenieClient.OpsgeniePost(falcopayload) - } - - if config.Webhook.Address != "" && (falcopayload.Priority >= types.Priority(config.Webhook.MinimumPriority) || falcopayload.Rule == testRule) { - go webhookClient.WebhookPost(falcopayload) - } - - if config.NodeRed.Address != "" && (falcopayload.Priority >= types.Priority(config.NodeRed.MinimumPriority) || falcopayload.Rule == testRule) { - go noderedClient.NodeRedPost(falcopayload) - } - - if config.CloudEvents.Address != "" && (falcopayload.Priority >= types.Priority(config.CloudEvents.MinimumPriority) || falcopayload.Rule == testRule) { - go cloudeventsClient.CloudEventsSend(falcopayload) - } - - if config.Azure.EventHub.Name != "" && (falcopayload.Priority >= types.Priority(config.Azure.EventHub.MinimumPriority) || falcopayload.Rule == testRule) { - go azureClient.EventHubPost(falcopayload) - } - - if config.GCP.PubSub.ProjectID != "" && config.GCP.PubSub.Topic != "" && (falcopayload.Priority >= types.Priority(config.GCP.PubSub.MinimumPriority) || falcopayload.Rule == testRule) { - go gcpClient.GCPPublishTopic(falcopayload) - } - - if config.GCP.CloudFunctions.Name != "" && (falcopayload.Priority >= types.Priority(config.GCP.CloudFunctions.MinimumPriority) || falcopayload.Rule == testRule) { - go gcpClient.GCPCallCloudFunction(falcopayload) - } - - if config.GCP.CloudRun.Endpoint != "" && (falcopayload.Priority >= types.Priority(config.GCP.CloudRun.MinimumPriority) || falcopayload.Rule == testRule) { - go gcpCloudRunClient.CloudRunFunctionPost(falcopayload) - } - - if config.GCP.Storage.Bucket != "" && (falcopayload.Priority >= types.Priority(config.GCP.Storage.MinimumPriority) || falcopayload.Rule == testRule) { - go gcpClient.UploadGCS(falcopayload) - } - - if config.Googlechat.WebhookURL != "" && (falcopayload.Priority >= types.Priority(config.Googlechat.MinimumPriority) || falcopayload.Rule == testRule) { - go googleChatClient.GooglechatPost(falcopayload) - } - - if config.Kafka.HostPort != "" && (falcopayload.Priority >= types.Priority(config.Kafka.MinimumPriority) || falcopayload.Rule == testRule) { - go kafkaClient.KafkaProduce(falcopayload) - } - - if config.KafkaRest.Address != "" && (falcopayload.Priority >= types.Priority(config.KafkaRest.MinimumPriority) || falcopayload.Rule == testRule) { - go kafkaRestClient.KafkaRestPost(falcopayload) - } - - if config.Pagerduty.RoutingKey != "" && (falcopayload.Priority >= types.Priority(config.Pagerduty.MinimumPriority) || falcopayload.Rule == testRule) { - go pagerdutyClient.PagerdutyPost(falcopayload) - } - - if config.Kubeless.Namespace != "" && config.Kubeless.Function != "" && (falcopayload.Priority >= types.Priority(config.Kubeless.MinimumPriority) || falcopayload.Rule == testRule) { - go kubelessClient.KubelessCall(falcopayload) - } - - if config.Openfaas.FunctionName != "" && (falcopayload.Priority >= types.Priority(config.Openfaas.MinimumPriority) || falcopayload.Rule == testRule) { - go openfaasClient.OpenfaasCall(falcopayload) - } - - if config.Tekton.EventListener != "" && (falcopayload.Priority >= types.Priority(config.Tekton.MinimumPriority) || falcopayload.Rule == testRule) { - go tektonClient.TektonPost(falcopayload) - } - - if config.Rabbitmq.URL != "" && config.Rabbitmq.Queue != "" && (falcopayload.Priority >= types.Priority(config.Openfaas.MinimumPriority) || falcopayload.Rule == testRule) { - go rabbitmqClient.Publish(falcopayload) - } - - if config.Wavefront.EndpointHost != "" && config.Wavefront.EndpointType != "" && (falcopayload.Priority >= types.Priority(config.Wavefront.MinimumPriority) || falcopayload.Rule == testRule) { - go wavefrontClient.WavefrontPost(falcopayload) - } - - if config.Grafana.HostPort != "" && (falcopayload.Priority >= types.Priority(config.Grafana.MinimumPriority) || falcopayload.Rule == testRule) { - go grafanaClient.GrafanaPost(falcopayload) - } - - if config.GrafanaOnCall.WebhookURL != "" && (falcopayload.Priority >= types.Priority(config.GrafanaOnCall.MinimumPriority) || falcopayload.Rule == testRule) { - go grafanaOnCallClient.GrafanaOnCallPost(falcopayload) - } - - if config.WebUI.URL != "" { - go webUIClient.WebUIPost(falcopayload) - } - - if config.Fission.Function != "" && (falcopayload.Priority >= types.Priority(config.Fission.MinimumPriority) || falcopayload.Rule == testRule) { - go fissionClient.FissionCall(falcopayload) - } - if config.PolicyReport.Enabled && (falcopayload.Priority >= types.Priority(config.PolicyReport.MinimumPriority)) { - if falcopayload.Source == syscalls || falcopayload.Source == syscall || falcopayload.Source == "k8saudit" { - go policyReportClient.UpdateOrCreatePolicyReport(falcopayload) - } - } - - if config.Yandex.S3.Bucket != "" && (falcopayload.Priority >= types.Priority(config.Yandex.S3.MinimumPriority) || falcopayload.Rule == testRule) { - go yandexClient.UploadYandexS3(falcopayload) - } - - if config.Yandex.DataStreams.StreamName != "" && (falcopayload.Priority >= types.Priority(config.Yandex.DataStreams.MinimumPriority) || falcopayload.Rule == testRule) { - go yandexClient.UploadYandexDataStreams(falcopayload) - } - - if config.Syslog.Host != "" && (falcopayload.Priority >= types.Priority(config.Syslog.MinimumPriority) || falcopayload.Rule == testRule) { - go syslogClient.SyslogPost(falcopayload) - } - - if config.MQTT.Broker != "" && (falcopayload.Priority >= types.Priority(config.MQTT.MinimumPriority) || falcopayload.Rule == testRule) { - go mqttClient.MQTTPublish(falcopayload) - } - - if config.Zincsearch.HostPort != "" && (falcopayload.Priority >= types.Priority(config.Zincsearch.MinimumPriority) || falcopayload.Rule == testRule) { - go zincsearchClient.ZincsearchPost(falcopayload) - } - - if config.Gotify.HostPort != "" && (falcopayload.Priority >= types.Priority(config.Gotify.MinimumPriority) || falcopayload.Rule == testRule) { - go gotifyClient.GotifyPost(falcopayload) - } - - if config.Spyderbat.OrgUID != "" && (falcopayload.Priority >= types.Priority(config.Spyderbat.MinimumPriority) || falcopayload.Rule == testRule) { - go spyderbatClient.SpyderbatPost(falcopayload) - } - - if config.TimescaleDB.Host != "" && (falcopayload.Priority >= types.Priority(config.TimescaleDB.MinimumPriority) || falcopayload.Rule == testRule) { - go timescaleDBClient.TimescaleDBPost(falcopayload) - } - - if config.Redis.Address != "" && (falcopayload.Priority >= types.Priority(config.Redis.MinimumPriority) || falcopayload.Rule == testRule) { - go redisClient.RedisPost(falcopayload) - } - - if config.Telegram.ChatID != "" && config.Telegram.Token != "" && (falcopayload.Priority >= types.Priority(config.Telegram.MinimumPriority) || falcopayload.Rule == testRule) { - go telegramClient.TelegramPost(falcopayload) - } - - if config.N8N.Address != "" && (falcopayload.Priority >= types.Priority(config.N8N.MinimumPriority) || falcopayload.Rule == testRule) { - go n8nClient.N8NPost(falcopayload) - } - - if config.OpenObserve.HostPort != "" && (falcopayload.Priority >= types.Priority(config.OpenObserve.MinimumPriority) || falcopayload.Rule == testRule) { - go openObserveClient.OpenObservePost(falcopayload) - } - - if config.Dynatrace.APIToken != "" && config.Dynatrace.APIUrl != "" && (falcopayload.Priority >= types.Priority(config.Dynatrace.MinimumPriority) || falcopayload.Rule == testRule) { - go dynatraceClient.DynatracePost(falcopayload) - } - - if config.OTLP.Traces.Endpoint != "" && (falcopayload.Priority >= types.Priority(config.OTLP.Traces.MinimumPriority)) && (falcopayload.Source == syscall || falcopayload.Source == syscalls) { - go otlpClient.OTLPTracesPost(falcopayload) - } - - if config.Talon.Address != "" && (falcopayload.Priority >= types.Priority(config.Talon.MinimumPriority) || falcopayload.Rule == testRule) { - go talonClient.TalonPost(falcopayload) - } -} diff --git a/main.go b/main.go index dc0342465..5b59d1e0f 100644 --- a/main.go +++ b/main.go @@ -18,8 +18,6 @@ import ( "github.com/DataDog/datadog-go/statsd" "github.com/embano1/memlog" - "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/falcosecurity/falcosidekick/outputs" "github.com/falcosecurity/falcosidekick/types" ) @@ -373,7 +371,7 @@ func init() { if config.AWS.SecurityLake.Interval < 5 { config.AWS.SecurityLake.Interval = 5 } - go awsClient.StartSecurityLakeWorker() + // go awsClient.StartSecurityLakeWorker() if err != nil { config.AWS.SecurityLake.Region = "" config.AWS.SecurityLake.Bucket = "" @@ -675,16 +673,16 @@ func init() { } } - if config.MQTT.Broker != "" { - var err error - mqttClient, err = outputs.NewMQTTClient(config, stats, promStats, statsdClient, dogstatsdClient) - if err != nil { - config.MQTT.Broker = "" - log.Printf("[ERROR] : MQTT - %v\n", err) - } else { - outputs.EnabledOutputs = append(outputs.EnabledOutputs, "MQTT") - } - } + // if config.MQTT.Broker != "" { + // var err error + // mqttClient, err = outputs.NewMQTTClient(config, stats, promStats, statsdClient, dogstatsdClient) + // if err != nil { + // config.MQTT.Broker = "" + // log.Printf("[ERROR] : MQTT - %v\n", err) + // } else { + // outputs.EnabledOutputs = append(outputs.EnabledOutputs, "MQTT") + // } + // } if config.Zincsearch.HostPort != "" { var err error @@ -823,38 +821,38 @@ func main() { log.Printf("[INFO] : Debug mode : %v", config.Debug) } - routes := map[string]http.Handler{ - "/": http.HandlerFunc(mainHandler), - "/ping": http.HandlerFunc(pingHandler), - "/healthz": http.HandlerFunc(healthHandler), - "/test": http.HandlerFunc(testHandler), - "/metrics": promhttp.Handler(), - } + // routes := map[string]http.Handler{ + // "/": http.HandlerFunc(mainHandler), + // "/ping": http.HandlerFunc(pingHandler), + // "/healthz": http.HandlerFunc(healthHandler), + // "/test": http.HandlerFunc(testHandler), + // "/metrics": promhttp.Handler(), + // } mainServeMux := http.NewServeMux() var HTTPServeMux *http.ServeMux // configure HTTP routes requested by NoTLSPath config - if config.TLSServer.Deploy { - HTTPServeMux = http.NewServeMux() - for _, r := range config.TLSServer.NoTLSPaths { - handler, ok := routes[r] - if ok { - delete(routes, r) - if config.Debug { - log.Printf("[DEBUG] : %s is served on http", r) - } - HTTPServeMux.Handle(r, handler) - } else { - log.Printf("[WARN] : tlsserver.notlspaths has unknown path '%s'", r) - } - } - } - - // configure main server routes - for r, handler := range routes { - mainServeMux.Handle(r, handler) - } + // if config.TLSServer.Deploy { + // HTTPServeMux = http.NewServeMux() + // for _, r := range config.TLSServer.NoTLSPaths { + // handler, ok := routes[r] + // if ok { + // delete(routes, r) + // if config.Debug { + // log.Printf("[DEBUG] : %s is served on http", r) + // } + // HTTPServeMux.Handle(r, handler) + // } else { + // log.Printf("[WARN] : tlsserver.notlspaths has unknown path '%s'", r) + // } + // } + // } + + // // configure main server routes + // for r, handler := range routes { + // mainServeMux.Handle(r, handler) + // } server := &http.Server{ Addr: fmt.Sprintf("%s:%d", config.ListenAddress, config.ListenPort), diff --git a/outputs/alertmanager.go b/outputs/alertmanager.go index 0abafd7ef..003b79c69 100644 --- a/outputs/alertmanager.go +++ b/outputs/alertmanager.go @@ -3,11 +3,9 @@ package outputs import ( - "encoding/json" + "fmt" "log" "regexp" - "sort" - "strconv" "strings" "time" @@ -36,82 +34,28 @@ var ( reg = regexp.MustCompile("[^a-zA-Z0-9_]") ) -func newAlertmanagerPayload(falcopayload types.FalcoPayload, config *types.Configuration) []alertmanagerPayload { +func newAlertmanagerPayload(KubearmorPayload types.KubearmorPayload, config *types.Configuration) []alertmanagerPayload { var amPayload alertmanagerPayload amPayload.Labels = make(map[string]string) amPayload.Annotations = make(map[string]string) - for i, j := range falcopayload.OutputFields { - if strings.HasPrefix(i, "n_evts") { - // avoid delta evts as label - continue - } - // strip cardinalities of syscall drops - if strings.HasPrefix(i, "n_drop") { - d, err := strconv.ParseInt(j.(string), 10, 64) - if err == nil { - var jj string - if d == 0 { - if falcopayload.Priority < types.Warning { - falcopayload.Priority = types.Warning - } - jj = "0" - } else { - for _, threshold := range config.Alertmanager.DropEventThresholdsList { - if d > threshold.Value { - jj = ">" + strconv.FormatInt(threshold.Value, 10) - if falcopayload.Priority < threshold.Priority { - falcopayload.Priority = threshold.Priority - } - break - } - } - } - if jj == "" { - jj = j.(string) - if prio := types.Priority(config.Alertmanager.DropEventDefaultPriority); falcopayload.Priority < prio { - falcopayload.Priority = prio - } - } - amPayload.Labels[i] = jj - } - continue - } - safeLabel := alertmanagerSafeLabel(i) + for i, j := range KubearmorPayload.OutputFields { switch v := j.(type) { case string: - //AlertManger unsupported chars in a label name - amPayload.Labels[safeLabel] = v - case json.Number: - amPayload.Labels[safeLabel] = v.String() + jj := j.(string) + amPayload.Labels[i] = jj default: - continue + vv := fmt.Sprint(v) + amPayload.Labels[i] = vv } - } - amPayload.Labels["source"] = "falco" - amPayload.Labels["rule"] = falcopayload.Rule - amPayload.Labels["eventsource"] = falcopayload.Source - if falcopayload.Hostname != "" { - amPayload.Labels[Hostname] = falcopayload.Hostname - } - if len(falcopayload.Tags) != 0 { - sort.Strings(falcopayload.Tags) - amPayload.Labels["tags"] = strings.Join(falcopayload.Tags, ",") - } - - amPayload.Labels["priority"] = falcopayload.Priority.String() - if val, ok := config.Alertmanager.CustomSeverityMap[falcopayload.Priority]; ok { - amPayload.Labels["severity"] = val - } else { - amPayload.Labels["severity"] = defaultSeverityMap[falcopayload.Priority] } - amPayload.Annotations["info"] = falcopayload.Output - amPayload.Annotations["description"] = falcopayload.Output - amPayload.Annotations["summary"] = falcopayload.Rule + amPayload.Labels["source"] = "Kubearmor" + if config.Alertmanager.ExpiresAfter != 0 { - amPayload.EndsAt = falcopayload.Time.Add(time.Duration(config.Alertmanager.ExpiresAfter) * time.Second) + timestamp := time.Unix(KubearmorPayload.Timestamp, 0) + amPayload.EndsAt = timestamp.Add(time.Duration(config.Alertmanager.ExpiresAfter) * time.Second) } for label, value := range config.Alertmanager.ExtraLabels { amPayload.Labels[label] = value @@ -128,7 +72,7 @@ func newAlertmanagerPayload(falcopayload types.FalcoPayload, config *types.Confi } // AlertmanagerPost posts event to AlertManager -func (c *Client) AlertmanagerPost(falcopayload types.FalcoPayload) { +func (c *Client) AlertmanagerPost(KubearmorPayload types.KubearmorPayload) { c.Stats.Alertmanager.Add(Total, 1) c.httpClientLock.Lock() defer c.httpClientLock.Unlock() @@ -136,7 +80,7 @@ func (c *Client) AlertmanagerPost(falcopayload types.FalcoPayload) { c.AddHeader(i, j) } - err := c.Post(newAlertmanagerPayload(falcopayload, c.Config)) + err := c.Post(newAlertmanagerPayload(KubearmorPayload, c.Config)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:alertmanager", "status:error"}) c.Stats.Alertmanager.Add(Error, 1) @@ -160,3 +104,26 @@ func alertmanagerSafeLabel(label string) string { // remove leading _ return strings.TrimLeft(replaced, "_") } + +func (c *Client) WatchAlertmanagerPostAlerts() error { + uid := "Alertmaneger" + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.AlertmanagerPost(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} diff --git a/outputs/alertmanager_test.go b/outputs/alertmanager_test.go index 127832f23..5328ef97c 100644 --- a/outputs/alertmanager_test.go +++ b/outputs/alertmanager_test.go @@ -16,7 +16,7 @@ const defaultThresholds = `[{"priority":"critical", "value":10000}, {"priority": func TestNewAlertmanagerPayloadO(t *testing.T) { expectedOutput := `[{"labels":{"proc_name":"falcosidekick","priority":"Debug","severity": "information","proc_tty":"1234","eventsource":"syscalls","hostname":"test-host","rule":"Test rule","source":"falco","tags":"example,test"},"annotations":{"info":"This is a test from falcosidekick","description":"This is a test from falcosidekick","summary":"Test rule"}}]` - var f types.FalcoPayload + var f types.KubearmorPayload d := json.NewDecoder(strings.NewReader(falcoTestInput)) d.UseNumber() err := d.Decode(&f) //have to decode it the way newFalcoPayload does @@ -40,7 +40,7 @@ func TestNewAlertmanagerPayloadO(t *testing.T) { func TestNewAlertmanagerPayloadDropEvent(t *testing.T) { input := `{"hostname":"host","output":"Falco internal: syscall event drop. 815508 system calls dropped in last second.","output_fields":{"ebpf_enabled":"1","n_drops":"815508","n_drops_buffer_clone_fork_enter":"0","n_drops_buffer_clone_fork_exit":"0","n_drops_buffer_connect_enter":"0","n_drops_buffer_connect_exit":"0","n_drops_buffer_dir_file_enter":"803","n_drops_buffer_dir_file_exit":"804","n_drops_buffer_execve_enter":"0","n_drops_buffer_execve_exit":"0","n_drops_buffer_open_enter":"798","n_drops_buffer_open_exit":"798","n_drops_buffer_other_interest_enter":"0","n_drops_buffer_other_interest_exit":"0","n_drops_buffer_total":"815508","n_drops_bug":"0","n_drops_page_faults":"0","n_drops_scratch_map":"0","n_evts":"2270350"},"priority":"Debug","rule":"Falco internal: syscall event drop","time":"2023-03-03T03:03:03.000000003Z"}` expectedOutput := `[{"labels":{"ebpf_enabled":"1","eventsource":"","hostname":"host","n_drops":">10000","n_drops_buffer_clone_fork_enter":"0","n_drops_buffer_clone_fork_exit":"0","n_drops_buffer_connect_enter":"0","n_drops_buffer_connect_exit":"0","n_drops_buffer_dir_file_enter":">100","n_drops_buffer_dir_file_exit":">100","n_drops_buffer_execve_enter":"0","n_drops_buffer_execve_exit":"0","n_drops_buffer_open_enter":">100","n_drops_buffer_open_exit":">100","n_drops_buffer_other_interest_enter":"0","n_drops_buffer_other_interest_exit":"0","n_drops_buffer_total":">10000","n_drops_bug":"0","n_drops_page_faults":"0","n_drops_scratch_map":"0","priority":"Critical","rule":"Falco internal: syscall event drop","severity":"critical","source":"falco"},"annotations":{"description":"Falco internal: syscall event drop. 815508 system calls dropped in last second.","info":"Falco internal: syscall event drop. 815508 system calls dropped in last second.","summary":"Falco internal: syscall event drop"},"endsAt":"0001-01-01T00:00:00Z"}]` - var f types.FalcoPayload + var f types.KubearmorPayload d := json.NewDecoder(strings.NewReader(input)) d.UseNumber() err := d.Decode(&f) //have to decode it the way newFalcoPayload does @@ -64,7 +64,7 @@ func TestNewAlertmanagerPayloadDropEvent(t *testing.T) { func TestNewAlertmanagerPayloadBadLabels(t *testing.T) { input := `{"hostname":"host","output":"Falco internal: syscall event drop. 815508 system calls dropped in last second.","output_fields":{"ebpf/enabled":"1","n drops/buffer?clone{fork]enter":"0","n_drops_buffer_clone_fork_exit":"0"},"priority":"Debug","rule":"Falco internal: syscall event drop","time":"2023-03-03T03:03:03.000000003Z"}` expectedOutput := `[{"labels":{"ebpf_enabled":"1","eventsource":"","hostname":"host","n_drops_buffer_clone_fork_enter":"0","n_drops_buffer_clone_fork_exit":"0","priority":"Warning","rule":"Falco internal: syscall event drop","severity":"warning","source":"falco"},"annotations":{"description":"Falco internal: syscall event drop. 815508 system calls dropped in last second.","info":"Falco internal: syscall event drop. 815508 system calls dropped in last second.","summary":"Falco internal: syscall event drop"},"endsAt":"0001-01-01T00:00:00Z"}]` - var f types.FalcoPayload + var f types.KubearmorPayload d := json.NewDecoder(strings.NewReader(input)) d.UseNumber() err := d.Decode(&f) //have to decode it the way newFalcoPayload does diff --git a/outputs/aws.go b/outputs/aws.go index 33a1c1df0..fcc694279 100644 --- a/outputs/aws.go +++ b/outputs/aws.go @@ -11,7 +11,6 @@ import ( "log" "net/url" "os" - "strings" "time" "github.com/DataDog/datadog-go/statsd" @@ -120,10 +119,10 @@ func NewAWSClient(config *types.Configuration, stats *types.Statistics, promStat } // InvokeLambda invokes a lambda function -func (c *Client) InvokeLambda(falcopayload types.FalcoPayload) { +func (c *Client) InvokeLambda(kubearmorpayload types.KubearmorPayload) { svc := lambda.New(c.AWSSession) - f, _ := json.Marshal(falcopayload) + f, _ := json.Marshal(kubearmorpayload) input := &lambda.InvokeInput{ FunctionName: aws.String(c.Config.AWS.Lambda.FunctionName), @@ -155,10 +154,10 @@ func (c *Client) InvokeLambda(falcopayload types.FalcoPayload) { } // SendMessage sends a message to SQS Queue -func (c *Client) SendMessage(falcopayload types.FalcoPayload) { +func (c *Client) SendMessage(kubearmorpayload types.KubearmorPayload) { svc := sqs.New(c.AWSSession) - f, _ := json.Marshal(falcopayload) + f, _ := json.Marshal(kubearmorpayload) input := &sqs.SendMessageInput{ MessageBody: aws.String(string(f)), @@ -187,8 +186,8 @@ func (c *Client) SendMessage(falcopayload types.FalcoPayload) { } // UploadS3 upload payload to S3 -func (c *Client) UploadS3(falcopayload types.FalcoPayload) { - f, _ := json.Marshal(falcopayload) +func (c *Client) UploadS3(kubearmorpayload types.KubearmorPayload) { + f, _ := json.Marshal(kubearmorpayload) prefix := "" t := time.Now() @@ -197,15 +196,11 @@ func (c *Client) UploadS3(falcopayload types.FalcoPayload) { } key := fmt.Sprintf("%s/%s/%s.json", prefix, t.Format("2006-01-02"), t.Format(time.RFC3339Nano)) - awsConfig := aws.NewConfig() - if c.Config.AWS.S3.Endpoint != "" { - awsConfig = awsConfig.WithEndpoint(c.Config.AWS.S3.Endpoint) - } - resp, err := s3.New(c.AWSSession, awsConfig).PutObject(&s3.PutObjectInput{ + resp, err := s3.New(c.AWSSession).PutObject(&s3.PutObjectInput{ Bucket: aws.String(c.Config.AWS.S3.Bucket), Key: aws.String(key), Body: bytes.NewReader(f), - ACL: aws.String(c.Config.AWS.S3.ObjectCannedACL), + ACL: aws.String(s3.ObjectCannedACLBucketOwnerFullControl), }) if err != nil { go c.CountMetric("outputs", 1, []string{"output:awss3", "status:error"}) @@ -225,64 +220,33 @@ func (c *Client) UploadS3(falcopayload types.FalcoPayload) { } // PublishTopic sends a message to a SNS Topic -func (c *Client) PublishTopic(falcopayload types.FalcoPayload) { +func (c *Client) PublishTopic(kubearmorpayload types.KubearmorPayload) { svc := sns.New(c.AWSSession) var msg *sns.PublishInput if c.Config.AWS.SNS.RawJSON { - f, _ := json.Marshal(falcopayload) + f, _ := json.Marshal(kubearmorpayload) msg = &sns.PublishInput{ Message: aws.String(string(f)), TopicArn: aws.String(c.Config.AWS.SNS.TopicArn), } } else { msg = &sns.PublishInput{ - Message: aws.String(falcopayload.Output), - MessageAttributes: map[string]*sns.MessageAttributeValue{ - "priority": { - DataType: aws.String("String"), - StringValue: aws.String(falcopayload.Priority.String()), - }, - "rule": { - DataType: aws.String("String"), - StringValue: aws.String(falcopayload.Rule), - }, - "source": { - DataType: aws.String("String"), - StringValue: aws.String(falcopayload.Source), - }, - }, + Message: aws.String(kubearmorpayload.EventType), TopicArn: aws.String(c.Config.AWS.SNS.TopicArn), } - if len(falcopayload.Tags) != 0 { - msg.MessageAttributes["tags"] = &sns.MessageAttributeValue{ - DataType: aws.String("String"), - StringValue: aws.String(strings.Join(falcopayload.Tags, ",")), - } - } - if falcopayload.Hostname != "" { + if kubearmorpayload.Hostname != "" { msg.MessageAttributes[Hostname] = &sns.MessageAttributeValue{ DataType: aws.String("String"), - StringValue: aws.String(falcopayload.Hostname), + StringValue: aws.String(kubearmorpayload.Hostname), } } - for i, j := range falcopayload.OutputFields { - m := strings.ReplaceAll(strings.ReplaceAll(i, "]", ""), "[", ".") - switch j.(type) { - case string: - msg.MessageAttributes[m] = &sns.MessageAttributeValue{ - DataType: aws.String("String"), - StringValue: aws.String(fmt.Sprintf("%v", j)), - } - case json.Number: - msg.MessageAttributes[m] = &sns.MessageAttributeValue{ - DataType: aws.String("Number"), - StringValue: aws.String(fmt.Sprintf("%v", j)), - } - default: - continue + for i, j := range kubearmorpayload.OutputFields { + msg.MessageAttributes[i] = &sns.MessageAttributeValue{ + DataType: aws.String("String"), + StringValue: aws.String(fmt.Sprintf("%v", j)), } } } @@ -309,15 +273,15 @@ func (c *Client) PublishTopic(falcopayload types.FalcoPayload) { } // SendCloudWatchLog sends a message to CloudWatch Log -func (c *Client) SendCloudWatchLog(falcopayload types.FalcoPayload) { +func (c *Client) SendCloudWatchLog(kubearmorpayload types.KubearmorPayload) { svc := cloudwatchlogs.New(c.AWSSession) - f, _ := json.Marshal(falcopayload) + f, _ := json.Marshal(kubearmorpayload) c.Stats.AWSCloudWatchLogs.Add(Total, 1) if c.Config.AWS.CloudWatchLogs.LogStream == "" { - streamName := "falcosidekick-logstream" + streamName := "sidekick-logstream" log.Printf("[INFO] : %v CloudWatchLogs - Log Stream not configured creating one called %s\n", c.OutputType, streamName) inputLogStream := &cloudwatchlogs.CreateLogStreamInput{ LogGroupName: aws.String(c.Config.AWS.CloudWatchLogs.LogGroup), @@ -342,7 +306,7 @@ func (c *Client) SendCloudWatchLog(falcopayload types.FalcoPayload) { logevent := &cloudwatchlogs.InputLogEvent{ Message: aws.String(string(f)), - Timestamp: aws.Int64(falcopayload.Time.UnixNano() / int64(time.Millisecond)), + Timestamp: aws.Int64(kubearmorpayload.Timestamp / int64(time.Millisecond)), } input := &cloudwatchlogs.PutLogEventsInput{ @@ -385,12 +349,12 @@ func (c *Client) putLogEvents(svc *cloudwatchlogs.CloudWatchLogs, input *cloudwa } // PutRecord puts a record in Kinesis -func (c *Client) PutRecord(falcoPayLoad types.FalcoPayload) { +func (c *Client) PutRecord(kubearmorpayload types.KubearmorPayload) { svc := kinesis.New(c.AWSSession) c.Stats.AWSKinesis.Add(Total, 1) - f, _ := json.Marshal(falcoPayLoad) + f, _ := json.Marshal(kubearmorpayload) input := &kinesis.PutRecordInput{ Data: f, PartitionKey: aws.String(uuid.NewString()), @@ -411,3 +375,146 @@ func (c *Client) PutRecord(falcoPayLoad types.FalcoPayload) { c.Stats.AWSKinesis.Add(OK, 1) c.PromStats.Outputs.With(map[string]string{"destination": "awskinesis", "status": "ok"}).Inc() } + +// lambda +func (c *Client) WatchInvokeLambdaAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.InvokeLambda(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} + +// SendMessage +func (c *Client) WatchSendMessageAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.SendMessage(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} + +// PublishTopic +func (c *Client) WatchPublishTopicAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.PublishTopic(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} + +// SendCloudWatchLog +func (c *Client) WatchSendCloudWatchLogAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.SendCloudWatchLog(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} + +// UploadS3 +func (c *Client) WatchUploadS3Alerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.UploadS3(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} + +func (c *Client) WatchPutRecordAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.PutRecord(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} diff --git a/outputs/awssecuritylake.go b/outputs/awssecuritylake.go index 0870cf80c..1fbbed86a 100644 --- a/outputs/awssecuritylake.go +++ b/outputs/awssecuritylake.go @@ -99,7 +99,7 @@ type OCSFProduct struct { Name string `json:"name" parquet:"name=name, type=BYTE_ARRAY, convertedtype=UTF8"` } -func NewOCSFSecurityFinding(falcopayload types.FalcoPayload) OCSFSecurityFinding { +func NewOCSFSecurityFinding(kubearmorpayload types.KubearmorPayload) OCSFSecurityFinding { ocsfsf := OCSFSecurityFinding{ ActivityID: 1, ActivityName: "Generate", @@ -109,32 +109,31 @@ func NewOCSFSecurityFinding(falcopayload types.FalcoPayload) OCSFSecurityFinding ClassUID: 2001, TypeUID: 200101, TypeName: "Security Finding: Generate", - // Attacks: getMitreAttacke(falcopayload.Tags), + // Attacks: getMitreAttacke(kubearmorpayload.Tags), Metadata: OCSFMetadata{ - Labels: falcopayload.Tags, + Labels: []string{kubearmorpayload.OutputFields["Labels"].(string)}, Product: OCSFProduct{ - Name: "Falco", - VendorName: "Falcosecurity", + Name: "Kubearmor", + VendorName: "Accuknox", }, Version: schemaVersion, }, - RawData: falcopayload.String(), + RawData: kubearmorpayload.String(), State: "New", StateID: 1, Finding: OCSFFIndingDetails{ - CreatedTime: falcopayload.Time.UnixMilli(), - Desc: falcopayload.Output, - Title: falcopayload.Rule, - Types: []string{falcopayload.Source}, - UID: falcopayload.UUID, + CreatedTime: kubearmorpayload.Timestamp, + Desc: kubearmorpayload.EventType, + Title: kubearmorpayload.OutputFields["PodName"].(string) + "-" + kubearmorpayload.EventType, + UID: kubearmorpayload.OutputFields["UID"].(string), }, - Message: falcopayload.Rule, - Observables: getObservables(falcopayload.Hostname, falcopayload.OutputFields), - Timestamp: falcopayload.Time.UnixMilli(), - Status: falcopayload.Priority.String(), + Message: kubearmorpayload.EventType + "-" + kubearmorpayload.ClusterName + "-" + kubearmorpayload.OutputFields["PodName"].(string), + Observables: getObservables(kubearmorpayload.Hostname, kubearmorpayload.OutputFields), + Timestamp: kubearmorpayload.Timestamp, + Status: kubearmorpayload.EventType, } - ocsfsf.SeverityID, ocsfsf.Severity = getAWSSecurityLakeSeverity(falcopayload.Priority) + ocsfsf.SeverityID, ocsfsf.Severity = 0, "" return ocsfsf } @@ -197,8 +196,8 @@ func getAWSSecurityLakeSeverity(priority types.PriorityType) (int32, string) { // return ocsfa // } -func (c *Client) EnqueueSecurityLake(falcopayload types.FalcoPayload) { - offset, err := c.Config.AWS.SecurityLake.Memlog.Write(c.Config.AWS.SecurityLake.Ctx, []byte(falcopayload.String())) +func (c *Client) EnqueueSecurityLake(kubearmorpayload types.KubearmorPayload) { + offset, err := c.Config.AWS.SecurityLake.Memlog.Write(c.Config.AWS.SecurityLake.Ctx, []byte(kubearmorpayload.String())) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:awssecuritylake.", "status:error"}) c.Stats.AWSSecurityLake.Add(Error, 1) @@ -206,7 +205,7 @@ func (c *Client) EnqueueSecurityLake(falcopayload types.FalcoPayload) { log.Printf("[ERROR] : %v SecurityLake - %v\n", c.OutputType, err) return } - log.Printf("[INFO] : %v SecurityLake - Event queued (%v)\n", c.OutputType, falcopayload.UUID) + log.Printf("[INFO] : %v SecurityLake - Event queued (%v)\n", c.OutputType, kubearmorpayload.OutputFields["UID"].(string)) *c.Config.AWS.SecurityLake.WriteOffset = offset } @@ -323,7 +322,7 @@ func (c *Client) writeParquet(uid string, records []memlog.Record) error { return err } for _, i := range records { - var f types.FalcoPayload + var f types.KubearmorPayload if err := json.Unmarshal(i.Data, &f); err != nil { log.Printf("[ERROR] : %v SecurityLake - Unmarshalling error: %v\n", c.OutputType, err) continue @@ -343,3 +342,27 @@ func (c *Client) writeParquet(uid string, records []memlog.Record) error { } return nil } + +// EnqueueSecurityLake +func (c *Client) WatchEnqueueSecurityLakeAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.EnqueueSecurityLake(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} diff --git a/outputs/azure.go b/outputs/azure.go index 5d746a5f4..6465d98a3 100644 --- a/outputs/azure.go +++ b/outputs/azure.go @@ -11,6 +11,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azidentity" azeventhubs "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" "github.com/DataDog/datadog-go/statsd" + "github.com/google/uuid" "github.com/falcosecurity/falcosidekick/types" ) @@ -28,7 +29,7 @@ func NewEventHubClient(config *types.Configuration, stats *types.Statistics, pro } // EventHubPost posts event to Azure Event Hub -func (c *Client) EventHubPost(falcopayload types.FalcoPayload) { +func (c *Client) EventHubPost(KubearmorPayload types.KubearmorPayload) { c.Stats.AzureEventHub.Add(Total, 1) ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) @@ -52,7 +53,7 @@ func (c *Client) EventHubPost(falcopayload types.FalcoPayload) { log.Printf("[INFO] : %v EventHub - Hub client created\n", c.OutputType) - data, err := json.Marshal(falcopayload) + data, err := json.Marshal(KubearmorPayload) if err != nil { c.setEventHubErrorMetrics() log.Printf("[ERROR] : Cannot marshal payload: %v", err.Error()) @@ -92,3 +93,27 @@ func (c *Client) setEventHubErrorMetrics() { c.Stats.AzureEventHub.Add(Error, 1) c.PromStats.Outputs.With(map[string]string{"destination": "azureeventhub", "status": Error}).Inc() } + +// EnqueueSecurityLake +func (c *Client) WatchEventHubPostlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.EventHubPost(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} diff --git a/outputs/client.go b/outputs/client.go index ceae6f86d..9ec9581ed 100644 --- a/outputs/client.go +++ b/outputs/client.go @@ -128,6 +128,13 @@ type Client struct { MQTTClient mqtt.Client TimescaleDBClient *timescaledb.Pool RedisClient *redis.Client + + // wait group + WgServer sync.WaitGroup + + GetLogs bool + // + Running bool } // InitClient returns a new output.Client for accessing the different API. diff --git a/outputs/cliq.go b/outputs/cliq.go index 329c5fda8..7910c24d2 100644 --- a/outputs/cliq.go +++ b/outputs/cliq.go @@ -6,8 +6,10 @@ import ( "bytes" "fmt" "log" + "time" "github.com/falcosecurity/falcosidekick/types" + "github.com/google/uuid" ) // Cliq API reference: https://www.zoho.com/cliq/help/restapi/v2/ @@ -65,7 +67,7 @@ type cliqPayload struct { Slides []cliqSlide `json:"slides,omitempty"` } -func newCliqPayload(falcopayload types.FalcoPayload, config *types.Configuration) cliqPayload { +func newCliqPayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) cliqPayload { var ( payload cliqPayload field cliqTableRow @@ -76,7 +78,7 @@ func newCliqPayload(falcopayload types.FalcoPayload, config *types.Configuration if config.Cliq.MessageFormatTemplate != nil { buf := &bytes.Buffer{} - if err := config.Cliq.MessageFormatTemplate.Execute(buf, falcopayload); err != nil { + if err := config.Cliq.MessageFormatTemplate.Execute(buf, kubearmorpayload); err != nil { log.Printf("[ERROR] : Cliq - Error expanding Cliq message %v", err) } else { payload.Text = buf.String() @@ -84,38 +86,42 @@ func newCliqPayload(falcopayload types.FalcoPayload, config *types.Configuration if config.Cliq.OutputFormat == All || config.Cliq.OutputFormat == Text || config.Cliq.OutputFormat == "" { slide := cliqSlide{ Type: textSlideType, - Data: falcopayload.Output, + Data: kubearmorpayload.EventType, } payload.Slides = append(payload.Slides, slide) } } } else { - payload.Text = falcopayload.Output + payload.Text = kubearmorpayload.EventType } if config.Cliq.OutputFormat == All || config.Cliq.OutputFormat == Fields || config.Cliq.OutputFormat == "" { - field.Field = Rule - field.Value = falcopayload.Rule + field.Field = "Event" + field.Value = kubearmorpayload.EventType table.Rows = append(table.Rows, field) - field.Field = Priority - field.Value = falcopayload.Priority.String() - table.Rows = append(table.Rows, field) - - if falcopayload.Hostname != "" { + if kubearmorpayload.Hostname != "" { field.Field = Hostname - field.Value = falcopayload.Hostname + field.Value = kubearmorpayload.Hostname table.Rows = append(table.Rows, field) } - for _, i := range getSortedStringKeys(falcopayload.OutputFields) { - field.Field = i - field.Value = falcopayload.OutputFields[i].(string) - table.Rows = append(table.Rows, field) + for _, i := range getSortedStringKeys(kubearmorpayload.OutputFields) { + j := kubearmorpayload.OutputFields[i] + switch j.(type) { + case string: + field.Field = i + field.Value = kubearmorpayload.OutputFields[i].(string) + table.Rows = append(table.Rows, field) + default: + field.Field = i + field.Value = fmt.Sprint(j) + table.Rows = append(table.Rows, field) + } } field.Field = Time - field.Value = falcopayload.Time.String() + field.Value = fmt.Sprint(kubearmorpayload.Timestamp) table.Rows = append(table.Rows, field) table.Headers = tableSlideHeaders @@ -128,23 +134,11 @@ func newCliqPayload(falcopayload types.FalcoPayload, config *types.Configuration if config.Cliq.UseEmoji { var emoji rune - switch falcopayload.Priority { - case types.Emergency: - emoji = emergencyEmoji - case types.Alert: + switch kubearmorpayload.EventType { + case "Alert": emoji = errorEmoji - case types.Critical: - emoji = errorEmoji - case types.Error: - emoji = emergencyEmoji - case types.Warning: - emoji = warningEmoji - case types.Notice: - emoji = noticeEmoji - case types.Informational: + case "Log": emoji = informationEmoji - case types.Debug: - emoji = debugEmoji default: emoji = '?' } @@ -161,13 +155,13 @@ func newCliqPayload(falcopayload types.FalcoPayload, config *types.Configuration } // CliqPost posts event to cliq -func (c *Client) CliqPost(falcopayload types.FalcoPayload) { +func (c *Client) CliqPost(KubearmorPayload types.KubearmorPayload) { c.Stats.Cliq.Add(Total, 1) c.httpClientLock.Lock() defer c.httpClientLock.Unlock() c.AddHeader(ContentTypeHeaderKey, "application/json") - err := c.Post(newCliqPayload(falcopayload, c.Config)) + err := c.Post(newCliqPayload(KubearmorPayload, c.Config)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:cliq", "status:error"}) c.Stats.Cliq.Add(Error, 1) @@ -181,3 +175,26 @@ func (c *Client) CliqPost(falcopayload types.FalcoPayload) { c.Stats.Cliq.Add(OK, 1) c.PromStats.Outputs.With(map[string]string{"destination": "cliq", "status": OK}).Inc() } + +func (c *Client) WatchCliqPostAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.CliqPost(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} diff --git a/outputs/cliq_test.go b/outputs/cliq_test.go index 1c6fb6f03..4b480a83d 100644 --- a/outputs/cliq_test.go +++ b/outputs/cliq_test.go @@ -59,7 +59,7 @@ func TestNewCliqPayload(t *testing.T) { }, } - var f types.FalcoPayload + var f types.KubearmorPayload require.Nil(t, json.Unmarshal([]byte(falcoTestInput), &f)) config := &types.Configuration{ Cliq: types.CliqOutputConfig{ diff --git a/outputs/cloudevents.go b/outputs/cloudevents.go index b56457f3a..345a5bd5e 100644 --- a/outputs/cloudevents.go +++ b/outputs/cloudevents.go @@ -5,14 +5,16 @@ package outputs import ( "context" "log" + "time" cloudevents "github.com/cloudevents/sdk-go/v2" + "github.com/google/uuid" "github.com/falcosecurity/falcosidekick/types" ) // CloudEventsSend produces a CloudEvent and sends to the CloudEvents consumers. -func (c *Client) CloudEventsSend(falcopayload types.FalcoPayload) { +func (c *Client) CloudEventsSend(KubearmorPayload types.KubearmorPayload) { c.Stats.CloudEvents.Add(Total, 1) if c.CloudEventsClient == nil { @@ -28,15 +30,12 @@ func (c *Client) CloudEventsSend(falcopayload types.FalcoPayload) { ctx := cloudevents.ContextWithTarget(context.Background(), c.EndpointURL.String()) event := cloudevents.NewEvent() - event.SetTime(falcopayload.Time) - event.SetSource("https://falco.org") - event.SetType("falco.rule.output.v1") - event.SetExtension("priority", falcopayload.Priority.String()) - event.SetExtension("rule", falcopayload.Rule) - event.SetExtension("event_source", falcopayload.Source) - - if falcopayload.Hostname != "" { - event.SetExtension(Hostname, falcopayload.Hostname) + event.SetTime(time.Unix(KubearmorPayload.Timestamp, 0)) + event.SetSource("https://kubearmor.io/") // TODO: this should have some info on the server that made the event. + event.SetType("kubearmor.rule.output.v1") + event.SetExtension("priority", KubearmorPayload.EventType) + if KubearmorPayload.Hostname != "" { + event.SetExtension(Hostname, KubearmorPayload.Hostname) } // Set Extensions. @@ -44,7 +43,7 @@ func (c *Client) CloudEventsSend(falcopayload types.FalcoPayload) { event.SetExtension(k, v) } - if err := event.SetData(cloudevents.ApplicationJSON, falcopayload); err != nil { + if err := event.SetData(cloudevents.ApplicationJSON, KubearmorPayload); err != nil { log.Printf("[ERROR] : CloudEvents, failed to set data : %v\n", err) } @@ -62,3 +61,26 @@ func (c *Client) CloudEventsSend(falcopayload types.FalcoPayload) { c.PromStats.Outputs.With(map[string]string{"destination": "cloudevents", "status": OK}).Inc() log.Printf("[INFO] : CloudEvents - Send OK\n") } + +func (c *Client) WatchCloudEventsSendAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.CloudEventsSend(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} diff --git a/outputs/constants.go b/outputs/constants.go index ad4fe9cfb..fdd3fb35c 100644 --- a/outputs/constants.go +++ b/outputs/constants.go @@ -22,6 +22,9 @@ const ( Accepted string = "accepted" Outputs string = "outputs" + Kubearmor string = "Kubearmor" + Accuknox string = "Accuknox" + Rule string = "rule" Priority string = "priority" Source string = "source" diff --git a/outputs/datadog.go b/outputs/datadog.go index 347b0ce21..90b144b82 100644 --- a/outputs/datadog.go +++ b/outputs/datadog.go @@ -5,9 +5,10 @@ package outputs import ( "fmt" "log" - "sort" + "time" "github.com/falcosecurity/falcosidekick/types" + "github.com/google/uuid" ) const ( @@ -23,35 +24,29 @@ type datadogPayload struct { Tags []string `json:"tags,omitempty"` } -func newDatadogPayload(falcopayload types.FalcoPayload) datadogPayload { +func newDatadogPayload(KubearmorPayload types.KubearmorPayload) datadogPayload { var d datadogPayload tags := make([]string, 0) - for _, i := range getSortedStringKeys(falcopayload.OutputFields) { - tags = append(tags, fmt.Sprintf("%v:%v", i, falcopayload.OutputFields[i])) - - } - tags = append(tags, "source:"+falcopayload.Source) - if falcopayload.Hostname != "" { - tags = append(tags, Hostname+":"+falcopayload.Hostname) + for i, j := range KubearmorPayload.OutputFields { + switch v := j.(type) { + case string: + tags = append(tags, i+":"+v) + default: + vv := fmt.Sprintln(v) + tags = append(tags, i+":"+vv) + continue + } } - if len(falcopayload.Tags) != 0 { - sort.Strings(falcopayload.Tags) - tags = append(tags, falcopayload.Tags...) - } d.Tags = tags - d.Title = falcopayload.Rule - d.Text = falcopayload.Output - d.SourceType = "falco" + d.SourceType = "kubearmor" var status string - switch falcopayload.Priority { - case types.Emergency, types.Alert, types.Critical, types.Error: + switch KubearmorPayload.EventType { + case "Alert": status = Error - case types.Warning: - status = Warning default: status = Info } @@ -61,10 +56,10 @@ func newDatadogPayload(falcopayload types.FalcoPayload) datadogPayload { } // DatadogPost posts event to Datadog -func (c *Client) DatadogPost(falcopayload types.FalcoPayload) { +func (c *Client) DatadogPost(KubearmorPayload types.KubearmorPayload) { c.Stats.Datadog.Add(Total, 1) - err := c.Post(newDatadogPayload(falcopayload)) + err := c.Post(newDatadogPayload(KubearmorPayload)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:datadog", "status:error"}) c.Stats.Datadog.Add(Error, 1) @@ -77,3 +72,26 @@ func (c *Client) DatadogPost(falcopayload types.FalcoPayload) { c.Stats.Datadog.Add(OK, 1) c.PromStats.Outputs.With(map[string]string{"destination": "datadog", "status": OK}).Inc() } + +func (c *Client) WatchDatadogPostAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.DatadogPost(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} diff --git a/outputs/datadog_test.go b/outputs/datadog_test.go index c7285ed33..192fab196 100644 --- a/outputs/datadog_test.go +++ b/outputs/datadog_test.go @@ -13,7 +13,7 @@ import ( func TestNewDatadogPayload(t *testing.T) { expectedOutput := `{"title":"Test rule","text":"This is a test from falcosidekick","alert_type":"info","source_type_name":"falco","tags":["proc.name:falcosidekick", "source:syscalls", "hostname:test-host", "example", "test"]}` - var f types.FalcoPayload + var f types.KubearmorPayload json.Unmarshal([]byte(falcoTestInput), &f) s, _ := json.Marshal(newDatadogPayload(f)) diff --git a/outputs/discord.go b/outputs/discord.go index a9bba5781..5a203f3f7 100644 --- a/outputs/discord.go +++ b/outputs/discord.go @@ -5,8 +5,7 @@ package outputs import ( "fmt" "log" - "sort" - "strings" + "time" "github.com/falcosecurity/falcosidekick/types" ) @@ -31,7 +30,7 @@ type discordEmbedFieldPayload struct { Inline bool `json:"inline"` } -func newDiscordPayload(falcopayload types.FalcoPayload, config *types.Configuration) discordPayload { +func newDiscordPayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) discordPayload { var iconURL string if config.Discord.Icon != "" { iconURL = config.Discord.Icon @@ -40,23 +39,11 @@ func newDiscordPayload(falcopayload types.FalcoPayload, config *types.Configurat } var color string - switch falcopayload.Priority { - case types.Emergency: - color = "15158332" // red - case types.Alert: + switch kubearmorpayload.EventType { + case "Alert": color = "11027200" // dark orange - case types.Critical: - color = "15105570" // orange - case types.Error: - color = "15844367" // gold - case types.Warning: - color = "12745742" // dark gold - case types.Notice: - color = "3066993" // teal - case types.Informational: + case "Log": color = "3447003" // blue - case types.Debug: - color = "12370112" // light grey } embeds := make([]discordEmbedPayload, 0) @@ -64,26 +51,29 @@ func newDiscordPayload(falcopayload types.FalcoPayload, config *types.Configurat embedFields := make([]discordEmbedFieldPayload, 0) var embedField discordEmbedFieldPayload - embedFields = append(embedFields, discordEmbedFieldPayload{Rule, falcopayload.Rule, true}) - embedFields = append(embedFields, discordEmbedFieldPayload{Priority, falcopayload.Priority.String(), true}) - embedFields = append(embedFields, discordEmbedFieldPayload{Source, falcopayload.Source, true}) - if falcopayload.Hostname != "" { - embedFields = append(embedFields, discordEmbedFieldPayload{Hostname, falcopayload.Hostname, true}) - } - - for _, i := range getSortedStringKeys(falcopayload.OutputFields) { - embedField = discordEmbedFieldPayload{i, fmt.Sprintf("```%v```", falcopayload.OutputFields[i]), true} + for i, j := range kubearmorpayload.OutputFields { + switch v := j.(type) { + case string: + jj := j.(string) + if jj == "" { + continue + } + embedField = discordEmbedFieldPayload{i, fmt.Sprintf("```%s```", jj), true} + default: + vv := fmt.Sprint(v) + embedField = discordEmbedFieldPayload{i, fmt.Sprintf("```%v```", vv), true} + } embedFields = append(embedFields, embedField) } - if len(falcopayload.Tags) != 0 { - sort.Strings(falcopayload.Tags) - embedFields = append(embedFields, discordEmbedFieldPayload{Tags, strings.Join(falcopayload.Tags, ", "), true}) + + if kubearmorpayload.Hostname != "" { + embedFields = append(embedFields, discordEmbedFieldPayload{Hostname, kubearmorpayload.Hostname, true}) } - embedFields = append(embedFields, discordEmbedFieldPayload{Time, falcopayload.Time.String(), true}) + embedFields = append(embedFields, discordEmbedFieldPayload{Time, fmt.Sprint(kubearmorpayload.Timestamp), true}) embed := discordEmbedPayload{ Title: "", - Description: falcopayload.Output, + Description: kubearmorpayload.EventType, Color: color, Fields: embedFields, } @@ -97,10 +87,10 @@ func newDiscordPayload(falcopayload types.FalcoPayload, config *types.Configurat } // DiscordPost posts events to discord -func (c *Client) DiscordPost(falcopayload types.FalcoPayload) { +func (c *Client) DiscordPost(KubearmorPayload types.KubearmorPayload) { c.Stats.Discord.Add(Total, 1) - err := c.Post(newDiscordPayload(falcopayload, c.Config)) + err := c.Post(newDiscordPayload(KubearmorPayload, c.Config)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:discord", "status:error"}) c.Stats.Discord.Add(Error, 1) @@ -114,3 +104,25 @@ func (c *Client) DiscordPost(falcopayload types.FalcoPayload) { c.Stats.Discord.Add(OK, 1) c.PromStats.Outputs.With(map[string]string{"destination": "discord", "status": OK}).Inc() } + +func (c *Client) WatchDiscordAlerts() error { + uid := "Discord" + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + fmt.Println("discord running") + for AlertRunning { + select { + case resp := <-conn: + c.DiscordPost(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + fmt.Println("discord stopped") + return nil +} diff --git a/outputs/discord_test.go b/outputs/discord_test.go index 558d3164f..4a4b6403b 100644 --- a/outputs/discord_test.go +++ b/outputs/discord_test.go @@ -62,7 +62,7 @@ func TestNewDiscordPayload(t *testing.T) { }, } - var f types.FalcoPayload + var f types.KubearmorPayload require.Nil(t, json.Unmarshal([]byte(falcoTestInput), &f)) config := &types.Configuration{ Discord: types.DiscordOutputConfig{}, diff --git a/outputs/elasticsearch.go b/outputs/elasticsearch.go index 878d37476..aa02bf58d 100644 --- a/outputs/elasticsearch.go +++ b/outputs/elasticsearch.go @@ -3,19 +3,17 @@ package outputs import ( - "encoding/json" "fmt" "log" "net/url" - "regexp" - "strings" "time" "github.com/falcosecurity/falcosidekick/types" + "github.com/google/uuid" ) type eSPayload struct { - types.FalcoPayload + types.KubearmorPayload Timestamp time.Time `json:"@timestamp"` } @@ -32,13 +30,13 @@ type mappingError struct { } // ElasticsearchPost posts event to Elasticsearch -func (c *Client) ElasticsearchPost(falcopayload types.FalcoPayload) { +func (c *Client) ElasticsearchPost(kubearmorpayload types.KubearmorPayload) { c.Stats.Elasticsearch.Add(Total, 1) current := time.Now() var eURL string switch c.Config.Elasticsearch.Suffix { - case None: + case "none": eURL = c.Config.Elasticsearch.HostPort + "/" + c.Config.Elasticsearch.Index + "/" + c.Config.Elasticsearch.Type case "monthly": eURL = c.Config.Elasticsearch.HostPort + "/" + c.Config.Elasticsearch.Index + "-" + current.Format("2006.01") + "/" + c.Config.Elasticsearch.Type @@ -66,47 +64,11 @@ func (c *Client) ElasticsearchPost(falcopayload types.FalcoPayload) { c.AddHeader(i, j) } - payload := eSPayload{FalcoPayload: falcopayload, Timestamp: falcopayload.Time} - if c.Config.Elasticsearch.FlattenFields || c.Config.Elasticsearch.CreateIndexTemplate { - for i, j := range payload.OutputFields { - payload.OutputFields[strings.ReplaceAll(i, ".", "_")] = j - delete(payload.OutputFields, i) - } - } - - err = c.Post(payload) + err = c.Post(kubearmorpayload) if err != nil { - var mappingErr mappingError - if err2 := json.Unmarshal([]byte(err.Error()), &mappingErr); err2 != nil { - c.setElasticSearchErrorMetrics() - return - } - if mappingErr.Error.Type == "document_parsing_exception" { - reg := regexp.MustCompile(`\[\w+(\.\w+)+\]`) - k := reg.FindStringSubmatch(mappingErr.Error.Reason) - if len(k) == 0 { - c.setElasticSearchErrorMetrics() - return - } - if !strings.Contains(k[0], "output_fields") { - c.setElasticSearchErrorMetrics() - return - } - s := strings.ReplaceAll(k[0], "[output_fields.", "") - s = strings.ReplaceAll(s, "]", "") - for i := range payload.OutputFields { - if strings.HasPrefix(i, s) { - delete(payload.OutputFields, i) - } - } - fmt.Println(payload.OutputFields) - log.Printf("[INFO] : %v - %v\n", c.OutputType, "attempt to POST again the payload without the wrong field") - err = c.Post(payload) - if err != nil { - c.setElasticSearchErrorMetrics() - return - } - } + c.setElasticSearchErrorMetrics() + log.Printf("[ERROR] : ElasticSearch - %v\n", err) + return } // Setting the success status @@ -115,60 +77,29 @@ func (c *Client) ElasticsearchPost(falcopayload types.FalcoPayload) { c.PromStats.Outputs.With(map[string]string{"destination": "elasticsearch", "status": OK}).Inc() } -func (c *Client) ElasticsearchCreateIndexTemplate(config types.ElasticsearchOutputConfig) error { - d := c - indexExists, err := c.isIndexTemplateExist(config) - if err != nil { - log.Printf("[ERROR] : %v - %v\n", c.OutputType, err.Error()) - return err - } - if indexExists { - log.Printf("[INFO] : %v - %v\n", c.OutputType, "Index template already exists") - return nil - } - - pattern := "-*" - if config.Suffix == None { - pattern = "" - } - m := strings.ReplaceAll(ESmapping, "${INDEX}", config.Index) - m = strings.ReplaceAll(m, "${PATTERN}", pattern) - m = strings.ReplaceAll(m, "${SHARDS}", fmt.Sprintf("%v", config.NumberOfShards)) - m = strings.ReplaceAll(m, "${REPLICAS}", fmt.Sprintf("%v", config.NumberOfReplicas)) - j := make(map[string]interface{}) - if err := json.Unmarshal([]byte(m), &j); err != nil { - log.Printf("[ERROR] : %v - %v\n", c.OutputType, err.Error()) - return err - } - // create the index template by PUT - if d.Put(j) != nil { - log.Printf("[ERROR] : %v - %v\n", c.OutputType, err.Error()) - return err - } - - log.Printf("[INFO] : %v - %v\n", c.OutputType, "Index template created") - return nil -} - -func (c *Client) isIndexTemplateExist(config types.ElasticsearchOutputConfig) (bool, error) { - clientCopy := c - var err error - u, err := url.Parse(fmt.Sprintf("%s/_index_template/falco", config.HostPort)) - if err != nil { - return false, err - } - clientCopy.EndpointURL = u - if err := clientCopy.Get(); err != nil { - if err.Error() == "resource not found" { - return false, nil - } - } - return true, nil -} - // setElasticSearchErrorMetrics set the error stats func (c *Client) setElasticSearchErrorMetrics() { go c.CountMetric(Outputs, 1, []string{"output:elasticsearch", "status:error"}) c.Stats.Elasticsearch.Add(Error, 1) c.PromStats.Outputs.With(map[string]string{"destination": "elasticsearch", "status": Error}).Inc() } + +func (c *Client) WatchElasticsearchPostAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + fmt.Println("discord running") + for AlertRunning { + select { + case resp := <-conn: + fmt.Println("response \n", resp) + c.ElasticsearchPost(resp) + } + } + fmt.Println("discord stopped") + return nil +} diff --git a/outputs/fission.go b/outputs/fission.go index a8bcb3c8e..c69fe2841 100644 --- a/outputs/fission.go +++ b/outputs/fission.go @@ -5,7 +5,6 @@ package outputs import ( "context" "encoding/json" - "fmt" "log" "strconv" @@ -46,27 +45,31 @@ func NewFissionClient(config *types.Configuration, stats *types.Statistics, prom KubernetesClient: clientset, }, nil } - - endpointUrl := fmt.Sprintf("http://%s.%s.svc.cluster.local:%d/fission-function/%s", config.Fission.RouterService, config.Fission.RouterNamespace, config.Fission.RouterPort, config.Fission.Function) initClientArgs := &types.InitClientArgs{ Config: config, Stats: stats, DogstatsdClient: dogstatsdClient, PromStats: promStats, - StatsdClient: statsdClient, } - - return NewClient(Fission, endpointUrl, config.Fission.MutualTLS, config.Fission.CheckCert, *initClientArgs) + return NewClient( + Fission, + "http://"+config.Fission.RouterService+"."+config.Fission.RouterNamespace+ + ".svc.cluster.local:"+strconv.Itoa(config.Fission.RouterPort)+ + "/fission-function/"+config.Fission.Function, + config.Fission.MutualTLS, + config.Fission.CheckCert, + *initClientArgs, + ) } // FissionCall . -func (c *Client) FissionCall(falcopayload types.FalcoPayload) { +func (c *Client) FissionCall(kubearmorpayload types.KubearmorPayload) { c.Stats.Fission.Add(Total, 1) if c.Config.Fission.KubeConfig != "" { - str, _ := json.Marshal(falcopayload) - req := c.KubernetesClient.CoreV1().RESTClient().Post().AbsPath(APIv1Namespaces + - c.Config.Fission.RouterNamespace + ServicesPath + c.Config.Fission.RouterService + + str, _ := json.Marshal(kubearmorpayload) + req := c.KubernetesClient.CoreV1().RESTClient().Post().AbsPath("/api/v1/namespaces/" + + c.Config.Fission.RouterNamespace + "/services/" + c.Config.Fission.RouterService + ":" + strconv.Itoa(c.Config.Fission.RouterPort) + "/proxy/" + "/fission-function/" + c.Config.Fission.Function).Body(str) req.SetHeader(FissionEventIDKey, uuid.New().String()) @@ -89,7 +92,7 @@ func (c *Client) FissionCall(falcopayload types.FalcoPayload) { c.AddHeader(FissionEventIDKey, uuid.New().String()) c.ContentType = FissionContentType - err := c.Post(falcopayload) + err := c.Post(kubearmorpayload) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:Fission", "status:error"}) c.Stats.Fission.Add(Error, 1) diff --git a/outputs/gcp.go b/outputs/gcp.go index c10c1e1cf..e022687aa 100644 --- a/outputs/gcp.go +++ b/outputs/gcp.go @@ -108,10 +108,10 @@ func NewGCPClient(config *types.Configuration, stats *types.Statistics, promStat } // GCPCallCloudFunction calls the given Cloud Function -func (c *Client) GCPCallCloudFunction(falcopayload types.FalcoPayload) { +func (c *Client) GCPCallCloudFunction(kubearmorpayload types.KubearmorPayload) { c.Stats.GCPCloudFunctions.Add(Total, 1) - payload, _ := json.Marshal(falcopayload) + payload, _ := json.Marshal(kubearmorpayload) data := string(payload) result, err := c.GCPCloudFunctionsClient.CallFunction(context.Background(), &gcpfunctionspb.CallFunctionRequest{ @@ -135,10 +135,10 @@ func (c *Client) GCPCallCloudFunction(falcopayload types.FalcoPayload) { } // GCPPublishTopic sends a message to a GCP PubSub Topic -func (c *Client) GCPPublishTopic(falcopayload types.FalcoPayload) { +func (c *Client) GCPPublishTopic(kubearmorpayload types.KubearmorPayload) { c.Stats.GCPPubSub.Add(Total, 1) - payload, _ := json.Marshal(falcopayload) + payload, _ := json.Marshal(kubearmorpayload) message := &pubsub.Message{ Data: payload, Attributes: c.Config.GCP.PubSub.CustomAttributes, @@ -162,10 +162,10 @@ func (c *Client) GCPPublishTopic(falcopayload types.FalcoPayload) { } // UploadGCS upload payload to -func (c *Client) UploadGCS(falcopayload types.FalcoPayload) { +func (c *Client) UploadGCS(kubearmorpayload types.KubearmorPayload) { c.Stats.GCPStorage.Add(Total, 1) - payload, _ := json.Marshal(falcopayload) + payload, _ := json.Marshal(kubearmorpayload) prefix := "" t := time.Now() @@ -175,7 +175,8 @@ func (c *Client) UploadGCS(falcopayload types.FalcoPayload) { key := fmt.Sprintf("%s/%s/%s.json", prefix, t.Format("2006-01-02"), t.Format(time.RFC3339Nano)) bucketWriter := c.GCSStorageClient.Bucket(c.Config.GCP.Storage.Bucket).Object(key).NewWriter(context.Background()) - n, err := bucketWriter.Write(payload) + defer bucketWriter.Close() + _, err := bucketWriter.Write(payload) if err != nil { log.Printf("[ERROR] : GCPStorage - %v - %v\n", "Error while Uploading message", err.Error()) c.Stats.GCPStorage.Add(Error, 1) @@ -183,20 +184,6 @@ func (c *Client) UploadGCS(falcopayload types.FalcoPayload) { c.PromStats.Outputs.With(map[string]string{"destination": "gcpstorage", "status": Error}).Inc() return } - if n == 0 { - log.Printf("[ERROR] : GCPStorage - %v\n", "Empty payload uploaded") - c.Stats.GCPStorage.Add(Error, 1) - go c.CountMetric("outputs", 1, []string{"output:gcpstorage", "status:error"}) - c.PromStats.Outputs.With(map[string]string{"destination": "gcpstorage", "status": Error}).Inc() - return - } - if err := bucketWriter.Close(); err != nil { - log.Printf("[ERROR] : GCPStorage - %v - %v\n", "Error while closing the writer", err.Error()) - c.Stats.GCPStorage.Add(Error, 1) - go c.CountMetric("outputs", 1, []string{"output:gcpstorage", "status:error"}) - c.PromStats.Outputs.With(map[string]string{"destination": "gcpstorage", "status": Error}).Inc() - return - } log.Printf("[INFO] : GCPStorage - Upload to bucket OK \n") c.Stats.GCPStorage.Add(OK, 1) diff --git a/outputs/gcpcloudrun.go b/outputs/gcpcloudrun.go index 34247d8ea..376eba32e 100644 --- a/outputs/gcpcloudrun.go +++ b/outputs/gcpcloudrun.go @@ -9,16 +9,16 @@ import ( ) // CloudRunFunctionPost call Cloud Function -func (c *Client) CloudRunFunctionPost(falcopayload types.FalcoPayload) { +func (c *Client) CloudRunFunctionPost(kubearmorpayload types.KubearmorPayload) { c.Stats.GCPCloudRun.Add(Total, 1) if c.Config.GCP.CloudRun.JWT != "" { c.httpClientLock.Lock() defer c.httpClientLock.Unlock() - c.AddHeader(AuthorizationHeaderKey, Bearer+" "+c.Config.GCP.CloudRun.JWT) + c.AddHeader(AuthorizationHeaderKey, "Bearer "+c.Config.GCP.CloudRun.JWT) } - err := c.Post(falcopayload) + err := c.Post(kubearmorpayload) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:gcpcloudrun", "status:error"}) c.Stats.GCPCloudRun.Add(Error, 1) diff --git a/outputs/googlechat.go b/outputs/googlechat.go index 6830a6ed3..21f782273 100644 --- a/outputs/googlechat.go +++ b/outputs/googlechat.go @@ -4,9 +4,8 @@ package outputs import ( "bytes" + "fmt" "log" - "sort" - "strings" "github.com/falcosecurity/falcosidekick/types" ) @@ -39,13 +38,13 @@ type googlechatPayload struct { Cards []card `json:"cards,omitempty"` } -func newGooglechatPayload(falcopayload types.FalcoPayload, config *types.Configuration) googlechatPayload { +func newGooglechatPayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) googlechatPayload { var messageText string widgets := []widget{} if config.Googlechat.MessageFormatTemplate != nil { buf := &bytes.Buffer{} - if err := config.Googlechat.MessageFormatTemplate.Execute(buf, falcopayload); err != nil { + if err := config.Googlechat.MessageFormatTemplate.Execute(buf, kubearmorpayload); err != nil { log.Printf("[ERROR] : GoogleChat - Error expanding Google Chat message %v", err) } else { messageText = buf.String() @@ -58,33 +57,23 @@ func newGooglechatPayload(falcopayload types.FalcoPayload, config *types.Configu } } - widgets = append(widgets, widget{KeyValue: keyValue{"rule", falcopayload.Rule}}) - widgets = append(widgets, widget{KeyValue: keyValue{"priority", falcopayload.Priority.String()}}) - widgets = append(widgets, widget{KeyValue: keyValue{"source", falcopayload.Source}}) - if falcopayload.Hostname != "" { - widgets = append(widgets, widget{KeyValue: keyValue{Hostname, falcopayload.Hostname}}) - } - - for _, i := range getSortedStringKeys(falcopayload.OutputFields) { + for _, i := range getSortedStringKeys(kubearmorpayload.OutputFields) { widgets = append(widgets, widget{ KeyValue: keyValue{ TopLabel: i, - Content: falcopayload.OutputFields[i].(string), + Content: fmt.Sprint(kubearmorpayload.OutputFields[i]), }, }) } - if len(falcopayload.Tags) != 0 { - sort.Strings(falcopayload.Tags) - widgets = append(widgets, widget{ - KeyValue: keyValue{ - TopLabel: "tags", - Content: strings.Join(falcopayload.Tags, ", "), - }, - }) + widgets = append(widgets, widget{KeyValue: keyValue{"priority", kubearmorpayload.EventType}}) + widgets = append(widgets, widget{KeyValue: keyValue{"source pod", kubearmorpayload.OutputFields["PodName"].(string)}}) + + if kubearmorpayload.Hostname != "" { + widgets = append(widgets, widget{KeyValue: keyValue{Hostname, kubearmorpayload.Hostname}}) } - widgets = append(widgets, widget{KeyValue: keyValue{"time", falcopayload.Time.String()}}) + widgets = append(widgets, widget{KeyValue: keyValue{"time", fmt.Sprint(kubearmorpayload.Timestamp)}}) return googlechatPayload{ Text: messageText, @@ -99,10 +88,10 @@ func newGooglechatPayload(falcopayload types.FalcoPayload, config *types.Configu } // GooglechatPost posts event to Google Chat -func (c *Client) GooglechatPost(falcopayload types.FalcoPayload) { +func (c *Client) GooglechatPost(kubearmorpayload types.KubearmorPayload) { c.Stats.GoogleChat.Add(Total, 1) - err := c.Post(newGooglechatPayload(falcopayload, c.Config)) + err := c.Post(newGooglechatPayload(kubearmorpayload, c.Config)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:googlechat", "status:error"}) c.Stats.GoogleChat.Add(Error, 1) diff --git a/outputs/googlechat_test.go b/outputs/googlechat_test.go index 5d35d4979..d748fdac1 100644 --- a/outputs/googlechat_test.go +++ b/outputs/googlechat_test.go @@ -69,7 +69,7 @@ func TestNewGoogleChatPayload(t *testing.T) { }, } - var f types.FalcoPayload + var f types.KubearmorPayload require.Nil(t, json.Unmarshal([]byte(falcoTestInput), &f)) config := &types.Configuration{ Googlechat: types.GooglechatConfig{}, diff --git a/outputs/gotify.go b/outputs/gotify.go index 015b9c716..01c72d6ac 100644 --- a/outputs/gotify.go +++ b/outputs/gotify.go @@ -42,16 +42,16 @@ type gotifyPayload struct { Extras map[string]map[string]string `json:"extras"` } -func newGotifyPayload(falcopayload types.FalcoPayload, config *types.Configuration) gotifyPayload { +func newGotifyPayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) gotifyPayload { g := gotifyPayload{ - Title: "[Falco] [" + falcopayload.Priority.String() + "] " + falcopayload.Rule, - Priority: int(types.Priority(falcopayload.Priority.String())), + Title: "[Kubearmor] [" + kubearmorpayload.EventType + "] ", + Priority: int(types.Priority(kubearmorpayload.EventType)), Extras: map[string]map[string]string{ "client::display": { "contentType": "text/markdown", }, }, - Message: falcopayload.Output, + //Message: kubearmorpayload.Output, } var ttmpl *textTemplate.Template @@ -63,14 +63,14 @@ func newGotifyPayload(falcopayload types.FalcoPayload, config *types.Configurati case Plaintext, Text: format = "plaintext" ttmpl, _ = textTemplate.New("gotify").Parse(gotifyTextTmpl) - err = ttmpl.Execute(&outtext, falcopayload) + err = ttmpl.Execute(&outtext, kubearmorpayload) case JSON: format = "plaintext" - messageBytes, err = json.Marshal(falcopayload) + messageBytes, err = json.Marshal(kubearmorpayload) default: format = "markdown" ttmpl, _ = textTemplate.New("gotify").Parse(gotifyMarkdownTmpl) - err = ttmpl.Execute(&outtext, falcopayload) + err = ttmpl.Execute(&outtext, kubearmorpayload) } if err != nil { log.Printf("[ERROR] : Gotify - %v\n", err) @@ -90,7 +90,7 @@ func newGotifyPayload(falcopayload types.FalcoPayload, config *types.Configurati } // GotifyPost posts event to Gotify -func (c *Client) GotifyPost(falcopayload types.FalcoPayload) { +func (c *Client) GotifyPost(kubearmorpayload types.KubearmorPayload) { c.Stats.Gotify.Add(Total, 1) if c.Config.Gotify.Token != "" { @@ -99,7 +99,7 @@ func (c *Client) GotifyPost(falcopayload types.FalcoPayload) { c.AddHeader("X-Gotify-Key", c.Config.Gotify.Token) } - err := c.Post(newGotifyPayload(falcopayload, c.Config)) + err := c.Post(newGotifyPayload(kubearmorpayload, c.Config)) if err != nil { c.setGotifyErrorMetrics() log.Printf("[ERROR] : Gotify - %v\n", err) diff --git a/outputs/grafana.go b/outputs/grafana.go index cc4afd63a..435f9abaf 100644 --- a/outputs/grafana.go +++ b/outputs/grafana.go @@ -7,6 +7,7 @@ import ( "log" "github.com/falcosecurity/falcosidekick/types" + "github.com/google/uuid" ) type grafanaPayload struct { @@ -28,30 +29,26 @@ type grafanaOnCallPayload struct { // The Content-Type to send along with the request const GrafanaContentType = "application/json" -func newGrafanaPayload(falcopayload types.FalcoPayload, config *types.Configuration) grafanaPayload { +func newGrafanaPayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) grafanaPayload { tags := []string{ - "falco", - falcopayload.Priority.String(), - falcopayload.Rule, - falcopayload.Source, + "kubearmor", + kubearmorpayload.EventType, } - if falcopayload.Hostname != "" { - tags = append(tags, falcopayload.Hostname) + if kubearmorpayload.Hostname != "" { + tags = append(tags, kubearmorpayload.Hostname) } if config.Grafana.AllFieldsAsTags { - for _, i := range falcopayload.OutputFields { - tags = append(tags, fmt.Sprintf("%v", i)) - } - if len(falcopayload.Tags) != 0 { - tags = append(tags, falcopayload.Tags...) + for key, i := range kubearmorpayload.OutputFields { + s := key + ": " + fmt.Sprint(i) + tags = append(tags, s) } } g := grafanaPayload{ - Text: falcopayload.Output, - Time: falcopayload.Time.UnixNano() / 1000000, - TimeEnd: falcopayload.Time.UnixNano() / 1000000, + Text: kubearmorpayload.EventType + "for pod" + kubearmorpayload.OutputFields["PodName"].(string), + Time: kubearmorpayload.Timestamp / 1000000, + TimeEnd: kubearmorpayload.Timestamp / 1000000, Tags: tags, } @@ -65,27 +62,27 @@ func newGrafanaPayload(falcopayload types.FalcoPayload, config *types.Configurat return g } -func newGrafanaOnCallPayload(falcopayload types.FalcoPayload) grafanaOnCallPayload { +func newGrafanaOnCallPayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) grafanaOnCallPayload { return grafanaOnCallPayload{ - AlertUID: falcopayload.UUID, - Title: fmt.Sprintf("[%v] %v", falcopayload.Priority, falcopayload.Rule), + AlertUID: kubearmorpayload.OutputFields["UID"].(string), + Title: fmt.Sprintf("[%v] %v", kubearmorpayload.EventType, kubearmorpayload.OutputFields["PodName"].(string)), State: "alerting", - Message: falcopayload.Output, + //Message: kubearmorpayload.Output, } } // GrafanaPost posts event to grafana -func (c *Client) GrafanaPost(falcopayload types.FalcoPayload) { +func (c *Client) GrafanaPost(kubearmorpayload types.KubearmorPayload) { c.Stats.Grafana.Add(Total, 1) c.ContentType = GrafanaContentType c.httpClientLock.Lock() defer c.httpClientLock.Unlock() - c.AddHeader("Authorization", Bearer+" "+c.Config.Grafana.APIKey) + c.AddHeader("Authorization", "Bearer "+c.Config.Grafana.APIKey) for i, j := range c.Config.Grafana.CustomHeaders { c.AddHeader(i, j) } - err := c.Post(newGrafanaPayload(falcopayload, c.Config)) + err := c.Post(newGrafanaPayload(kubearmorpayload, c.Config)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:grafana", "status:error"}) c.Stats.Grafana.Add(Error, 1) @@ -100,7 +97,7 @@ func (c *Client) GrafanaPost(falcopayload types.FalcoPayload) { } // GrafanaOnCallPost posts event to grafana onCall -func (c *Client) GrafanaOnCallPost(falcopayload types.FalcoPayload) { +func (c *Client) GrafanaOnCallPost(kubearmorpayload types.KubearmorPayload) { c.Stats.GrafanaOnCall.Add(Total, 1) c.ContentType = GrafanaContentType c.httpClientLock.Lock() @@ -109,7 +106,7 @@ func (c *Client) GrafanaOnCallPost(falcopayload types.FalcoPayload) { c.AddHeader(i, j) } - err := c.Post(newGrafanaOnCallPayload(falcopayload)) + err := c.Post(newGrafanaOnCallPayload(kubearmorpayload, c.Config)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:grafanaoncall", "status:error"}) c.Stats.Grafana.Add(Error, 1) @@ -122,3 +119,44 @@ func (c *Client) GrafanaOnCallPost(falcopayload types.FalcoPayload) { c.Stats.Grafana.Add(OK, 1) c.PromStats.Outputs.With(map[string]string{"destination": "grafanaoncall", "status": OK}).Inc() } + +func (c *Client) WatchGrafanaPostAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + Running := true + fmt.Println("discord running") + for Running { + select { + case resp := <-conn: + fmt.Println("response \n", resp) + c.GrafanaPost(resp) + } + } + fmt.Println("discord stopped") + return nil +} + +func (c *Client) WatchGrafanaOnCallPostAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + fmt.Println("discord running") + for AlertRunning { + select { + case resp := <-conn: + fmt.Println("response \n", resp) + c.GrafanaOnCallPost(resp) + } + } + fmt.Println("discord stopped") + return nil +} diff --git a/outputs/influxdb.go b/outputs/influxdb.go index 2d6f206c9..b1a035e1f 100644 --- a/outputs/influxdb.go +++ b/outputs/influxdb.go @@ -3,41 +3,39 @@ package outputs import ( + "fmt" "log" "strings" "github.com/falcosecurity/falcosidekick/types" + "github.com/google/uuid" ) type influxdbPayload string -func newInfluxdbPayload(falcopayload types.FalcoPayload) influxdbPayload { - s := "events,rule=" + strings.Replace(falcopayload.Rule, " ", "_", -1) + ",priority=" + falcopayload.Priority.String() + ",source=" + falcopayload.Source +func newInfluxdbPayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) influxdbPayload { + s := "events,rule=" + strings.Replace(kubearmorpayload.EventType, " ", "_", -1) + ",priority=" + kubearmorpayload.EventType + ",source=" + kubearmorpayload.OutputFields["PodName"].(string) - for i, j := range falcopayload.OutputFields { + for i, j := range kubearmorpayload.OutputFields { switch v := j.(type) { case string: s += "," + i + "=" + strings.Replace(v, " ", "_", -1) default: + vv := fmt.Sprint(v) + s += "," + i + "=" + strings.Replace(vv, " ", "_", -1) continue } } - if falcopayload.Hostname != "" { - s += "," + Hostname + "=" + falcopayload.Hostname + if kubearmorpayload.Hostname != "" { + s += "," + Hostname + "=" + kubearmorpayload.Hostname } - if len(falcopayload.Tags) != 0 { - s += ",tags=" + strings.Join(falcopayload.Tags, "_") - } - - s += " value=\"" + falcopayload.Output + "\"" - return influxdbPayload(s) } // InfluxdbPost posts event to InfluxDB -func (c *Client) InfluxdbPost(falcopayload types.FalcoPayload) { +func (c *Client) InfluxdbPost(kubearmorpayload types.KubearmorPayload) { c.Stats.Influxdb.Add(Total, 1) c.httpClientLock.Lock() @@ -48,7 +46,7 @@ func (c *Client) InfluxdbPost(falcopayload types.FalcoPayload) { c.AddHeader("Authorization", "Token "+c.Config.Influxdb.Token) } - err := c.Post(newInfluxdbPayload(falcopayload)) + err := c.Post(newInfluxdbPayload(kubearmorpayload, c.Config)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:influxdb", "status:error"}) c.Stats.Influxdb.Add(Error, 1) @@ -62,3 +60,23 @@ func (c *Client) InfluxdbPost(falcopayload types.FalcoPayload) { c.Stats.Influxdb.Add(OK, 1) c.PromStats.Outputs.With(map[string]string{"destination": "influxdb", "status": OK}).Inc() } + +func (c *Client) WatchInfluxdbPostAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + fmt.Println("discord running") + for AlertRunning { + select { + case resp := <-conn: + fmt.Println("response \n", resp) + c.InfluxdbPost(resp) + } + } + fmt.Println("discord stopped") + return nil +} diff --git a/outputs/influxdb_test.go b/outputs/influxdb_test.go index 67ec0fc5f..9be5e9456 100644 --- a/outputs/influxdb_test.go +++ b/outputs/influxdb_test.go @@ -13,10 +13,10 @@ import ( func TestNewInfluxdbPayload(t *testing.T) { expectedOutput := `"events,rule=Test_rule,priority=Debug,source=syscalls,proc.name=falcosidekick,hostname=test-host,tags=test_example value=\"This is a test from falcosidekick\""` - var f types.FalcoPayload + var f types.KubearmorPayload require.Nil(t, json.Unmarshal([]byte(falcoTestInput), &f)) - influxdbPayload, err := json.Marshal(newInfluxdbPayload(f)) + influxdbPayload, err := json.Marshal(newInfluxdbPayload(f, &types.Configuration{})) require.Nil(t, err) require.Equal(t, string(influxdbPayload), expectedOutput) diff --git a/outputs/jira.go b/outputs/jira.go new file mode 100644 index 000000000..a2926c4c8 --- /dev/null +++ b/outputs/jira.go @@ -0,0 +1,90 @@ +package outputs + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "strings" + + "github.com/andygrunwald/go-jira" +) + +// PrettyString formats the string +func PrettyString(str string) (string, error) { + var prettyJSON bytes.Buffer + if err := json.Indent(&prettyJSON, []byte(str), "", " "); err != nil { + return "", err + } + return prettyJSON.String(), nil +} + +// SendMsgtoJira to send message to jira integration +func SendMsgtoJira(ctx context.Context, value string, result []byte) error { + splitval := strings.Split(value, ",") + + sendAlert := string(result) + + resJson, err := PrettyString(sendAlert) + if err != nil { + fmt.Println("Err in creating pretty res_json in Jira " + err.Error()) + return errors.New("Err in creating pretty res_json in Jira") + } + if len(splitval) == 7 { + + jiraClient, err := createClient(splitval[4], splitval[5], splitval[1]) + if err != nil { + fmt.Println("Err in creating a new Jira Client " + err.Error()) + return errors.New("Err in creating a new Jira Client ") + } + + i := jira.Issue{ + Fields: &jira.IssueFields{ + Description: resJson, + Type: jira.IssueType{ + Name: splitval[3], + }, + Project: jira.Project{ + Key: splitval[2], + }, + Summary: splitval[0], + }, + } + issue, _, err := jiraClient.Issue.Create(&i) + if err != nil { + fmt.Println("Failed to create Jira Ticket " + err.Error()) + return errors.New("Failed to create Jira Ticket ") + } + fmt.Println("Jira Ticket Created Successfully :> %v", issue) + return nil + } + fmt.Println("unable to get the required value in send msg to jira ") + return errors.New("unable to get the required value for sending message to jira ") +} + +func createClient(userEmail, token, siteUrl string) (*jira.Client, error) { + tp := jira.BasicAuthTransport{ + Username: userEmail, + Password: token, + } + + jiraClient, err := jira.NewClient(tp.Client(), siteUrl) + if err != nil { + fmt.Println("Err in creating a new Jira Client " + err.Error()) + return nil, errors.New("Err in creating a new Jira Client ") + } + return jiraClient, nil +} + +// ValidateToken Validate token that is being sent from UI +func ValidateToken(userEmail, token, siteUrl, project string) bool { + jiraClient, err := createClient(userEmail, token, siteUrl) + if err != nil { + return false + } + if _, _, err := jiraClient.Project.Get(project); err != nil { + return false + } + return true +} diff --git a/outputs/kafka.go b/outputs/kafka.go index 5659c1ba9..6a220b016 100644 --- a/outputs/kafka.go +++ b/outputs/kafka.go @@ -14,6 +14,7 @@ import ( "time" "github.com/DataDog/datadog-go/statsd" + "github.com/google/uuid" "github.com/segmentio/kafka-go" "github.com/segmentio/kafka-go/sasl/plain" "github.com/segmentio/kafka-go/sasl/scram" @@ -145,10 +146,10 @@ func NewKafkaClient(config *types.Configuration, stats *types.Statistics, promSt } // KafkaProduce sends a message to a Apach Kafka Topic -func (c *Client) KafkaProduce(falcopayload types.FalcoPayload) { +func (c *Client) KafkaProduce(kubearmorpayload types.KubearmorPayload) { c.Stats.Kafka.Add(Total, 1) - falcoMsg, err := json.Marshal(falcopayload) + Msg, err := json.Marshal(kubearmorpayload) if err != nil { c.incrKafkaErrorMetrics(1) log.Printf("[ERROR] : Kafka - %v - %v\n", "failed to marshalling message", err.Error()) @@ -156,7 +157,7 @@ func (c *Client) KafkaProduce(falcopayload types.FalcoPayload) { } kafkaMsg := kafka.Message{ - Value: falcoMsg, + Value: Msg, } // Errors are logged/captured via handleKafkaCompletion function, ignore here @@ -195,3 +196,23 @@ func (c *Client) incrKafkaErrorMetrics(add int) { c.Stats.Kafka.Add(Error, int64(add)) c.PromStats.Outputs.With(map[string]string{"destination": "kafka", "status": Error}).Add(float64(add)) } + +func (c *Client) WatchKafkaProduceAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + fmt.Println("discord running") + for AlertRunning { + select { + case resp := <-conn: + fmt.Println("response \n", resp) + c.KafkaProduce(resp) + } + } + fmt.Println("discord stopped") + return nil +} diff --git a/outputs/kafkarest.go b/outputs/kafkarest.go index 27d10bbc8..b880ad1a3 100644 --- a/outputs/kafkarest.go +++ b/outputs/kafkarest.go @@ -3,64 +3,102 @@ package outputs import ( - "encoding/base64" + "context" "encoding/json" - "fmt" "log" + "strconv" + "github.com/DataDog/datadog-go/statsd" "github.com/falcosecurity/falcosidekick/types" + "github.com/google/uuid" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" ) -// Records are the items inside the request wrapper -type Records struct { - Value string `json:"value"` -} +// Some constant strings to use in request headers +const KubelessEventIDKey = "event-id" +const KubelessUserAgentKey = "User-Agent" +const KubelessEventTypeKey = "event-type" +const KubelessEventNamespaceKey = "event-namespace" +const KubelessEventTypeValue = "kubearmor" +const KubelessContentType = "application/json" -// KafkaRestPayload is the request wrapper for Kafka Rest -type KafkaRestPayload struct { - Records []Records `json:"records"` +// NewKubelessClient returns a new output.Client for accessing Kubernetes. +func NewKubelessClient(config *types.Configuration, stats *types.Statistics, promStats *types.PromStatistics, statsdClient, dogstatsdClient *statsd.Client) (*Client, error) { + if config.Kubeless.Kubeconfig != "" { + restConfig, err := clientcmd.BuildConfigFromFlags("", config.Kubeless.Kubeconfig) + if err != nil { + return nil, err + } + clientset, err := kubernetes.NewForConfig(restConfig) + if err != nil { + return nil, err + } + return &Client{ + OutputType: "Kubeless", + Config: config, + Stats: stats, + PromStats: promStats, + StatsdClient: statsdClient, + DogstatsdClient: dogstatsdClient, + KubernetesClient: clientset, + }, nil + } + return NewClient( + "Kubeless", + "http://"+config.Kubeless.Function+"."+config.Kubeless.Namespace+".svc.cluster.local:"+strconv.Itoa(config.Kubeless.Port), + config.Kubeless.MutualTLS, + config.Kubeless.CheckCert, + config, + stats, + promStats, + statsdClient, + dogstatsdClient, + ) } -// KafkaRestPost posts event the Kafka Rest Proxy -func (c *Client) KafkaRestPost(falcopayload types.FalcoPayload) { - c.Stats.KafkaRest.Add(Total, 1) +// KubelessCall . +func (c *Client) KubelessCall(kubearmorpayload types.KubearmorPayload) { + c.Stats.Kubeless.Add(Total, 1) - var version int - switch c.Config.KafkaRest.Version { - case 2: - version = c.Config.KafkaRest.Version - case 1: - version = c.Config.KafkaRest.Version - default: - version = 2 - } - falcoMsg, err := json.Marshal(falcopayload) - if err != nil { - c.Stats.KafkaRest.Add(Error, 1) - c.PromStats.Outputs.With(map[string]string{"destination": "kafkarest", "status": Error}).Inc() - log.Printf("[ERROR] : Kafka Rest - %v - %v\n", "failed to marshalling message", err.Error()) - return - } + if c.Config.Kubeless.Kubeconfig != "" { + str, _ := json.Marshal(kubearmorpayload) + req := c.KubernetesClient.CoreV1().RESTClient().Post().AbsPath("/api/v1/namespaces/" + c.Config.Kubeless.Namespace + "/services/" + c.Config.Kubeless.Function + ":" + strconv.Itoa(c.Config.Kubeless.Port) + "/proxy/").Body(str) + req.SetHeader(KubelessEventIDKey, uuid.New().String()) + req.SetHeader(ContentTypeHeaderKey, KubelessContentType) + req.SetHeader(UserAgentHeaderKey, UserAgentHeaderValue) + req.SetHeader(KubelessEventTypeKey, KubelessEventTypeValue) + req.SetHeader(KubelessEventNamespaceKey, c.Config.Kubeless.Namespace) - c.ContentType = fmt.Sprintf("application/vnd.kafka.binary.v%d+json", version) + res := req.Do(context.TODO()) + rawbody, err := res.Raw() + if err != nil { + go c.CountMetric(Outputs, 1, []string{"output:kubeless", "status:error"}) + c.Stats.Kubeless.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "kubeless", "status": Error}).Inc() + log.Printf("[ERROR] : Kubeless - %v\n", err) + return + } + log.Printf("[INFO] : Kubeless - Function Response : %v\n", string(rawbody)) + } else { + c.httpClientLock.Lock() + defer c.httpClientLock.Unlock() + c.AddHeader(KubelessEventIDKey, uuid.New().String()) + c.AddHeader(KubelessEventTypeKey, KubelessEventTypeValue) + c.AddHeader(KubelessEventNamespaceKey, c.Config.Kubeless.Namespace) + c.ContentType = KubelessContentType - payload := KafkaRestPayload{ - Records: []Records{{ - Value: base64.StdEncoding.EncodeToString(falcoMsg), - }}, + err := c.Post(kubearmorpayload) + if err != nil { + go c.CountMetric(Outputs, 1, []string{"output:kubeless", "status:error"}) + c.Stats.Kubeless.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "kubeless", "status": Error}).Inc() + log.Printf("[ERROR] : Kubeless - %v\n", err) + return + } } - - err = c.Post(payload) - if err != nil { - go c.CountMetric(Outputs, 1, []string{"output:kafkarest", "status:error"}) - c.Stats.KafkaRest.Add(Error, 1) - c.PromStats.Outputs.With(map[string]string{"destination": "kafkarest", "status": Error}).Inc() - log.Printf("[ERROR] : Kafka Rest - %v\n", err.Error()) - return - } - - // Setting the success status - go c.CountMetric(Outputs, 1, []string{"output:kafkarest", "status:ok"}) - c.Stats.KafkaRest.Add(OK, 1) - c.PromStats.Outputs.With(map[string]string{"destination": "kafkarest", "status": OK}).Inc() + log.Printf("[INFO] : Kubeless - Call Function \"%v\" OK\n", c.Config.Kubeless.Function) + go c.CountMetric(Outputs, 1, []string{"output:kubeless", "status:ok"}) + c.Stats.Kubeless.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "kubeless", "status": OK}).Inc() } diff --git a/outputs/kubeless.go b/outputs/kubeless.go index 2a962a597..f8a635ee8 100644 --- a/outputs/kubeless.go +++ b/outputs/kubeless.go @@ -5,7 +5,6 @@ package outputs import ( "context" "encoding/json" - "fmt" "log" "strconv" @@ -22,7 +21,7 @@ const KubelessEventIDKey = "event-id" const KubelessUserAgentKey = "User-Agent" const KubelessEventTypeKey = "event-type" const KubelessEventNamespaceKey = "event-namespace" -const KubelessEventTypeValue = "falco" +const KubelessEventTypeValue = "kubearmor" const KubelessContentType = "application/json" // NewKubelessClient returns a new output.Client for accessing Kubernetes. @@ -46,25 +45,27 @@ func NewKubelessClient(config *types.Configuration, stats *types.Statistics, pro KubernetesClient: clientset, }, nil } - - endpointUrl := fmt.Sprintf("http://%s.%s.svc.cluster.local:%d", config.Kubeless.Function, config.Kubeless.Namespace, config.Kubeless.Port) initClientArgs := &types.InitClientArgs{ Config: config, Stats: stats, DogstatsdClient: dogstatsdClient, PromStats: promStats, - StatsdClient: statsdClient, } - - return NewClient("Kubeless", endpointUrl, config.Kubeless.MutualTLS, config.Kubeless.CheckCert, *initClientArgs) + return NewClient( + "Kubeless", + "http://"+config.Kubeless.Function+"."+config.Kubeless.Namespace+".svc.cluster.local:"+strconv.Itoa(config.Kubeless.Port), + config.Kubeless.MutualTLS, + config.Kubeless.CheckCert, + *initClientArgs, + ) } // KubelessCall . -func (c *Client) KubelessCall(falcopayload types.FalcoPayload) { +func (c *Client) KubelessCall(kubearmorpayload types.KubearmorPayload) { c.Stats.Kubeless.Add(Total, 1) if c.Config.Kubeless.Kubeconfig != "" { - str, _ := json.Marshal(falcopayload) + str, _ := json.Marshal(kubearmorpayload) req := c.KubernetesClient.CoreV1().RESTClient().Post().AbsPath("/api/v1/namespaces/" + c.Config.Kubeless.Namespace + "/services/" + c.Config.Kubeless.Function + ":" + strconv.Itoa(c.Config.Kubeless.Port) + "/proxy/").Body(str) req.SetHeader(KubelessEventIDKey, uuid.New().String()) req.SetHeader(ContentTypeHeaderKey, KubelessContentType) @@ -90,7 +91,7 @@ func (c *Client) KubelessCall(falcopayload types.FalcoPayload) { c.AddHeader(KubelessEventNamespaceKey, c.Config.Kubeless.Namespace) c.ContentType = KubelessContentType - err := c.Post(falcopayload) + err := c.Post(kubearmorpayload) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:kubeless", "status:error"}) c.Stats.Kubeless.Add(Error, 1) diff --git a/outputs/loki.go b/outputs/loki.go index c2228b657..ad8809946 100644 --- a/outputs/loki.go +++ b/outputs/loki.go @@ -5,7 +5,6 @@ package outputs import ( "fmt" "log" - "sort" "strings" "github.com/falcosecurity/falcosidekick/types" @@ -25,13 +24,12 @@ type lokiValue = []string // The Content-Type to send along with the request const LokiContentType = "application/json" -func newLokiPayload(falcopayload types.FalcoPayload, config *types.Configuration) lokiPayload { - s := make(map[string]string, 3+len(falcopayload.OutputFields)+len(config.Loki.ExtraLabelsList)+len(falcopayload.Tags)) - s["rule"] = falcopayload.Rule - s["source"] = falcopayload.Source - s["priority"] = falcopayload.Priority.String() +func newLokiPayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) lokiPayload { + s := make(map[string]string, 3+len(kubearmorpayload.OutputFields)+len(config.Loki.ExtraLabelsList)) + s["source"] = kubearmorpayload.OutputFields["PodName"].(string) + s["priority"] = kubearmorpayload.EventType - for i, j := range falcopayload.OutputFields { + for i, j := range kubearmorpayload.OutputFields { switch v := j.(type) { case string: for k := range config.Customfields { @@ -45,61 +43,54 @@ func newLokiPayload(falcopayload types.FalcoPayload, config *types.Configuration } } default: - continue - } - } + vv := fmt.Sprint(v) + for k := range config.Customfields { + if i == k { + s[strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(i, ".", ""), "]", ""), "[", "")] = strings.ReplaceAll(vv, "\"", "") + } + } + for _, k := range config.Loki.ExtraLabelsList { + if i == k { + s[strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(i, ".", ""), "]", ""), "[", "")] = strings.ReplaceAll(vv, "\"", "") + } + } - if falcopayload.Hostname != "" { - s[Hostname] = falcopayload.Hostname + } } - if len(falcopayload.Tags) != 0 { - sort.Strings(falcopayload.Tags) - s["tags"] = strings.Join(falcopayload.Tags, ",") + if kubearmorpayload.Hostname != "" { + s[Hostname] = kubearmorpayload.Hostname } return lokiPayload{Streams: []lokiStream{ { Stream: s, - Values: []lokiValue{[]string{fmt.Sprintf("%v", falcopayload.Time.UnixNano()), falcopayload.Output}}, + Values: []lokiValue{[]string{fmt.Sprintf("%v", kubearmorpayload.Timestamp)}}, }, }} } -func (c *Client) configureTenant() { +// LokiPost posts event to Loki +func (c *Client) LokiPost(kubearmorpayload types.KubearmorPayload) { + c.Stats.Loki.Add(Total, 1) + c.ContentType = LokiContentType if c.Config.Loki.Tenant != "" { c.httpClientLock.Lock() defer c.httpClientLock.Unlock() c.AddHeader("X-Scope-OrgID", c.Config.Loki.Tenant) } -} -func (c *Client) configureAuth() { if c.Config.Loki.User != "" && c.Config.Loki.APIKey != "" { c.httpClientLock.Lock() defer c.httpClientLock.Unlock() c.BasicAuth(c.Config.Loki.User, c.Config.Loki.APIKey) } -} -func (c *Client) configureCustomHeaders() { - c.httpClientLock.Lock() - defer c.httpClientLock.Unlock() for i, j := range c.Config.Loki.CustomHeaders { c.AddHeader(i, j) } -} - -// LokiPost posts event to Loki -func (c *Client) LokiPost(falcopayload types.FalcoPayload) { - c.Stats.Loki.Add(Total, 1) - c.ContentType = LokiContentType - - c.configureTenant() - c.configureAuth() - c.configureCustomHeaders() - err := c.Post(newLokiPayload(falcopayload, c.Config)) + err := c.Post(newLokiPayload(kubearmorpayload, c.Config)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:loki", "status:error"}) c.Stats.Loki.Add(Error, 1) diff --git a/outputs/loki_test.go b/outputs/loki_test.go index 29b4ab04d..507eb2e37 100644 --- a/outputs/loki_test.go +++ b/outputs/loki_test.go @@ -27,7 +27,7 @@ func TestNewLokiPayload(t *testing.T) { }, } - var f types.FalcoPayload + var f types.KubearmorPayload require.Nil(t, json.Unmarshal([]byte(falcoTestInput), &f)) output := newLokiPayload(f, &types.Configuration{}) diff --git a/outputs/mattermost.go b/outputs/mattermost.go index 6e5bf25a2..15d83719d 100644 --- a/outputs/mattermost.go +++ b/outputs/mattermost.go @@ -4,14 +4,13 @@ package outputs import ( "bytes" + "fmt" "log" - "sort" - "strings" "github.com/falcosecurity/falcosidekick/types" ) -func newMattermostPayload(falcopayload types.FalcoPayload, config *types.Configuration) slackPayload { +func newMattermostPayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) slackPayload { var ( messageText string attachments []slackAttachment @@ -21,46 +20,40 @@ func newMattermostPayload(falcopayload types.FalcoPayload, config *types.Configu ) if config.Mattermost.OutputFormat == All || config.Mattermost.OutputFormat == Fields || config.Mattermost.OutputFormat == "" { - field.Title = Rule - field.Value = falcopayload.Rule - field.Short = true - fields = append(fields, field) - if falcopayload.Hostname != "" { - field.Title = Hostname - field.Value = falcopayload.Hostname - field.Short = true - fields = append(fields, field) - } field.Title = Priority - field.Value = falcopayload.Priority.String() - field.Short = true - fields = append(fields, field) - field.Title = Source - field.Value = falcopayload.Source + field.Value = kubearmorpayload.EventType field.Short = true fields = append(fields, field) - if len(falcopayload.Tags) != 0 { - sort.Strings(falcopayload.Tags) - field.Title = Tags - field.Value = strings.Join(falcopayload.Tags, ", ") - field.Short = true - fields = append(fields, field) - } - for _, i := range getSortedStringKeys(falcopayload.OutputFields) { - field.Title = i - field.Value = falcopayload.OutputFields[i].(string) - if len([]rune(falcopayload.OutputFields[i].(string))) < 36 { - field.Short = true - } else { - field.Short = false + for _, i := range getSortedStringKeys(kubearmorpayload.OutputFields) { + j := kubearmorpayload.OutputFields[i] + switch v := j.(type) { + case string: + field.Title = i + field.Value = kubearmorpayload.OutputFields[i].(string) + if len([]rune(kubearmorpayload.OutputFields[i].(string))) < 36 { + field.Short = true + } else { + field.Short = false + } + fields = append(fields, field) + default: + vv := fmt.Sprint(v) + field.Title = i + field.Value = vv + if len([]rune(vv)) < 36 { + field.Short = true + } else { + field.Short = false + } + fields = append(fields, field) + } - fields = append(fields, field) } field.Title = Time field.Short = false - field.Value = falcopayload.Time.String() + field.Value = fmt.Sprint(kubearmorpayload.Timestamp) fields = append(fields, field) attachment.Footer = DefaultFooter @@ -69,15 +62,9 @@ func newMattermostPayload(falcopayload types.FalcoPayload, config *types.Configu } } - attachment.Fallback = falcopayload.Output - attachment.Fields = fields - if config.Mattermost.OutputFormat == All || config.Mattermost.OutputFormat == Text || config.Mattermost.OutputFormat == "" { - attachment.Text = falcopayload.Output - } - if config.Mattermost.MessageFormatTemplate != nil { buf := &bytes.Buffer{} - if err := config.Mattermost.MessageFormatTemplate.Execute(buf, falcopayload); err != nil { + if err := config.Mattermost.MessageFormatTemplate.Execute(buf, kubearmorpayload); err != nil { log.Printf("[ERROR] : Mattermost - Error expanding Mattermost message %v", err) } else { messageText = buf.String() @@ -85,23 +72,11 @@ func newMattermostPayload(falcopayload types.FalcoPayload, config *types.Configu } var color string - switch falcopayload.Priority { - case types.Emergency: - color = Red - case types.Alert: - color = Orange - case types.Critical: + switch kubearmorpayload.EventType { + case "Alert": color = Orange - case types.Error: - color = Red - case types.Warning: - color = Yellow - case types.Notice: - color = Lightcyan - case types.Informational: + case "log": color = LigthBlue - case types.Debug: - color = PaleCyan } attachment.Color = color @@ -114,7 +89,7 @@ func newMattermostPayload(falcopayload types.FalcoPayload, config *types.Configu s := slackPayload{ Text: messageText, - Username: config.Mattermost.Username, + Username: "Kubearmor", IconURL: iconURL, Attachments: attachments, } @@ -123,10 +98,10 @@ func newMattermostPayload(falcopayload types.FalcoPayload, config *types.Configu } // MattermostPost posts event to Mattermost -func (c *Client) MattermostPost(falcopayload types.FalcoPayload) { +func (c *Client) MattermostPost(kubearmorpayload types.KubearmorPayload) { c.Stats.Mattermost.Add(Total, 1) - err := c.Post(newMattermostPayload(falcopayload, c.Config)) + err := c.Post(newMattermostPayload(kubearmorpayload, c.Config)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:mattermost", "status:error"}) c.Stats.Mattermost.Add(Error, 1) diff --git a/outputs/mattermost_test.go b/outputs/mattermost_test.go index c3ed3b4bd..1195a611a 100644 --- a/outputs/mattermost_test.go +++ b/outputs/mattermost_test.go @@ -64,7 +64,7 @@ func TestMattermostPayload(t *testing.T) { }, } - var f types.FalcoPayload + var f types.KubearmorPayload require.Nil(t, json.Unmarshal([]byte(falcoTestInput), &f)) config := &types.Configuration{ Mattermost: types.MattermostOutputConfig{ diff --git a/outputs/mqtt.go b/outputs/mqtt.go index 8d8d7376e..69ee43173 100644 --- a/outputs/mqtt.go +++ b/outputs/mqtt.go @@ -4,6 +4,7 @@ package outputs import ( "crypto/tls" + "fmt" "log" "github.com/DataDog/datadog-go/statsd" @@ -19,14 +20,15 @@ func NewMQTTClient(config *types.Configuration, stats *types.Statistics, promSta options := mqtt.NewClientOptions() options.AddBroker(config.MQTT.Broker) - options.SetClientID("falcosidekick-" + uuid.NewString()[:6]) + options.SetClientID("kubearmor-" + uuid.NewString()[:6]) if config.MQTT.User != "" && config.MQTT.Password != "" { options.Username = config.MQTT.User options.Password = config.MQTT.Password } if !config.MQTT.CheckCert { + // #nosec G402 This is only set as a result of explicit configuration options.TLSConfig = &tls.Config{ - InsecureSkipVerify: true, // #nosec G402 This is only set as a result of explicit configuration + InsecureSkipVerify: true, } } options.OnConnectionLost = func(client mqtt.Client, err error) { @@ -47,7 +49,7 @@ func NewMQTTClient(config *types.Configuration, stats *types.Statistics, promSta } // MQTTPublish . -func (c *Client) MQTTPublish(falcopayload types.FalcoPayload) { +func (c *Client) MQTTPublish(kubearmorpayload types.KubearmorPayload) { c.Stats.MQTT.Add(Total, 1) t := c.MQTTClient.Connect() @@ -60,7 +62,7 @@ func (c *Client) MQTTPublish(falcopayload types.FalcoPayload) { return } defer c.MQTTClient.Disconnect(100) - if err := c.MQTTClient.Publish(c.Config.MQTT.Topic, byte(c.Config.MQTT.QOS), c.Config.MQTT.Retained, falcopayload.String()).Error(); err != nil { + if err := c.MQTTClient.Publish(c.Config.MQTT.Topic, byte(c.Config.MQTT.QOS), c.Config.MQTT.Retained, kubearmorpayload.String()).Error(); err != nil { go c.CountMetric(Outputs, 1, []string{"output:mqtt", "status:error"}) c.Stats.MQTT.Add(Error, 1) c.PromStats.Outputs.With(map[string]string{"destination": "mqtt", "status": Error}).Inc() @@ -73,3 +75,23 @@ func (c *Client) MQTTPublish(falcopayload types.FalcoPayload) { c.Stats.MQTT.Add(OK, 1) c.PromStats.Outputs.With(map[string]string{"destination": "mqtt", "status": OK}).Inc() } + +func (c *Client) WatchMQTTPublishAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + fmt.Println("discord running") + for AlertRunning { + select { + case resp := <-conn: + fmt.Println("response \n", resp) + c.MQTTPublish(resp) + } + } + fmt.Println("discord stopped") + return nil +} diff --git a/outputs/n8n.go b/outputs/n8n.go index 75c6d13c2..542f21ccb 100644 --- a/outputs/n8n.go +++ b/outputs/n8n.go @@ -9,7 +9,7 @@ import ( ) // N8NPost posts event to an URL -func (c *Client) N8NPost(falcopayload types.FalcoPayload) { +func (c *Client) N8NPost(kubearmorpayload types.KubearmorPayload) { c.Stats.N8N.Add(Total, 1) if c.Config.N8N.User != "" && c.Config.N8N.Password != "" { @@ -24,7 +24,7 @@ func (c *Client) N8NPost(falcopayload types.FalcoPayload) { c.AddHeader(c.Config.N8N.HeaderAuthName, c.Config.N8N.HeaderAuthValue) } - err := c.Post(falcopayload) + err := c.Post(kubearmorpayload) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:n8n", "status:error"}) c.Stats.N8N.Add(Error, 1) diff --git a/outputs/nats.go b/outputs/nats.go index a60164e2a..9b7f72e5d 100644 --- a/outputs/nats.go +++ b/outputs/nats.go @@ -7,7 +7,9 @@ import ( "log" "regexp" "strings" + "time" + "github.com/google/uuid" nats "github.com/nats-io/nats.go" "github.com/falcosecurity/falcosidekick/types" @@ -16,7 +18,7 @@ import ( var slugRegularExpression = regexp.MustCompile("[^a-z0-9]+") // NatsPublish publishes event to NATS -func (c *Client) NatsPublish(falcopayload types.FalcoPayload) { +func (c *Client) NatsPublish(kubearmorpayload types.KubearmorPayload) { c.Stats.Nats.Add(Total, 1) nc, err := nats.Connect(c.EndpointURL.String()) @@ -28,15 +30,14 @@ func (c *Client) NatsPublish(falcopayload types.FalcoPayload) { defer nc.Flush() defer nc.Close() - r := strings.Trim(slugRegularExpression.ReplaceAllString(strings.ToLower(falcopayload.Rule), "_"), "_") - j, err := json.Marshal(falcopayload) + j, err := json.Marshal(kubearmorpayload) if err != nil { c.setStanErrorMetrics() log.Printf("[ERROR] : STAN - %v\n", err.Error()) return } - err = nc.Publish("falco."+strings.ToLower(falcopayload.Priority.String())+"."+r, j) + err = nc.Publish("kubearmor."+strings.ToLower(kubearmorpayload.EventType), j) if err != nil { c.setNatsErrorMetrics() log.Printf("[ERROR] : NATS - %v\n", err) @@ -55,3 +56,26 @@ func (c *Client) setNatsErrorMetrics() { c.Stats.Nats.Add(Error, 1) c.PromStats.Outputs.With(map[string]string{"destination": "nats", "status": Error}).Inc() } + +func (c *Client) WatchNatsPublishAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.NatsPublish(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} diff --git a/outputs/nodered.go b/outputs/nodered.go index 662e6fbe2..c6cc3d327 100644 --- a/outputs/nodered.go +++ b/outputs/nodered.go @@ -10,7 +10,7 @@ import ( ) // NodeRedPost posts event to Slack -func (c *Client) NodeRedPost(falcopayload types.FalcoPayload) { +func (c *Client) NodeRedPost(falcopayload types.KubearmorPayload) { c.Stats.NodeRed.Add(Total, 1) c.httpClientLock.Lock() diff --git a/outputs/openfaas.go b/outputs/openfaas.go index 650ec6342..577e493ed 100644 --- a/outputs/openfaas.go +++ b/outputs/openfaas.go @@ -5,7 +5,6 @@ package outputs import ( "context" "encoding/json" - "fmt" "log" "strconv" @@ -38,29 +37,31 @@ func NewOpenfaasClient(config *types.Configuration, stats *types.Statistics, pro KubernetesClient: clientset, }, nil } - - endpointUrl := fmt.Sprintf("http://%s.%s:%d/function/%s.%s", config.Openfaas.GatewayService, config.Openfaas.GatewayNamespace, config.Openfaas.GatewayPort, config.Openfaas.FunctionName, config.Openfaas.FunctionNamespace) initClientArgs := &types.InitClientArgs{ Config: config, Stats: stats, DogstatsdClient: dogstatsdClient, PromStats: promStats, - StatsdClient: statsdClient, } - - return NewClient(Openfaas, endpointUrl, config.Openfaas.MutualTLS, config.Openfaas.CheckCert, *initClientArgs) + return NewClient( + Openfaas, + "http://"+config.Openfaas.GatewayService+"."+config.Openfaas.GatewayNamespace+":"+strconv.Itoa(config.Openfaas.GatewayPort)+"/function/"+config.Openfaas.FunctionName+"."+config.Openfaas.FunctionNamespace, + config.Openfaas.MutualTLS, + config.Openfaas.CheckCert, + *initClientArgs, + ) } // OpenfaasCall . -func (c *Client) OpenfaasCall(falcopayload types.FalcoPayload) { +func (c *Client) OpenfaasCall(kubearmorpayload types.KubearmorPayload) { c.Stats.Openfaas.Add(Total, 1) if c.Config.Openfaas.Kubeconfig != "" { - str, _ := json.Marshal(falcopayload) + str, _ := json.Marshal(kubearmorpayload) req := c.KubernetesClient.CoreV1().RESTClient().Post().AbsPath("/api/v1/namespaces/" + c.Config.Openfaas.GatewayNamespace + "/services/" + c.Config.Openfaas.GatewayService + ":" + strconv.Itoa(c.Config.Openfaas.GatewayPort) + "/proxy" + "/function/" + c.Config.Openfaas.FunctionName + "." + c.Config.Openfaas.FunctionNamespace).Body(str) req.SetHeader("event-id", uuid.New().String()) req.SetHeader("Content-Type", "application/json") - req.SetHeader("User-Agent", "Falcosidekick") + req.SetHeader("User-Agent", "Kubearmor") res := req.Do(context.TODO()) rawbody, err := res.Raw() @@ -73,7 +74,7 @@ func (c *Client) OpenfaasCall(falcopayload types.FalcoPayload) { } log.Printf("[INFO] : %v - Function Response : %v\n", Openfaas, string(rawbody)) } else { - err := c.Post(falcopayload) + err := c.Post(kubearmorpayload) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:openfaas", "status:error"}) c.Stats.Openfaas.Add(Error, 1) diff --git a/outputs/openobserve.go b/outputs/openobserve.go index 041dd146d..fdee55e85 100644 --- a/outputs/openobserve.go +++ b/outputs/openobserve.go @@ -9,7 +9,7 @@ import ( ) // OpenObservePost posts event to OpenObserve -func (c *Client) OpenObservePost(falcopayload types.FalcoPayload) { +func (c *Client) OpenObservePost(kubearmorpayload types.KubearmorPayload) { c.Stats.OpenObserve.Add(Total, 1) if c.Config.OpenObserve.Username != "" && c.Config.OpenObserve.Password != "" { @@ -22,7 +22,7 @@ func (c *Client) OpenObservePost(falcopayload types.FalcoPayload) { c.AddHeader(i, j) } - if err := c.Post(falcopayload); err != nil { + if err := c.Post(kubearmorpayload); err != nil { c.setOpenObserveErrorMetrics() log.Printf("[ERROR] : OpenObserve - %v\n", err) return diff --git a/outputs/opsgenie.go b/outputs/opsgenie.go index 65c3f21bf..5643450d2 100644 --- a/outputs/opsgenie.go +++ b/outputs/opsgenie.go @@ -3,6 +3,7 @@ package outputs import ( + "fmt" "log" "strings" @@ -17,58 +18,49 @@ type opsgeniePayload struct { Priority string `json:"priority,omitempty"` } -func newOpsgeniePayload(falcopayload types.FalcoPayload) opsgeniePayload { - details := make(map[string]string, len(falcopayload.OutputFields)) - for i, j := range falcopayload.OutputFields { +func newOpsgeniePayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) opsgeniePayload { + details := make(map[string]string, len(kubearmorpayload.OutputFields)) + for i, j := range kubearmorpayload.OutputFields { switch v := j.(type) { case string: details[strings.ReplaceAll(i, ".", "_")] = v default: - continue + vv := fmt.Sprint(v) + details[strings.ReplaceAll(i, ".", "_")] = vv } } - details["source"] = falcopayload.Source - details["rule"] = falcopayload.Rule - details["priority"] = falcopayload.Priority.String() - if falcopayload.Hostname != "" { - details[Hostname] = falcopayload.Hostname - } - if len(falcopayload.Tags) != 0 { - details["tags"] = strings.Join(falcopayload.Tags, ", ") + details["source"] = "kubearmor" + details["priority"] = kubearmorpayload.EventType + if kubearmorpayload.Hostname != "" { + details[Hostname] = kubearmorpayload.Hostname } var prio string - switch falcopayload.Priority { - case types.Emergency, types.Alert: + switch kubearmorpayload.EventType { + case "Alert": prio = "P1" - case types.Critical: - prio = "P2" - case types.Error: - prio = "P3" - case types.Warning: - prio = "P4" default: prio = "P5" } return opsgeniePayload{ - Message: falcopayload.Output, - Entity: "Falcosidekick", - Description: falcopayload.Rule, + Message: kubearmorpayload.EventType + " for " + kubearmorpayload.OutputFields["PodName"].(string), + Entity: "Kubearmor", + Description: kubearmorpayload.EventType, Details: details, Priority: prio, } } // OpsgeniePost posts event to OpsGenie -func (c *Client) OpsgeniePost(falcopayload types.FalcoPayload) { +func (c *Client) OpsgeniePost(kubearmorpayload types.KubearmorPayload) { c.Stats.Opsgenie.Add(Total, 1) c.httpClientLock.Lock() defer c.httpClientLock.Unlock() c.AddHeader(AuthorizationHeaderKey, "GenieKey "+c.Config.Opsgenie.APIKey) - err := c.Post(newOpsgeniePayload(falcopayload)) + err := c.Post(newOpsgeniePayload(kubearmorpayload, c.Config)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:opsgenie", "status:error"}) c.Stats.Opsgenie.Add(Error, 1) diff --git a/outputs/opsgenie_test.go b/outputs/opsgenie_test.go index c09519151..03aa13c6e 100644 --- a/outputs/opsgenie_test.go +++ b/outputs/opsgenie_test.go @@ -27,9 +27,9 @@ func TestNewOpsgeniePayload(t *testing.T) { Priority: "P5", } - var f types.FalcoPayload + var f types.KubearmorPayload require.Nil(t, json.Unmarshal([]byte(falcoTestInput), &f)) - output := newOpsgeniePayload(f) + output := newOpsgeniePayload(f, &types.Configuration{}) require.Equal(t, output, expectedOutput) } diff --git a/outputs/outputs.go b/outputs/outputs.go new file mode 100644 index 000000000..c605b7b7e --- /dev/null +++ b/outputs/outputs.go @@ -0,0 +1,208 @@ +package outputs + +import ( + "encoding/json" + "fmt" + "log" + "sync" + "time" + + "github.com/falcosecurity/falcosidekick/types" +) + +// Podowner struct +type Podowner struct { + Ref string `json:"ref,omitempty"` + Name string `json:"name,omitempty"` + Namespace string `json:"namespace,omitempty"` +} + +// Alerts struct +type Alerts struct { + Timestamp int64 `json:"timestamp,omitempty"` + UpdatedTime string `json:"updated_time,omitempty"` + ClusterName string `json:"cluster_name,omitempty"` + HostName string `json:"host_name,omitempty"` + NamespaceName string `json:"namespace_name,omitempty"` + Owner *Podowner `json:"owner,omitempty"` + PodName string `json:"pod_name,omitempty"` + Labels string `json:"labels,omitempty"` + ContainerID string `json:"container_id,omitempty"` + ContainerName string `json:"container_name,omitempty"` + ContainerImage string `json:"container_image,omitempty"` + HostPPID int32 `json:"host_ppid,omitempty"` + HostPID int32 `json:"host_pid,omitempty"` + PPID int32 `json:"ppid,omitempty"` + PID int32 `json:"pid,omitempty"` + UID int32 `json:"uid,omitempty"` + ParentProcessName string `json:"parent_process_name,omitempty"` + ProcessName string `json:"process_name,omitempty"` + PolicyName string `json:"policy_name,omitempty"` + Severity string `json:"severity,omitempty"` + Tags string `json:"tags,omitempty"` + ATags []string `json:"atags,omitempty"` + Message string `json:"message,omitempty"` + Type string `json:"type,omitempty"` + Source string `json:"source,omitempty"` + Operation string `json:"operation,omitempty"` + Resource string `json:"resource,omitempty"` + Data string `json:"data,omitempty"` + Enforcer string `json:"enforcer,omitempty"` + Action string `json:"action,omitempty"` + Result string `json:"result,omitempty"` +} + +// AlertStruct Structure +type AlertStruct struct { + Broadcast chan types.KubearmorPayload +} + +// AlertLock Lock +var AlertLock *sync.RWMutex + +// AlertStructs Map +var AlertStructs map[string]AlertStruct + +// Running bool +var AlertRunning bool + +// AlertBufferChannel store incoming data from msg stream in buffer +var AlertBufferChannel chan []byte + +// LogStruct Structure +type LogStruct struct { + Filter string + Broadcast chan types.KubearmorPayload +} + +var LogLock *sync.RWMutex + +// LogStructs Map +var LogStructs map[string]LogStruct + +func InitSidekick() { + + AlertRunning = true + + //initial buffer struct + AlertBufferChannel = make(chan []byte, 1000) + + // initialize alert structs + AlertStructs = make(map[string]AlertStruct) + AlertLock = &sync.RWMutex{} + + // initialize log structs + LogStructs = make(map[string]LogStruct) + LogLock = &sync.RWMutex{} + +} + +func addAlertStruct(uid string, conn chan types.KubearmorPayload) { + AlertLock.Lock() + defer AlertLock.Unlock() + + alertStruct := AlertStruct{} + alertStruct.Broadcast = conn + + AlertStructs[uid] = alertStruct + + fmt.Println("Added a new client (" + uid + ") for WatchAlerts") +} +func removeAlertStruct(uid string) { + AlertLock.Lock() + defer AlertLock.Unlock() + + delete(AlertStructs, uid) + fmt.Println("Deleted a new client (" + uid + ") for WatchAlerts") + +} + +func (c *Client) AddAlertFromBuffChan() { + for AlertRunning { + select { + case res := <-AlertBufferChannel: + + alert := types.KubearmorPayload{} + // further updates needed + alert.Timestamp = time.Now().Unix() + alert.UpdatedTime = time.Now().String() + alert.ClusterName = "cluster_1" + alert.Hostname = "host" + alert.EventType = "Alert" + alert.OutputFields = make(map[string]interface{}) + + json.Unmarshal(res, &alert.OutputFields) + + AlertLock.RLock() + for uid := range AlertStructs { + select { + case AlertStructs[uid].Broadcast <- (alert): + default: + } + } + AlertLock.RUnlock() + + default: + time.Sleep(time.Millisecond * 10) + } + + } +} + +func (c *Client) SendAlerts() error { + defer c.WgServer.Done() + + for { + var res Alerts + + res = Alerts{ + Timestamp: 1622487600, + UpdatedTime: "2024-07-25T14:20:00Z", + ClusterName: "example-cluster", + HostName: "example-host", + NamespaceName: "default", + Owner: &Podowner{ + Ref: "owner-ref-value", + Name: "owner-name-value", + Namespace: "owner-namespace-value", + }, + PodName: "example-pod", + Labels: "key=value", + ContainerID: "container-id", + ContainerName: "example-container", + ContainerImage: "example-image", + HostPPID: 1, + HostPID: 2, + PPID: 3, + PID: 4, + UID: 1000, + ParentProcessName: "parent-process", + ProcessName: "process", + PolicyName: "example-policy", + Severity: "high", + Tags: "tag1,tag2", + ATags: []string{"tag1", "tag2"}, + Message: "new message", + Type: "alert-type", + Source: "source", + Operation: "operation", + Resource: "resource", + Data: "data", + Enforcer: "enforcer", + Action: "action", + Result: "result", + } + + jsonData, err := json.Marshal(res) + if err != nil { + log.Fatalf("Error marshaling to JSON: %v", err) + } + + select { + case AlertBufferChannel <- jsonData: + default: + } + time.Sleep(10 * time.Second) + } + +} diff --git a/outputs/pagerduty.go b/outputs/pagerduty.go index fd0fb2110..1ef700b66 100644 --- a/outputs/pagerduty.go +++ b/outputs/pagerduty.go @@ -5,7 +5,6 @@ package outputs import ( "context" "log" - "sort" "strings" "time" @@ -20,10 +19,10 @@ const ( ) // PagerdutyPost posts alert event to Pagerduty -func (c *Client) PagerdutyPost(falcopayload types.FalcoPayload) { +func (c *Client) PagerdutyPost(kubearmorpayload types.KubearmorPayload) { c.Stats.Pagerduty.Add(Total, 1) - event := createPagerdutyEvent(falcopayload, c.Config.Pagerduty) + event := createPagerdutyEvent(kubearmorpayload, c.Config.Pagerduty) if strings.ToLower(c.Config.Pagerduty.Region) == "eu" { pagerduty.WithV2EventsAPIEndpoint(EUEndpoint) @@ -45,27 +44,23 @@ func (c *Client) PagerdutyPost(falcopayload types.FalcoPayload) { log.Printf("[INFO] : Pagerduty - Create Incident OK\n") } -func createPagerdutyEvent(falcopayload types.FalcoPayload, config types.PagerdutyConfig) pagerduty.V2Event { - details := make(map[string]interface{}, len(falcopayload.OutputFields)+4) - details["rule"] = falcopayload.Rule - details["priority"] = falcopayload.Priority.String() - details["source"] = falcopayload.Source - if len(falcopayload.Hostname) != 0 { - falcopayload.OutputFields[Hostname] = falcopayload.Hostname - } - if len(falcopayload.Tags) != 0 { - sort.Strings(falcopayload.Tags) - details["tags"] = strings.Join(falcopayload.Tags, ", ") +func createPagerdutyEvent(kubearmorpayload types.KubearmorPayload, config types.PagerdutyConfig) pagerduty.V2Event { + details := make(map[string]interface{}, len(kubearmorpayload.OutputFields)+4) + details["priority"] = kubearmorpayload.EventType + details["source"] = kubearmorpayload.OutputFields["PodName"].(string) + if len(kubearmorpayload.Hostname) != 0 { + kubearmorpayload.OutputFields[Hostname] = kubearmorpayload.Hostname } + timestamp := time.Unix(kubearmorpayload.Timestamp, 0) event := pagerduty.V2Event{ RoutingKey: config.RoutingKey, Action: "trigger", Payload: &pagerduty.V2Payload{ - Source: "falco", - Summary: falcopayload.Output, + Source: "Kubearmor", + Summary: kubearmorpayload.EventType + " for " + kubearmorpayload.OutputFields["PodName"].(string), Severity: "critical", - Timestamp: falcopayload.Time.Format(time.RFC3339), - Details: falcopayload.OutputFields, + Timestamp: timestamp.Format(time.RFC3339), + Details: kubearmorpayload.OutputFields, }, } return event diff --git a/outputs/pagerduty_test.go b/outputs/pagerduty_test.go index 2836f0f50..4b5ad447e 100644 --- a/outputs/pagerduty_test.go +++ b/outputs/pagerduty_test.go @@ -33,7 +33,7 @@ func TestPagerdutyPayload(t *testing.T) { }, } - var f types.FalcoPayload + var f types.KubearmorPayload json.Unmarshal([]byte(falcoTestInput), &f) event := createPagerdutyEvent(f, types.PagerdutyConfig{}) diff --git a/outputs/poc/poc.go b/outputs/poc/poc.go new file mode 100644 index 000000000..bcb20b7a0 --- /dev/null +++ b/outputs/poc/poc.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + "sync" + + "github.com/DataDog/datadog-go/statsd" + "github.com/falcosecurity/falcosidekick/outputs" + "github.com/falcosecurity/falcosidekick/types" +) + +var globalMap map[string]*outputs.Client +var AlertLock *sync.RWMutex +var AlertBufferChannel chan []byte +var slackClient *outputs.Client +var statsdClient, dogstatsdClient *statsd.Client + +func main() { + // var e1 types.smtpOutputConfig + // e1.From = "yajush@accuknox.com" + // e1.To = "yajushsharma12@gmail.com" + // e1.OutputFormat = outputs.HtmlTmpl + // e1.HostPort = "" + // e1.TLS = true + // e1.User = "" + // e1.AuthMechanism = "" + + var t1 types.SlackOutputConfig + t1.WebhookURL = "https://hooks.slack.com/services/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" + t1.Channel = "integration-alerts" + t1.Footer = "filters" + t1.Icon = "https://help.accuknox.com/assets/images/logo.png" + t1.Username = "accuknox" + cf1 := types.Configuration{ + Slack: t1, + } + stats := &types.Statistics{} + promStats := &types.PromStatistics{} + initClientArgs := &types.InitClientArgs{ + Config: &cf1, + Stats: stats, + DogstatsdClient: dogstatsdClient, + PromStats: promStats, + } + c1, err := outputs.NewClient("Slack", cf1.Slack.WebhookURL, cf1.Slack.MutualTLS, cf1.Slack.CheckCert, *initClientArgs) + if err != nil { + fmt.Println("error---") + } + // var t2 types.SlackOutputConfig + // t2.WebhookURL = "" + // cf2 := types.Configuration{ + // Slack: t2, + // } + + // c2 := outputs.Client{ + // Config: &cf2, + // } + // outputs.AlertLock = &sync.RWMutex{} + // outputs.AlertRunning = true + fmt.Println(c1) + outputs.InitSidekick() + + go c1.SendAlerts() + go c1.AddAlertFromBuffChan() + go c1.WatchSlackAlerts() + + select {} +} diff --git a/outputs/rabbitmq.go b/outputs/rabbitmq.go index c1dd48131..7d9eacbb4 100644 --- a/outputs/rabbitmq.go +++ b/outputs/rabbitmq.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "log" + "time" "github.com/DataDog/datadog-go/statsd" "github.com/falcosecurity/falcosidekick/types" @@ -42,10 +43,10 @@ func NewRabbitmqClient(config *types.Configuration, stats *types.Statistics, pro } // Publish sends a message to a Rabbitmq -func (c *Client) Publish(falcopayload types.FalcoPayload) { +func (c *Client) Publish(KubearmorPayload types.KubearmorPayload) { c.Stats.Rabbitmq.Add(Total, 1) - payload, _ := json.Marshal(falcopayload) + payload, _ := json.Marshal(KubearmorPayload) err := c.RabbitmqClient.Publish("", c.Config.Rabbitmq.Queue, false, false, amqp.Publishing{ ContentType: "text/plain", @@ -66,3 +67,25 @@ func (c *Client) Publish(falcopayload types.FalcoPayload) { go c.CountMetric("outputs", 1, []string{"output:rabbitmq", "status:ok"}) c.PromStats.Outputs.With(map[string]string{"destination": "rabbitmq", "status": OK}).Inc() } + +func (c *Client) WatchRabbitmqPublishAlerts() error { + uid := "Rabbitmq" + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + Running := true + for Running { + select { + case resp := <-conn: + c.Publish(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} diff --git a/outputs/redis.go b/outputs/redis.go index 6369b666a..0c370e41b 100644 --- a/outputs/redis.go +++ b/outputs/redis.go @@ -5,11 +5,13 @@ package outputs import ( "context" "encoding/json" + "fmt" "log" "strings" "github.com/DataDog/datadog-go/statsd" "github.com/falcosecurity/falcosidekick/types" + "github.com/google/uuid" "github.com/redis/go-redis/v9" ) @@ -18,6 +20,7 @@ func (c *Client) ReportError(err error) { c.Stats.Redis.Add(Error, 1) c.PromStats.Outputs.With(map[string]string{"destination": "redis", "status": Error}).Inc() log.Printf("[ERROR] : Redis - %v\n", err) + return } func NewRedisClient(config *types.Configuration, stats *types.Statistics, promStats *types.PromStatistics, @@ -46,11 +49,11 @@ func NewRedisClient(config *types.Configuration, stats *types.Statistics, promSt }, nil } -func (c *Client) RedisPost(falcopayload types.FalcoPayload) { +func (c *Client) RedisPost(kubearmorpayload types.KubearmorPayload) { c.Stats.Redis.Add(Total, 1) - redisPayload, _ := json.Marshal(falcopayload) + redisPayload, _ := json.Marshal(kubearmorpayload) if strings.ToLower(c.Config.Redis.StorageType) == "hashmap" { - _, err := c.RedisClient.HSet(context.Background(), c.Config.Redis.Key, falcopayload.UUID, redisPayload).Result() + _, err := c.RedisClient.HSet(context.Background(), c.Config.Redis.Key, kubearmorpayload.OutputFields["UID"], redisPayload).Result() if err != nil { c.ReportError(err) } @@ -66,3 +69,23 @@ func (c *Client) RedisPost(falcopayload types.FalcoPayload) { c.Stats.Redis.Add(OK, 1) c.PromStats.Outputs.With(map[string]string{"destination": "redis", "status": OK}).Inc() } + +func (c *Client) WatchRedisPostAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + fmt.Println("discord running") + for AlertRunning { + select { + case resp := <-conn: + fmt.Println("response \n", resp) + c.RedisPost(resp) + } + } + fmt.Println("discord stopped") + return nil +} diff --git a/outputs/rocketchat.go b/outputs/rocketchat.go index ac87d9797..5836a22f1 100644 --- a/outputs/rocketchat.go +++ b/outputs/rocketchat.go @@ -4,14 +4,15 @@ package outputs import ( "bytes" + "fmt" "log" - "sort" - "strings" + "time" "github.com/falcosecurity/falcosidekick/types" + "github.com/google/uuid" ) -func newRocketchatPayload(falcopayload types.FalcoPayload, config *types.Configuration) slackPayload { +func newRocketchatPayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) slackPayload { var ( messageText string attachments []slackAttachment @@ -21,58 +22,55 @@ func newRocketchatPayload(falcopayload types.FalcoPayload, config *types.Configu ) if config.Rocketchat.OutputFormat == All || config.Rocketchat.OutputFormat == Fields || config.Rocketchat.OutputFormat == "" { - field.Title = Rule - field.Value = falcopayload.Rule - field.Short = true - fields = append(fields, field) field.Title = Priority - field.Value = falcopayload.Priority.String() + field.Value = kubearmorpayload.EventType field.Short = true fields = append(fields, field) field.Title = Source - field.Value = falcopayload.Source + field.Value = kubearmorpayload.OutputFields["PodName"].(string) field.Short = true fields = append(fields, field) - if len(falcopayload.Tags) != 0 { - sort.Strings(falcopayload.Tags) - field.Title = Tags - field.Value = strings.Join(falcopayload.Tags, ", ") - field.Short = true - fields = append(fields, field) - } - for _, i := range getSortedStringKeys(falcopayload.OutputFields) { - field.Title = i - field.Value = falcopayload.OutputFields[i].(string) - if len([]rune(falcopayload.OutputFields[i].(string))) < 36 { - field.Short = true - } else { - field.Short = false + for _, i := range getSortedStringKeys(kubearmorpayload.OutputFields) { + j := kubearmorpayload.OutputFields[i] + switch v := j.(type) { + case string: + field.Title = i + field.Value = kubearmorpayload.OutputFields[i].(string) + if len([]rune(kubearmorpayload.OutputFields[i].(string))) < 36 { + field.Short = true + } else { + field.Short = false + } + fields = append(fields, field) + default: + vv := fmt.Sprint(v) + field.Title = i + field.Value = vv + if len([]rune(vv)) < 36 { + field.Short = true + } else { + field.Short = false + } + fields = append(fields, field) } - fields = append(fields, field) } field.Title = Time field.Short = false - field.Value = falcopayload.Time.String() + field.Value = fmt.Sprint(kubearmorpayload.Timestamp) fields = append(fields, field) - if falcopayload.Hostname != "" { + if kubearmorpayload.Hostname != "" { field.Title = Hostname - field.Value = falcopayload.Hostname + field.Value = kubearmorpayload.Hostname field.Short = true fields = append(fields, field) } } - attachment.Fallback = falcopayload.Output - attachment.Fields = fields - if config.Rocketchat.OutputFormat == All || config.Rocketchat.OutputFormat == Text || config.Rocketchat.OutputFormat == "" { - attachment.Text = falcopayload.Output - } - if config.Rocketchat.MessageFormatTemplate != nil { buf := &bytes.Buffer{} - if err := config.Rocketchat.MessageFormatTemplate.Execute(buf, falcopayload); err != nil { + if err := config.Rocketchat.MessageFormatTemplate.Execute(buf, kubearmorpayload); err != nil { log.Printf("[ERROR] : RocketChat - Error expanding RocketChat message %v", err) } else { messageText = buf.String() @@ -81,23 +79,11 @@ func newRocketchatPayload(falcopayload types.FalcoPayload, config *types.Configu if config.Rocketchat.OutputFormat == All || config.Rocketchat.OutputFormat == Fields || config.Rocketchat.OutputFormat == "" { var color string - switch falcopayload.Priority { - case types.Emergency: - color = Red - case types.Alert: - color = Orange - case types.Critical: + switch kubearmorpayload.EventType { + case "Alert": color = Orange - case types.Error: - color = Red - case types.Warning: - color = Yellow - case types.Notice: - color = Lightcyan - case types.Informational: + case "Log": color = LigthBlue - case types.Debug: - color = PaleCyan } attachment.Color = color @@ -111,7 +97,7 @@ func newRocketchatPayload(falcopayload types.FalcoPayload, config *types.Configu s := slackPayload{ Text: messageText, - Username: config.Rocketchat.Username, + Username: "Kubearmor", IconURL: iconURL, Attachments: attachments} @@ -119,10 +105,10 @@ func newRocketchatPayload(falcopayload types.FalcoPayload, config *types.Configu } // RocketchatPost posts event to Rocketchat -func (c *Client) RocketchatPost(falcopayload types.FalcoPayload) { +func (c *Client) RocketchatPost(kubearmorpayload types.KubearmorPayload) { c.Stats.Rocketchat.Add(Total, 1) - err := c.Post(newRocketchatPayload(falcopayload, c.Config)) + err := c.Post(newRocketchatPayload(kubearmorpayload, c.Config)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:rocketchat", "status:error"}) c.Stats.Rocketchat.Add(Error, 1) @@ -136,3 +122,26 @@ func (c *Client) RocketchatPost(falcopayload types.FalcoPayload) { c.Stats.Rocketchat.Add(OK, 1) c.PromStats.Outputs.With(map[string]string{"destination": "rocketchat", "status": OK}).Inc() } + +func (c *Client) WatchRocketchatPostAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.RocketchatPost(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} diff --git a/outputs/rocketchat_test.go b/outputs/rocketchat_test.go index 14ed817cc..e7a5bf0fd 100644 --- a/outputs/rocketchat_test.go +++ b/outputs/rocketchat_test.go @@ -64,7 +64,7 @@ func TestNewRocketchatPayload(t *testing.T) { }, } - var f types.FalcoPayload + var f types.KubearmorPayload require.Nil(t, json.Unmarshal([]byte(falcoTestInput), &f)) config := &types.Configuration{ Rocketchat: types.RocketchatOutputConfig{ diff --git a/outputs/slack.go b/outputs/slack.go index bf5435508..1c7326c18 100644 --- a/outputs/slack.go +++ b/outputs/slack.go @@ -4,9 +4,9 @@ package outputs import ( "bytes" + "fmt" "log" - "sort" - "strings" + "time" "github.com/falcosecurity/falcosidekick/types" ) @@ -37,7 +37,7 @@ type slackPayload struct { Attachments []slackAttachment `json:"attachments,omitempty"` } -func newSlackPayload(falcopayload types.FalcoPayload, config *types.Configuration) slackPayload { +func newSlackPayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) slackPayload { var ( messageText string attachments []slackAttachment @@ -46,63 +46,61 @@ func newSlackPayload(falcopayload types.FalcoPayload, config *types.Configuratio field slackAttachmentField ) if config.Slack.OutputFormat == All || config.Slack.OutputFormat == Fields || config.Slack.OutputFormat == "" { - field.Title = Rule - field.Value = falcopayload.Rule - field.Short = true - fields = append(fields, field) field.Title = Priority - field.Value = falcopayload.Priority.String() + field.Value = kubearmorpayload.EventType field.Short = true fields = append(fields, field) field.Title = Source - field.Value = falcopayload.Source + field.Value = kubearmorpayload.OutputFields["pod_name"].(string) field.Short = true fields = append(fields, field) - if falcopayload.Hostname != "" { + if kubearmorpayload.Hostname != "" { field.Title = Hostname - field.Value = falcopayload.Hostname - field.Short = true - fields = append(fields, field) - } - if len(falcopayload.Tags) != 0 { - sort.Strings(falcopayload.Tags) - field.Title = Tags - field.Value = strings.Join(falcopayload.Tags, ", ") + field.Value = kubearmorpayload.Hostname field.Short = true fields = append(fields, field) } + for _, i := range getSortedStringKeys(kubearmorpayload.OutputFields) { + j := kubearmorpayload.OutputFields[i] + switch v := j.(type) { + case string: + field.Title = i + field.Value = kubearmorpayload.OutputFields[i].(string) + if len([]rune(kubearmorpayload.OutputFields[i].(string))) < 36 { + field.Short = true + } else { + field.Short = false + } + fields = append(fields, field) + default: + vv := fmt.Sprint(v) + field.Title = i + field.Value = vv + if len([]rune(vv)) < 36 { + field.Short = true + } else { + field.Short = false + } + fields = append(fields, field) - for _, i := range getSortedStringKeys(falcopayload.OutputFields) { - field.Title = i - field.Value = falcopayload.OutputFields[i].(string) - if len([]rune(falcopayload.OutputFields[i].(string))) < 36 { - field.Short = true - } else { - field.Short = false } - fields = append(fields, field) } field.Title = Time field.Short = false - field.Value = falcopayload.Time.String() + field.Value = fmt.Sprint(kubearmorpayload.Timestamp) fields = append(fields, field) attachment.Footer = DefaultFooter if config.Slack.Footer != "" { attachment.Footer = config.Slack.Footer } - } - - attachment.Fallback = falcopayload.Output - attachment.Fields = fields - if config.Slack.OutputFormat == All || config.Slack.OutputFormat == Text || config.Slack.OutputFormat == "" { - attachment.Text = falcopayload.Output + attachment.Fields = fields } if config.Slack.MessageFormatTemplate != nil { buf := &bytes.Buffer{} - if err := config.Slack.MessageFormatTemplate.Execute(buf, falcopayload); err != nil { + if err := config.Slack.MessageFormatTemplate.Execute(buf, kubearmorpayload); err != nil { log.Printf("[ERROR] : Slack - Error expanding Slack message %v", err) } else { messageText = buf.String() @@ -110,23 +108,11 @@ func newSlackPayload(falcopayload types.FalcoPayload, config *types.Configuratio } var color string - switch falcopayload.Priority { - case types.Emergency: - color = Red - case types.Alert: - color = Orange - case types.Critical: + switch kubearmorpayload.EventType { + case "Alert": color = Orange - case types.Error: - color = Red - case types.Warning: - color = Yellow - case types.Notice: - color = Lightcyan - case types.Informational: + case "Log": color = LigthBlue - case types.Debug: - color = PaleCyan } attachment.Color = color @@ -146,20 +132,43 @@ func newSlackPayload(falcopayload types.FalcoPayload, config *types.Configuratio } // SlackPost posts event to Slack -func (c *Client) SlackPost(falcopayload types.FalcoPayload) { - c.Stats.Slack.Add(Total, 1) +func (c *Client) SlackPost(kubearmorpayload types.KubearmorPayload) { + // c.Stats.Slack.Add(Total, 1) - err := c.Post(newSlackPayload(falcopayload, c.Config)) + err := c.Post(newSlackPayload(kubearmorpayload, c.Config)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:slack", "status:error"}) - c.Stats.Slack.Add(Error, 1) - c.PromStats.Outputs.With(map[string]string{"destination": "slack", "status": Error}).Inc() + // c.Stats.Slack.Add(Error, 1) + // c.PromStats.Outputs.With(map[string]string{"destination": "slack", "status": Error}).Inc() log.Printf("[ERROR] : Slack - %v\n", err) return } // Setting the success status go c.CountMetric(Outputs, 1, []string{"output:slack", "status:ok"}) - c.Stats.Slack.Add(OK, 1) - c.PromStats.Outputs.With(map[string]string{"destination": "slack", "status": OK}).Inc() + // c.Stats.Slack.Add(OK, 1) + // c.PromStats.Outputs.With(map[string]string{"destination": "slack", "status": OK}).Inc() +} + +func (c *Client) WatchSlackAlerts() error { + uid := "slack" + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.SlackPost(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil } diff --git a/outputs/slack_test.go b/outputs/slack_test.go index 5ea971c29..d757e20a9 100644 --- a/outputs/slack_test.go +++ b/outputs/slack_test.go @@ -64,7 +64,7 @@ func TestNewSlackPayload(t *testing.T) { }, } - var f types.FalcoPayload + var f types.KubearmorPayload require.Nil(t, json.Unmarshal([]byte(falcoTestInput), &f)) config := &types.Configuration{ Slack: types.SlackOutputConfig{ diff --git a/outputs/smtp.go b/outputs/smtp.go index 6afd42d3d..731655588 100644 --- a/outputs/smtp.go +++ b/outputs/smtp.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" textTemplate "text/template" + "time" "github.com/DataDog/datadog-go/statsd" sasl "github.com/emersion/go-sasl" @@ -48,16 +49,18 @@ func NewSMTPClient(config *types.Configuration, stats *types.Statistics, promSta }, nil } -func newSMTPPayload(falcopayload types.FalcoPayload, config *types.Configuration) SMTPPayload { +func newSMTPPayload(KubearmorPayload types.KubearmorPayload, config *types.Configuration) SMTPPayload { s := SMTPPayload{ From: "From: " + config.SMTP.From, To: "To: " + config.SMTP.To, - Subject: "Subject: [" + falcopayload.Priority.String() + "] " + falcopayload.Output, + Subject: "Subject: " + "Alert : " + "triggerName", } + time := time.Unix(KubearmorPayload.Timestamp, 0) + s.Body = "From: " + config.SMTP.From + "\n" s.Body += "To: " + config.SMTP.To + "\n" - s.Body += "Date: " + falcopayload.Time.Format(rfc2822) + "\n" + s.Body += "Date: " + time.Format(rfc2822) + "\n" s.Body += "MIME-version: 1.0\n" if config.SMTP.OutputFormat != Text { @@ -69,7 +72,7 @@ func newSMTPPayload(falcopayload types.FalcoPayload, config *types.Configuration ttmpl := textTemplate.New(Text) ttmpl, _ = ttmpl.Parse(plaintextTmpl) var outtext bytes.Buffer - err := ttmpl.Execute(&outtext, falcopayload) + err := ttmpl.Execute(&outtext, KubearmorPayload) if err != nil { log.Printf("[ERROR] : SMTP - %v\n", err) return s @@ -83,9 +86,9 @@ func newSMTPPayload(falcopayload types.FalcoPayload, config *types.Configuration s.Body += "--4t74weu9byeSdJTM\nContent-Type: text/html; charset=\"UTF-8\";\n\n" htmpl := htmlTemplate.New("html") - htmpl, _ = htmpl.Parse(htmlTmpl) + htmpl, _ = htmpl.Parse(HtmlTmpl) var outhtml bytes.Buffer - err = htmpl.Execute(&outhtml, falcopayload) + err = htmpl.Execute(&outhtml, KubearmorPayload) if err != nil { log.Printf("[ERROR] : SMTP - %v\n", err) return s @@ -127,8 +130,8 @@ func (c *Client) GetAuth() (sasl.Client, error) { } // SendMail sends email to SMTP server -func (c *Client) SendMail(falcopayload types.FalcoPayload) { - sp := newSMTPPayload(falcopayload, c.Config) +func (c *Client) SendMail(KubearmorPayload types.KubearmorPayload) { + sp := newSMTPPayload(KubearmorPayload, c.Config) to := strings.Split(strings.ReplaceAll(c.Config.SMTP.To, " ", ""), ",") @@ -179,3 +182,26 @@ func (c *Client) SendMail(falcopayload types.FalcoPayload) { go c.CountMetric("outputs", 1, []string{"output:smtp", "status:ok"}) c.Stats.SMTP.Add(OK, 1) } + +func (c *Client) WatchSmtpAlerts() error { + uid := "email" + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.SendMail(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} diff --git a/outputs/smtp_templates.go b/outputs/smtp_templates.go index 929eb5577..7713998ae 100644 --- a/outputs/smtp_templates.go +++ b/outputs/smtp_templates.go @@ -16,7 +16,7 @@ Time: {{ .Time }} ` -var htmlTmpl = ` +var HtmlTmpl = ` {{ $color := "#858585" }} {{ $prio := printf "%v" .Priority }} {{ if or (eq $prio "Emergency") (eq $prio "emergency") }}{{ $color = "#e20b0b" }}{{ end }} diff --git a/outputs/spyderbat.go b/outputs/spyderbat.go index 174d134ba..a008eeb20 100644 --- a/outputs/spyderbat.go +++ b/outputs/spyderbat.go @@ -6,12 +6,10 @@ import ( "bytes" "encoding/json" "errors" - "fmt" - "io" + "io/ioutil" "log" "net/http" "net/url" - "strings" "time" "github.com/DataDog/datadog-go/statsd" @@ -27,7 +25,7 @@ func isSourcePresent(config *types.Configuration) (bool, error) { client := &http.Client{} - source_url, err := url.JoinPath(config.Spyderbat.APIUrl, APIv1Path+config.Spyderbat.OrgUID+SourcePath) + source_url, err := url.JoinPath(config.Spyderbat.APIUrl, "api/v1/org/"+config.Spyderbat.OrgUID+"/source/") if err != nil { return false, err } @@ -35,7 +33,7 @@ func isSourcePresent(config *types.Configuration) (bool, error) { if err != nil { return false, err } - req.Header.Add("Authorization", Bearer+" "+config.Spyderbat.APIKey) + req.Header.Add("Authorization", "Bearer "+config.Spyderbat.APIKey) resp, err := client.Do(req) if err != nil { @@ -46,7 +44,7 @@ func isSourcePresent(config *types.Configuration) (bool, error) { } defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) + body, err := ioutil.ReadAll(resp.Body) if err != nil { return false, err } @@ -54,7 +52,7 @@ func isSourcePresent(config *types.Configuration) (bool, error) { if err := json.Unmarshal(body, &sources); err != nil { return false, err } - uid := Falcosidekick_ + config.Spyderbat.OrgUID + uid := "kubearmor_" + config.Spyderbat.OrgUID for _, source := range sources { if id, ok := source["uid"]; ok && id.(string) == uid { return true, nil @@ -74,7 +72,7 @@ func makeSource(config *types.Configuration) error { data := SourceBody{ Name: config.Spyderbat.Source, Description: config.Spyderbat.SourceDescription, - UID: Falcosidekick_ + config.Spyderbat.OrgUID, + UID: "kubearmor_" + config.Spyderbat.OrgUID, } body := new(bytes.Buffer) if err := json.NewEncoder(body).Encode(data); err != nil { @@ -83,7 +81,7 @@ func makeSource(config *types.Configuration) error { client := &http.Client{} - source_url, err := url.JoinPath(config.Spyderbat.APIUrl, APIv1Path+config.Spyderbat.OrgUID+SourcePath) + source_url, err := url.JoinPath(config.Spyderbat.APIUrl, "api/v1/org/"+config.Spyderbat.OrgUID+"/source/") if err != nil { return err } @@ -91,7 +89,7 @@ func makeSource(config *types.Configuration) error { if err != nil { return err } - req.Header.Add("Authorization", Bearer+" "+config.Spyderbat.APIKey) + req.Header.Add("Authorization", "Bearer "+config.Spyderbat.APIKey) resp, err := client.Do(req) if err != nil { @@ -99,7 +97,7 @@ func makeSource(config *types.Configuration) error { } if resp.StatusCode != http.StatusOK { if resp.StatusCode == http.StatusBadRequest { - if b, err := io.ReadAll(resp.Body); err == nil { + if b, err := ioutil.ReadAll(resp.Body); err == nil { return errors.New("Bad request: " + string(b)) } } @@ -110,17 +108,17 @@ func makeSource(config *types.Configuration) error { return nil } -const Schema = "falco_alert::1.0.0" - -var PriorityMap = map[types.PriorityType]string{ - types.Emergency: "critical", - types.Alert: "high", - types.Critical: "critical", - types.Error: "high", - types.Warning: "medium", - types.Notice: "low", - types.Informational: "info", - types.Debug: "info", +const Schema = "kubearmor_alert::1.0.0" + +var PriorityMap = map[string]string{ + "types.Emergency": "critical", + "Alert": "high", + "types.Critical": "critical", + "types.Error": "high", + "types.Warning": "medium", + "types.Notice": "low", + "Log": "info", + "types.Debug": "info", } type spyderbatPayload struct { @@ -136,38 +134,14 @@ type spyderbatPayload struct { Container string `json:"container"` } -func newSpyderbatPayload(falcopayload types.FalcoPayload) (spyderbatPayload, error) { +func newSpyderbatPayload(kubearmorpayload types.KubearmorPayload) (spyderbatPayload, error) { nowTime := float64(time.Now().UnixNano()) / 1000000000 - timeStr := falcopayload.OutputFields["evt.time"] - if timeStr == nil { - errStr := fmt.Sprintf("evt.time is nil for rule %s", falcopayload.Rule) - return spyderbatPayload{}, errors.New(errStr) - } - jsonTime, err := timeStr.(json.Number).Int64() - if err != nil { - return spyderbatPayload{}, err - } - eventTime := float64(jsonTime / 1000000000.0) - - pidStr := falcopayload.OutputFields["proc.pid"] - if pidStr == nil { - errStr := fmt.Sprintf("proc.pid is nil for rule %s", falcopayload.Rule) - return spyderbatPayload{}, errors.New(errStr) - } - pid, err := pidStr.(json.Number).Int64() - if err != nil { - return spyderbatPayload{}, err - } + eventTime := float64(kubearmorpayload.Timestamp / 1000000000.0) - level := PriorityMap[falcopayload.Priority] - args := strings.Split(falcopayload.Output, " ") - var message []string - if len(args) > 2 { - message = args[2:] - } - arguments := falcopayload.OutputFields["proc.cmdline"].(string) - container := falcopayload.OutputFields["container.id"].(string) + level := PriorityMap[kubearmorpayload.EventType] + arguments := kubearmorpayload.OutputFields["proc.cmdline"].(string) + container := kubearmorpayload.OutputFields["container.id"].(string) return spyderbatPayload{ Schema: Schema, @@ -175,9 +149,8 @@ func newSpyderbatPayload(falcopayload types.FalcoPayload) (spyderbatPayload, err MonotonicTime: time.Now().Nanosecond(), OrcTime: nowTime, Time: eventTime, - PID: int32(pid), + PID: int32(kubearmorpayload.OutputFields["PID"].(int32)), Level: level, - Message: message, Arguments: arguments, Container: container, }, nil @@ -200,8 +173,8 @@ func NewSpyderbatClient(config *types.Configuration, stats *types.Statistics, pr } } - source := Falcosidekick_ + config.Spyderbat.OrgUID - data_url, err := url.JoinPath(config.Spyderbat.APIUrl, APIv1Path+config.Spyderbat.OrgUID+SourcePath+source+"/data/sb-agent") + source := "kubearmor_" + config.Spyderbat.OrgUID + data_url, err := url.JoinPath(config.Spyderbat.APIUrl, "api/v1/org/"+config.Spyderbat.OrgUID+"/source/"+source+"/data/sb-agent") if err != nil { log.Printf("[ERROR] : Spyderbat - %v\n", err.Error()) return nil, ErrClientCreation @@ -225,7 +198,7 @@ func NewSpyderbatClient(config *types.Configuration, stats *types.Statistics, pr }, nil } -func (c *Client) SpyderbatPost(falcopayload types.FalcoPayload) { +func (c *Client) SpyderbatPost(kubearmorpayload types.KubearmorPayload) { c.Stats.Spyderbat.Add(Total, 1) c.httpClientLock.Lock() @@ -233,7 +206,7 @@ func (c *Client) SpyderbatPost(falcopayload types.FalcoPayload) { c.AddHeader("Authorization", "Bearer "+c.Config.Spyderbat.APIKey) c.AddHeader("Content-Encoding", "gzip") - payload, err := newSpyderbatPayload(falcopayload) + payload, err := newSpyderbatPayload(kubearmorpayload) if err == nil { err = c.Post(payload) } diff --git a/outputs/stan.go b/outputs/stan.go index 25335c7d2..79f28aacb 100644 --- a/outputs/stan.go +++ b/outputs/stan.go @@ -13,7 +13,7 @@ import ( ) // StanPublish publishes event to NATS Streaming -func (c *Client) StanPublish(falcopayload types.FalcoPayload) { +func (c *Client) StanPublish(kubearmorpayload types.KubearmorPayload) { c.Stats.Stan.Add(Total, 1) nc, err := stan.Connect(c.Config.Stan.ClusterID, c.Config.Stan.ClientID, stan.NatsURL(c.EndpointURL.String())) @@ -24,15 +24,14 @@ func (c *Client) StanPublish(falcopayload types.FalcoPayload) { } defer nc.Close() - r := strings.Trim(slugRegularExpression.ReplaceAllString(strings.ToLower(falcopayload.Rule), "_"), "_") - j, err := json.Marshal(falcopayload) + j, err := json.Marshal(kubearmorpayload) if err != nil { c.setStanErrorMetrics() log.Printf("[ERROR] : STAN - %v\n", err.Error()) return } - err = nc.Publish("falco."+strings.ToLower(falcopayload.Priority.String())+"."+r, j) + err = nc.Publish("kubearmor."+strings.ToLower(kubearmorpayload.EventType)+".", j) if err != nil { c.setStanErrorMetrics() log.Printf("[ERROR] : STAN - %v\n", err) diff --git a/outputs/syslog.go b/outputs/syslog.go index a71e5aebf..6548a215d 100644 --- a/outputs/syslog.go +++ b/outputs/syslog.go @@ -34,54 +34,30 @@ func isValidProtocolString(protocol string) bool { return protocol == TCP || protocol == UDP } -func getCEFSeverity(priority types.PriorityType) string { +func getCEFSeverity(priority string) string { switch priority { - case types.Debug: - return "0" - case types.Informational: + case "Log": return "3" - case types.Notice: - return "4" - case types.Warning: - return "6" - case types.Error: - return "7" - case types.Critical: - return "8" - case types.Alert: + case "Alert": return "9" - case types.Emergency: - return "10" default: return "Uknown" } } -func (c *Client) SyslogPost(falcopayload types.FalcoPayload) { - c.Stats.Syslog.Add(Total, 1) +func (c *Client) SyslogPost(kubearmorpayload types.KubearmorPayload) { + //c.Stats.Syslog.Add(Total, 1) endpoint := fmt.Sprintf("%s:%s", c.Config.Syslog.Host, c.Config.Syslog.Port) - + fmt.Println("endpoint ", endpoint) var priority syslog.Priority - switch falcopayload.Priority { - case types.Emergency: - priority = syslog.LOG_EMERG - case types.Alert: + switch kubearmorpayload.EventType { + case "Alert": priority = syslog.LOG_ALERT - case types.Critical: - priority = syslog.LOG_CRIT - case types.Error: - priority = syslog.LOG_ERR - case types.Warning: - priority = syslog.LOG_WARNING - case types.Notice: - priority = syslog.LOG_NOTICE - case types.Informational: + case "Log": priority = syslog.LOG_INFO - case types.Debug: - priority = syslog.LOG_DEBUG } - sysLog, err := syslog.Dial(c.Config.Syslog.Protocol, endpoint, priority, Falco) + sysLog, err := syslog.Dial(c.Config.Syslog.Protocol, endpoint, priority, Accuknox) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:syslog", "status:error"}) c.Stats.Syslog.Add(Error, 1) @@ -89,39 +65,42 @@ func (c *Client) SyslogPost(falcopayload types.FalcoPayload) { log.Printf("[ERROR] : Syslog - %v\n", err) return } + fmt.Println("syslog - ", sysLog) var payload []byte + timestamp := time.Unix(kubearmorpayload.Timestamp, 0) if c.Config.Syslog.Format == "cef" { s := fmt.Sprintf( - "CEF:0|Falcosecurity|Falco|1.0|Falco Event|%v|%v|uuid=%v start=%v msg=%v source=%v", - falcopayload.Rule, - getCEFSeverity(falcopayload.Priority), - falcopayload.UUID, - falcopayload.Time.Format(time.RFC3339), - falcopayload.Output, - falcopayload.Source, + "CEF:0|Accuknox|Kubearmor|1.0|Kubearmor Event|%v|uid=%v start=%v", + kubearmorpayload.EventType, + fmt.Sprint(kubearmorpayload.OutputFields["UID"]), + timestamp.Format(time.RFC3339), ) - if falcopayload.Hostname != "" { - s += " hostname=" + falcopayload.Hostname - } - s += " outputfields=" - for i, j := range falcopayload.OutputFields { - s += fmt.Sprintf("%v:%v ", i, j) - } - if len(falcopayload.Tags) != 0 { - s += "tags=" + strings.Join(falcopayload.Tags, ",") + s += " " + kubearmorpayload.EventType + "=" + for i, j := range kubearmorpayload.OutputFields { + switch v := j.(type) { + case string: + if v == "" { + continue + } + s += fmt.Sprintf("%v:%v ", i, v) + default: + vv := fmt.Sprint(v) + s += fmt.Sprintf("%v:%v ", i, vv) + } } + fmt.Println("payload ", s) payload = []byte(strings.TrimSuffix(s, " ")) } else { - payload, _ = json.Marshal(falcopayload) + payload, _ = json.Marshal(kubearmorpayload) } _, err = sysLog.Write(payload) if err != nil { - go c.CountMetric(Outputs, 1, []string{"output:syslog", "status:error"}) - c.Stats.Syslog.Add(Error, 1) - c.PromStats.Outputs.With(map[string]string{"destination": "syslog", "status": Error}).Inc() + // go c.CountMetric(Outputs, 1, []string{"output:syslog", "status:error"}) + // c.Stats.Syslog.Add(Error, 1) + // c.PromStats.Outputs.With(map[string]string{"destination": "syslog", "status": Error}).Inc() log.Printf("[ERROR] : Syslog - %v\n", err) return } @@ -130,3 +109,27 @@ func (c *Client) SyslogPost(falcopayload types.FalcoPayload) { c.Stats.Syslog.Add(OK, 1) c.PromStats.Outputs.With(map[string]string{"destination": "syslog", "status": OK}).Inc() } + +func (c *Client) WatchSyslogsAlerts() error { + uid := "syslog" + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + fmt.Println("got it ", resp) + c.SyslogPost(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} diff --git a/outputs/teams.go b/outputs/teams.go index 83df4804a..a6733f7e9 100644 --- a/outputs/teams.go +++ b/outputs/teams.go @@ -3,11 +3,12 @@ package outputs import ( + "fmt" "log" - "sort" - "strings" + "time" "github.com/falcosecurity/falcosidekick/types" + "github.com/google/uuid" ) type teamsFact struct { @@ -31,7 +32,7 @@ type teamsPayload struct { Sections []teamsSection `json:"sections"` } -func newTeamsPayload(falcopayload types.FalcoPayload, config *types.Configuration) teamsPayload { +func newTeamsPayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) teamsPayload { var ( sections []teamsSection section teamsSection @@ -39,43 +40,38 @@ func newTeamsPayload(falcopayload types.FalcoPayload, config *types.Configuratio fact teamsFact ) - section.ActivityTitle = "Falco Sidekick" - section.ActivitySubTitle = falcopayload.Time.String() - - if config.Teams.OutputFormat == All || config.Teams.OutputFormat == Text || config.Teams.OutputFormat == "" { - section.Text = falcopayload.Output - } + section.ActivityTitle = "Kubearmor Sidekick" + section.ActivitySubTitle = fmt.Sprint(kubearmorpayload.Timestamp) if config.Teams.ActivityImage != "" { section.ActivityImage = config.Teams.ActivityImage } if config.Teams.OutputFormat == All || config.Teams.OutputFormat == "facts" || config.Teams.OutputFormat == "" { - fact.Name = Rule - fact.Value = falcopayload.Rule - facts = append(facts, fact) + for i, j := range kubearmorpayload.OutputFields { + switch v := j.(type) { + case string: + fact.Name = i + fact.Value = v + default: + vv := fmt.Sprint(v) + fact.Name = i + fact.Value = vv + + } + + facts = append(facts, fact) + } + fact.Name = Priority - fact.Value = falcopayload.Priority.String() + fact.Value = kubearmorpayload.EventType facts = append(facts, fact) fact.Name = Source - fact.Value = falcopayload.Source + fact.Value = kubearmorpayload.OutputFields["PodName"].(string) facts = append(facts, fact) - if falcopayload.Hostname != "" { + if kubearmorpayload.Hostname != "" { fact.Name = Hostname - fact.Value = falcopayload.Hostname - facts = append(facts, fact) - } - - for _, i := range getSortedStringKeys(falcopayload.OutputFields) { - fact.Name = i - fact.Value = falcopayload.OutputFields[i].(string) - facts = append(facts, fact) - } - - if len(falcopayload.Tags) != 0 { - sort.Strings(falcopayload.Tags) - fact.Name = Tags - fact.Value = strings.Join(falcopayload.Tags, ", ") + fact.Value = kubearmorpayload.Hostname facts = append(facts, fact) } } @@ -83,30 +79,17 @@ func newTeamsPayload(falcopayload types.FalcoPayload, config *types.Configuratio section.Facts = facts var color string - switch falcopayload.Priority { - case types.Emergency: - color = "e20b0b" - case types.Alert: + switch kubearmorpayload.EventType { + case "Alert": color = "ff5400" - case types.Critical: - color = "ff9000" - case types.Error: - color = "ffc700" - case types.Warning: - color = "ffff00" - case types.Notice: - color = "5bffb5" - case types.Informational: + case "Log": color = "68c2ff" - case types.Debug: - color = "ccfff2" } sections = append(sections, section) t := teamsPayload{ Type: "MessageCard", - Summary: falcopayload.Output, ThemeColor: color, Sections: sections, } @@ -115,10 +98,10 @@ func newTeamsPayload(falcopayload types.FalcoPayload, config *types.Configuratio } // TeamsPost posts event to Teams -func (c *Client) TeamsPost(falcopayload types.FalcoPayload) { +func (c *Client) TeamsPost(kubearmorpayload types.KubearmorPayload) { c.Stats.Teams.Add(Total, 1) - err := c.Post(newTeamsPayload(falcopayload, c.Config)) + err := c.Post(newTeamsPayload(kubearmorpayload, c.Config)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:teams", "status:error"}) c.Stats.Teams.Add(Error, 1) @@ -132,3 +115,26 @@ func (c *Client) TeamsPost(falcopayload types.FalcoPayload) { c.Stats.Teams.Add(OK, 1) c.PromStats.Outputs.With(map[string]string{"destination": "teams", "status": OK}).Inc() } + +func (c *Client) WatchTeamsPostAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.TeamsPost(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} diff --git a/outputs/teams_test.go b/outputs/teams_test.go index ce7740981..6e5c19632 100644 --- a/outputs/teams_test.go +++ b/outputs/teams_test.go @@ -52,7 +52,7 @@ func TestNewTeamsPayload(t *testing.T) { }, } - var f types.FalcoPayload + var f types.KubearmorPayload require.Nil(t, json.Unmarshal([]byte(falcoTestInput), &f)) output := newTeamsPayload(f, &types.Configuration{}) diff --git a/outputs/tekton.go b/outputs/tekton.go index e483f7c39..4b4c24d6e 100644 --- a/outputs/tekton.go +++ b/outputs/tekton.go @@ -9,10 +9,10 @@ import ( ) // TektonPost posts event to EventListner -func (c *Client) TektonPost(falcopayload types.FalcoPayload) { +func (c *Client) TektonPost(kubearmorpayload types.KubearmorPayload) { c.Stats.Tekton.Add(Total, 1) - err := c.Post(falcopayload) + err := c.Post(kubearmorpayload) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:tekton", "status:error"}) c.Stats.Tekton.Add(Error, 1) diff --git a/outputs/telegram.go b/outputs/telegram.go index 3d1e211ed..d59a87ac2 100644 --- a/outputs/telegram.go +++ b/outputs/telegram.go @@ -25,7 +25,7 @@ func markdownV2EscapeText(text interface{}) string { } var ( - telegramMarkdownV2Tmpl = `*\[Falco\] \[{{markdownV2EscapeText .Priority }}\] {{markdownV2EscapeText .Rule }}* + telegramMarkdownV2Tmpl = `*\[Kubearmor\] \[{{markdownV2EscapeText .Priority }}\] {{markdownV2EscapeText .Rule }}* • *Time*: {{markdownV2EscapeText .Time }} • *Source*: {{markdownV2EscapeText .Source }} @@ -47,7 +47,7 @@ type telegramPayload struct { ChatID string `json:"chat_id,omitempty"` } -func newTelegramPayload(falcopayload types.FalcoPayload, config *types.Configuration) telegramPayload { +func newTelegramPayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) telegramPayload { payload := telegramPayload{ ParseMode: "MarkdownV2", @@ -61,7 +61,7 @@ func newTelegramPayload(falcopayload types.FalcoPayload, config *types.Configura "markdownV2EscapeText": markdownV2EscapeText, } ttmpl, _ := textTemplate.New("telegram").Funcs(funcs).Parse(telegramMarkdownV2Tmpl) - err := ttmpl.Execute(&textBuffer, falcopayload) + err := ttmpl.Execute(&textBuffer, kubearmorpayload) if err != nil { log.Printf("[ERROR] : Telegram - %v\n", err) return payload @@ -72,10 +72,10 @@ func newTelegramPayload(falcopayload types.FalcoPayload, config *types.Configura } // TelegramPost posts event to Telegram -func (c *Client) TelegramPost(falcopayload types.FalcoPayload) { +func (c *Client) TelegramPost(kubearmorpayload types.KubearmorPayload) { c.Stats.Telegram.Add(Total, 1) - err := c.Post(newTelegramPayload(falcopayload, c.Config)) + err := c.Post(newTelegramPayload(kubearmorpayload, c.Config)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:telegram", "status:error"}) c.Stats.Telegram.Add(Error, 1) diff --git a/outputs/telegram_test.go b/outputs/telegram_test.go index 8917c4df9..f4a9ba1bd 100644 --- a/outputs/telegram_test.go +++ b/outputs/telegram_test.go @@ -19,7 +19,7 @@ func TestNewTelegramPayload(t *testing.T) { ChatID: "-987654321", } - var f types.FalcoPayload + var f types.KubearmorPayload require.Nil(t, json.Unmarshal([]byte(falcoTestInput), &f)) config := &types.Configuration{ Telegram: types.TelegramConfig{ diff --git a/outputs/timescaledb.go b/outputs/timescaledb.go index 52a126373..c344d8bcc 100644 --- a/outputs/timescaledb.go +++ b/outputs/timescaledb.go @@ -10,6 +10,7 @@ import ( "github.com/DataDog/datadog-go/statsd" "github.com/falcosecurity/falcosidekick/types" + "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" ) @@ -47,23 +48,17 @@ func NewTimescaleDBClient(config *types.Configuration, stats *types.Statistics, }, nil } -func newTimescaleDBPayload(falcopayload types.FalcoPayload, config *types.Configuration) timescaledbPayload { +func newTimescaleDBPayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) timescaledbPayload { vals := make(map[string]any, 7+len(config.Customfields)+len(config.Templatedfields)) - vals[Time] = falcopayload.Time - vals[Rule] = falcopayload.Rule - vals[Priority] = falcopayload.Priority.String() - vals[Source] = falcopayload.Source - vals["output"] = falcopayload.Output - - if len(falcopayload.Tags) != 0 { - vals[Tags] = strings.Join(falcopayload.Tags, ",") - } + vals[Time] = kubearmorpayload.Timestamp + vals[Priority] = kubearmorpayload.EventType + vals["Source Pod"] = kubearmorpayload.OutputFields["PodName"].(string) - if falcopayload.Hostname != "" { - vals[Hostname] = falcopayload.Hostname + if kubearmorpayload.Hostname != "" { + vals[Hostname] = kubearmorpayload.Hostname } - for i, j := range falcopayload.OutputFields { + for i, j := range kubearmorpayload.OutputFields { switch v := j.(type) { case string: for k := range config.Customfields { @@ -110,11 +105,11 @@ func newTimescaleDBPayload(falcopayload types.FalcoPayload, config *types.Config return timescaledbPayload{SQL: sql, Values: retVals} } -func (c *Client) TimescaleDBPost(falcopayload types.FalcoPayload) { +func (c *Client) TimescaleDBPost(kubearmorpayload types.KubearmorPayload) { c.Stats.TimescaleDB.Add(Total, 1) var ctx = context.Background() - tsdbPayload := newTimescaleDBPayload(falcopayload, c.Config) + tsdbPayload := newTimescaleDBPayload(kubearmorpayload, c.Config) _, err := c.TimescaleDBClient.Exec(ctx, tsdbPayload.SQL, tsdbPayload.Values...) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:timescaledb", "status:error"}) @@ -132,3 +127,23 @@ func (c *Client) TimescaleDBPost(falcopayload types.FalcoPayload) { log.Printf("[DEBUG] : TimescaleDB payload : %v\n", tsdbPayload) } } + +func (c *Client) WatchTimescaleDBPostAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + fmt.Println("discord running") + for AlertRunning { + select { + case resp := <-conn: + fmt.Println("response \n", resp) + c.TimescaleDBPost(resp) + } + } + fmt.Println("discord stopped") + return nil +} diff --git a/outputs/timescaledb_test.go b/outputs/timescaledb_test.go index 0415785ee..dc9117b63 100644 --- a/outputs/timescaledb_test.go +++ b/outputs/timescaledb_test.go @@ -29,7 +29,7 @@ func TestNewTimescaleDBPayload(t *testing.T) { "template_field_1": "falcosidekick", } - var f types.FalcoPayload + var f types.KubearmorPayload require.Nil(t, json.Unmarshal([]byte(falcoTestInput), &f)) f.OutputFields["custom_field_1"] = "test-custom-value-1" f.OutputFields["template_field_1"] = "falcosidekick" diff --git a/outputs/wavefront.go b/outputs/wavefront.go index 73f30dc85..dd8205351 100644 --- a/outputs/wavefront.go +++ b/outputs/wavefront.go @@ -4,8 +4,6 @@ package outputs import ( "fmt" - "log" - "strings" "github.com/DataDog/datadog-go/statsd" "github.com/falcosecurity/falcosidekick/types" @@ -62,18 +60,17 @@ func NewWavefrontClient(config *types.Configuration, stats *types.Statistics, pr } // WavefrontPost sends metrics to WaveFront. -func (c *Client) WavefrontPost(falcopayload types.FalcoPayload) { +func (c *Client) WavefrontPost(kubearmorpayload types.KubearmorPayload) { tags := make(map[string]string) - tags["severity"] = falcopayload.Priority.String() - tags["rule"] = falcopayload.Rule - tags["source"] = falcopayload.Source + tags["severity"] = kubearmorpayload.EventType + tags["source"] = kubearmorpayload.OutputFields["PodName"].(string) - if falcopayload.Hostname != "" { - tags[Hostname] = falcopayload.Hostname + if kubearmorpayload.Hostname != "" { + tags[Hostname] = kubearmorpayload.Hostname } - for tag, value := range falcopayload.OutputFields { + for tag, value := range kubearmorpayload.OutputFields { switch v := value.(type) { case string: tags[tag] = v @@ -82,30 +79,22 @@ func (c *Client) WavefrontPost(falcopayload types.FalcoPayload) { } } - if len(falcopayload.Tags) != 0 { - tags["tags"] = strings.Join(falcopayload.Tags, ", ") - - } - c.Stats.Wavefront.Add(Total, 1) if c.WavefrontSender != nil { sender := *c.WavefrontSender // TODO: configurable metric name - if err := sender.SendMetric(c.Config.Wavefront.MetricName, 1, falcopayload.Time.UnixNano(), "falco-exporter", tags); err != nil { + if err := sender.SendMetric(c.Config.Wavefront.MetricName, 1, kubearmorpayload.Timestamp, "kubearmor", tags); err != nil { c.Stats.Wavefront.Add(Error, 1) c.PromStats.Outputs.With(map[string]string{"destination": "wavefront", "status": Error}).Inc() - log.Printf("[ERROR] : Wavefront - Unable to send event %s: %s\n", falcopayload.Rule, err) return } if err := sender.Flush(); err != nil { c.Stats.Wavefront.Add(Error, 1) c.PromStats.Outputs.With(map[string]string{"destination": "wavefront", "status": Error}).Inc() - log.Printf("[ERROR] : Wavefront - Unable to flush event %s: %s\n", falcopayload.Rule, err) return } c.Stats.Wavefront.Add(OK, 1) c.PromStats.Outputs.With(map[string]string{"destination": "wavefront", "status": OK}).Inc() - log.Printf("[INFO] : Wavefront - Send Event OK %s\n", falcopayload.Rule) } } diff --git a/outputs/webhook.go b/outputs/webhook.go index 7ef96a1c1..e981a8be5 100644 --- a/outputs/webhook.go +++ b/outputs/webhook.go @@ -5,12 +5,14 @@ package outputs import ( "log" "strings" + "time" "github.com/falcosecurity/falcosidekick/types" + "github.com/google/uuid" ) // WebhookPost posts event to an URL -func (c *Client) WebhookPost(falcopayload types.FalcoPayload) { +func (c *Client) WebhookPost(kubearmorpayload types.KubearmorPayload) { c.Stats.Webhook.Add(Total, 1) if len(c.Config.Webhook.CustomHeaders) != 0 { @@ -22,9 +24,9 @@ func (c *Client) WebhookPost(falcopayload types.FalcoPayload) { } var err error if strings.ToUpper(c.Config.Webhook.Method) == HttpPut { - err = c.Put(falcopayload) + err = c.Put(kubearmorpayload) } else { - err = c.Post(falcopayload) + err = c.Post(kubearmorpayload) } if err != nil { @@ -40,3 +42,26 @@ func (c *Client) WebhookPost(falcopayload types.FalcoPayload) { c.Stats.Webhook.Add(OK, 1) c.PromStats.Outputs.With(map[string]string{"destination": "webhook", "status": OK}).Inc() } + +func (c *Client) WatchWebhookAlerts() error { + uid := uuid.Must(uuid.NewRandom()).String() + + conn := make(chan types.KubearmorPayload, 1000) + defer close(conn) + addAlertStruct(uid, conn) + defer removeAlertStruct(uid) + + for AlertRunning { + select { + // case <-Context().Done(): + // return nil + case resp := <-conn: + c.WebhookPost(resp) + default: + time.Sleep(time.Millisecond * 10) + + } + } + + return nil +} diff --git a/outputs/webui.go b/outputs/webui.go index 0ab3eea06..0c2c75a46 100644 --- a/outputs/webui.go +++ b/outputs/webui.go @@ -9,22 +9,22 @@ import ( ) type WebUIPayload struct { - Event types.FalcoPayload `json:"event"` - Outputs []string `json:"outputs"` + Event types.KubearmorPayload `json:"event"` + Outputs []string `json:"outputs"` } -func newWebUIPayload(falcopayload types.FalcoPayload) WebUIPayload { +func newWebUIPayload(kubearmorpayload types.KubearmorPayload, config *types.Configuration) WebUIPayload { return WebUIPayload{ - Event: falcopayload, + Event: kubearmorpayload, Outputs: EnabledOutputs, } } // WebUIPost posts event to Slack -func (c *Client) WebUIPost(falcopayload types.FalcoPayload) { +func (c *Client) WebUIPost(kubearmorpayload types.KubearmorPayload) { c.Stats.WebUI.Add(Total, 1) - err := c.Post(newWebUIPayload(falcopayload)) + err := c.Post(newWebUIPayload(kubearmorpayload, c.Config)) if err != nil { go c.CountMetric(Outputs, 1, []string{"output:webui", "status:error"}) c.Stats.WebUI.Add(Error, 1) diff --git a/outputs/yandex.go b/outputs/yandex.go index 8edde64b1..76d05452f 100644 --- a/outputs/yandex.go +++ b/outputs/yandex.go @@ -64,8 +64,8 @@ func NewYandexClient(config *types.Configuration, stats *types.Statistics, promS } // UploadYandexS3 uploads payload to Yandex S3 -func (c *Client) UploadYandexS3(falcopayload types.FalcoPayload) { - f, _ := json.Marshal(falcopayload) +func (c *Client) UploadYandexS3(kubearmorpayload types.KubearmorPayload) { + f, _ := json.Marshal(kubearmorpayload) prefix := "" t := time.Now() if c.Config.Yandex.S3.Prefix != "" { @@ -91,10 +91,10 @@ func (c *Client) UploadYandexS3(falcopayload types.FalcoPayload) { } // UploadYandexDataStreams uploads payload to Yandex Data Streams -func (c *Client) UploadYandexDataStreams(falcoPayLoad types.FalcoPayload) { +func (c *Client) UploadYandexDataStreams(kubearmorpayload types.KubearmorPayload) { svc := kinesis.New(c.AWSSession) - f, _ := json.Marshal(falcoPayLoad) + f, _ := json.Marshal(kubearmorpayload) input := &kinesis.PutRecordInput{ Data: f, PartitionKey: aws.String(uuid.NewString()), diff --git a/outputs/zincsearch.go b/outputs/zincsearch.go index 04745e5d5..c385c5d93 100644 --- a/outputs/zincsearch.go +++ b/outputs/zincsearch.go @@ -10,7 +10,7 @@ import ( ) // ZincsearchPost posts event to Zincsearch -func (c *Client) ZincsearchPost(falcopayload types.FalcoPayload) { +func (c *Client) ZincsearchPost(kubearmorpayload types.KubearmorPayload) { c.Stats.Zincsearch.Add(Total, 1) if c.Config.Zincsearch.Username != "" && c.Config.Zincsearch.Password != "" { @@ -20,7 +20,7 @@ func (c *Client) ZincsearchPost(falcopayload types.FalcoPayload) { } fmt.Println(c.EndpointURL) - err := c.Post(falcopayload) + err := c.Post(kubearmorpayload) if err != nil { c.setZincsearchErrorMetrics() log.Printf("[ERROR] : Zincsearch - %v\n", err) diff --git a/types/types.go b/types/types.go index 667d2476d..cbd7c5ef9 100644 --- a/types/types.go +++ b/types/types.go @@ -27,25 +27,19 @@ type FalcoPayload struct { Hostname string `json:"hostname,omitempty"` } -func (f FalcoPayload) String() string { - j, _ := json.Marshal(f) - return string(j) +// Payload is a struct to map kubearmor event json +type KubearmorPayload struct { + Timestamp int64 ` json:"Timestamp,omitempty"` + UpdatedTime string ` json:"UpdatedTime,omitempty"` + ClusterName string ` json:"ClusterName,omitempty"` + Hostname string ` json:"HostName,omitempty"` + EventType string ` json:"EventType,omitempty"` + OutputFields map[string]interface{} `json:"Detail"` } -func (f FalcoPayload) Check() bool { - if f.Priority.String() == "" { - return false - } - if f.Rule == "" { - return false - } - if f.Time.IsZero() { - return false - } - if len(f.OutputFields) == 0 { - return false - } - return true +func (f KubearmorPayload) String() string { + j, _ := json.Marshal(f) + return string(j) } // Configuration is a struct to store configuration @@ -63,6 +57,7 @@ type Configuration struct { Templatedfields map[string]string Prometheus prometheusOutputConfig Slack SlackOutputConfig + Email EmailOutputConfig Cliq CliqOutputConfig Mattermost MattermostOutputConfig Rocketchat RocketchatOutputConfig @@ -164,6 +159,18 @@ type SlackOutputConfig struct { MutualTLS bool } +// EmailOutputConfig represents parameters for Slack +type EmailOutputConfig struct { + Host string + Username string + Password string + Port int + Sender string + SenderEmail string + AlertUrl string + HeaderLogo string +} + // CliqOutputConfig represents parameters for Zoho Cliq type CliqOutputConfig struct { WebhookURL string