From 59ae4b13e00064539e53e713167ace7af652dcac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Batuhan=20Apayd=C4=B1n?= Date: Thu, 11 Mar 2021 11:38:55 +0300 Subject: [PATCH] openfaas output type added MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Batuhan Apaydın --- README.md | 20 +++++++++++ config.go | 10 ++++++ config_example.yaml | 9 +++++ handlers.go | 4 +++ main.go | 11 ++++++ outputs/client.go | 5 ++- outputs/constants.go | 1 + outputs/openfaas.go | 84 ++++++++++++++++++++++++++++++++++++++++++++ stats.go | 1 + types/types.go | 12 +++++++ 10 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 outputs/openfaas.go diff --git a/README.md b/README.md index f518e68bb..8a7d9a52e 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Currently available outputs are : - [**Apache Kafka**](https://kafka.apache.org/) - [**PagerDuty**](https://pagerduty.com/) - [**Kubeless**](https://kubeless.io/) +- [**OpenFaaS**](https://www.openfaas.com) - [**WebUI**](https://github.com/falcosecurity/falcosidekick-ui) (a Web UI for displaying latest events in real time) ## Usage @@ -301,6 +302,15 @@ kubeless: kubeconfig: "~/.kube/config" # Kubeconfig file to use (only if falcoside is running outside the cluster) # minimumpriority: "debug" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) +openfaas: + gatewayservice: "" # Service of OpenFaaS Gateway, "gateway" (default) + gatewaynamespace: "" # Namespace of OpenFaaS Gateway, "openfaas" (default) + gatewayport: 8080 # Port of service of OpenFaaS Gateway + functionname: "" # Name of OpenFaaS function, if not empty, OpenFaaS is enabled + functionnamespace: "" # Namespace of OpenFaaS function, "openfaas-fn" (default) + kubeconfig: "~/.kube/config" # Kubeconfig file to use (only if falcosidekick is running outside the cluster) + # minimumpriority: "debug" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + webui: url: "" # WebUI URL, if not empty, WebUI output is enabled ``` @@ -557,6 +567,16 @@ care of lower/uppercases**) : `yaml: a.b --> envvar: A_B` : - **KUBELESS_MINIMUMPRIORITY**: "debug" # minimum priority of event for using this output, order is `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)` +- **OPENFAAS_GATEWAYNAMESPACE** : Namespace of OpenFaaS Gateway, "openfaas" (default) +- **OPENFAAS_GATEWAYSERVICE** : Service of OpenFaaS Gateway, "gateway" (default) +- **OPENFAAS_FUNCTIONNAME** : Name of OpenFaaS function, if not empty, OpenFaaS is enabled +- **OPENFAAS_FUNCTIONNAMESPACE** : # Namespace of OpenFaaS function, "openfaas-fn" (default) +- **OPENFAAS_GATEWAYPORT** : Port of service of OpenFaaS Gateway +- **OPENFAAS_KUBECONFIG** : Kubeconfig file to use (only if falcoside is running + outside the cluster) +- **OPENFAAS_MINIMUMPRIORITY** : "debug" # minimum priority of event for using + this output, order is + `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)` - **WEBUI_URL** : WebUI URL, if not empty, WebUI output is _enabled_ diff --git a/config.go b/config.go index 821441615..e675f0c4f 100644 --- a/config.go +++ b/config.go @@ -141,6 +141,15 @@ func getConfig() *types.Configuration { v.SetDefault("Kubeless.Port", 8080) v.SetDefault("Kubeless.Kubeconfig", "") v.SetDefault("Kubeless.MinimumPriority", "") + + v.SetDefault("Openfaas.GatewayNamespace", "openfaas") + v.SetDefault("Openfaas.GatewayService", "gateway") + v.SetDefault("Openfaas.FunctionName", "") + v.SetDefault("Openfaas.FunctionNamespace", "openfaas-fn") + v.SetDefault("Openfaas.GatewayPort", 8080) + v.SetDefault("Openfaas.Kubeconfig", "") + v.SetDefault("Openfaas.MinimumPriority", "") + v.SetDefault("Webui.URL", "") v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) @@ -227,6 +236,7 @@ func getConfig() *types.Configuration { c.Kafka.MinimumPriority = checkPriority(c.Kafka.MinimumPriority) c.Pagerduty.MinimumPriority = checkPriority(c.Pagerduty.MinimumPriority) c.Kubeless.MinimumPriority = checkPriority(c.Kubeless.MinimumPriority) + c.Openfaas.MinimumPriority = checkPriority(c.Openfaas.MinimumPriority) c.Slack.MessageFormatTemplate = getMessageFormatTemplate("Slack", c.Slack.MessageFormat) c.Rocketchat.MessageFormatTemplate = getMessageFormatTemplate("Rocketchat", c.Rocketchat.MessageFormat) diff --git a/config_example.yaml b/config_example.yaml index 23359bfba..15f92ac1b 100644 --- a/config_example.yaml +++ b/config_example.yaml @@ -166,5 +166,14 @@ kubeless: kubeconfig: "~/.kube/config" # Kubeconfig file to use (only if falcoside is running outside the cluster) # minimumpriority: "debug" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) +openfaas: + gatewayservice: "" # Service of OpenFaaS Gateway, "gateway" (default) + gatewaynamespace: "" # Namespace of OpenFaaS Gateway, "openfaas" (default) + gatewayport: 8080 # Port of service of OpenFaaS Gateway + functionname: "" # Name of OpenFaaS function, if not empty, OpenFaaS is enabled + functionnamespace: "" # Namespace of OpenFaaS function, "openfaas-fn" (default) + kubeconfig: "~/.kube/config" # Kubeconfig file to use (only if falcosidekick is running outside the cluster) + # minimumpriority: "debug" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + webui: url: "" # WebUI URL, if not empty, WebUI output is enabled diff --git a/handlers.go b/handlers.go index 5cb8ce2b5..cf28d6397 100644 --- a/handlers.go +++ b/handlers.go @@ -225,6 +225,10 @@ func forwardEvent(falcopayload types.FalcoPayload) { go kubelessClient.KubelessCall(falcopayload) } + if config.Openfaas.FunctionName != "" && (falcopayload.Priority >= types.Priority(config.Openfaas.MinimumPriority) || falcopayload.Rule == testRule) { + go openfaasClient.OpenfaasCall(falcopayload) + } + if config.WebUI.URL != "" { go webUIClient.WebUIPost(falcopayload) } diff --git a/main.go b/main.go index 10351615c..dbd547277 100644 --- a/main.go +++ b/main.go @@ -40,6 +40,7 @@ var ( kafkaClient *outputs.Client pagerdutyClient *outputs.Client kubelessClient *outputs.Client + openfaasClient *outputs.Client webUIClient *outputs.Client statsdClient, dogstatsdClient *statsd.Client @@ -369,6 +370,16 @@ func init() { } } + if config.Openfaas.FunctionName != "" { + var err error + openfaasClient, err = outputs.NewOpenfaasClient(config, stats, promStats, statsdClient, dogstatsdClient) + if err != nil { + log.Printf("[ERROR] : OpenFaaS - %v\n", err) + } else { + outputs.EnabledOutputs = append(outputs.EnabledOutputs, "OpenFaaS") + } + } + log.Printf("[INFO] : Enabled Outputs : %s\n", outputs.EnabledOutputs) } diff --git a/outputs/client.go b/outputs/client.go index 380f1e574..7612e74be 100644 --- a/outputs/client.go +++ b/outputs/client.go @@ -159,9 +159,12 @@ func (c *Client) Post(payload interface{}) error { switch resp.StatusCode { case http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent: //200, 201, 202, 204 log.Printf("[INFO] : %v - Post OK (%v)\n", c.OutputType, resp.StatusCode) + body, _ := ioutil.ReadAll(resp.Body) if c.OutputType == Kubeless { - body, _ := ioutil.ReadAll(resp.Body) log.Printf("[INFO] : Kubeless - Function Response : %v\n", string(body)) + } else if c.OutputType == Openfaas { + log.Printf("[INFO] : %v - Function Response : %v\n", Openfaas, + string(body)) } return nil case http.StatusBadRequest: //400 diff --git a/outputs/constants.go b/outputs/constants.go index 002d9ad28..88f6fc31a 100644 --- a/outputs/constants.go +++ b/outputs/constants.go @@ -37,4 +37,5 @@ const ( Orange string = "#ff5400" Kubeless string = "Kubeless" + Openfaas string = "OpenFaas" ) diff --git a/outputs/openfaas.go b/outputs/openfaas.go new file mode 100644 index 000000000..14ebe7c18 --- /dev/null +++ b/outputs/openfaas.go @@ -0,0 +1,84 @@ +package outputs + +import ( + "context" + "encoding/json" + "log" + "strconv" + + "github.com/DataDog/datadog-go/statsd" + "github.com/google/uuid" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + + "github.com/falcosecurity/falcosidekick/types" +) + +// NewOpenfaasClient returns a new output.Client for accessing Kubernetes. +func NewOpenfaasClient(config *types.Configuration, stats *types.Statistics, promStats *types.PromStatistics, statsdClient, dogstatsdClient *statsd.Client) (*Client, error) { + if config.Openfaas.Kubeconfig != "" { + restConfig, err := clientcmd.BuildConfigFromFlags("", config.Openfaas.Kubeconfig) + if err != nil { + return nil, err + } + clientset, err := kubernetes.NewForConfig(restConfig) + if err != nil { + return nil, err + } + return &Client{ + OutputType: Openfaas, + Config: config, + Stats: stats, + PromStats: promStats, + StatsdClient: statsdClient, + DogstatsdClient: dogstatsdClient, + KubernetesClient: clientset, + }, nil + } + return NewClient( + Openfaas, + "http://"+config.Openfaas.GatewayService+"."+config.Openfaas.GatewayNamespace+":"+strconv.Itoa(config.Openfaas.GatewayPort)+"/function/"+config.Openfaas.FunctionName+"."+config.Openfaas.FunctionNamespace, + config, + stats, + promStats, + statsdClient, + dogstatsdClient, + ) +} + +// OpenfaasCall . +func (c *Client) OpenfaasCall(falcopayload types.FalcoPayload) { + c.Stats.Openfaas.Add(Total, 1) + + if c.Config.Openfaas.Kubeconfig != "" { + str, _ := json.Marshal(falcopayload) + 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") + + res := req.Do(context.TODO()) + rawbody, err := res.Raw() + if err != nil { + go c.CountMetric(Outputs, 1, []string{"output:openfaas", "status:error"}) + c.Stats.Openfaas.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "openfaas", "status": Error}).Inc() + log.Printf("[ERROR] : %v - %v\n", Openfaas, err) + return + } + log.Printf("[INFO] : %v - Function Response : %v\n", Openfaas, string(rawbody)) + } else { + err := c.Post(falcopayload) + if err != nil { + go c.CountMetric(Outputs, 1, []string{"output:openfaas", "status:error"}) + c.Stats.Openfaas.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "openfaas", "status": Error}).Inc() + log.Printf("[ERROR] : %v - %v\n", Openfaas, err) + return + } + } + log.Printf("[INFO] : %v - Call Function \"%v\" OK\n", Openfaas, c.Config.Openfaas.FunctionName+"."+c.Config.Openfaas.FunctionNamespace) + go c.CountMetric(Outputs, 1, []string{"output:openfaas", "status:ok"}) + c.Stats.Openfaas.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "openfaas", "status": OK}).Inc() +} diff --git a/stats.go b/stats.go index 7fe4f57ca..2791a9d42 100644 --- a/stats.go +++ b/stats.go @@ -51,6 +51,7 @@ func getInitStats() *types.Statistics { Kafka: getOutputNewMap("kafka"), Pagerduty: getOutputNewMap("pagerduty"), Kubeless: getOutputNewMap("kubeless"), + Openfaas: getOutputNewMap("openfaas"), WebUI: getOutputNewMap("webui"), } stats.Falco.Add(outputs.Emergency, 0) diff --git a/types/types.go b/types/types.go index 8054fe48d..b018ed019 100644 --- a/types/types.go +++ b/types/types.go @@ -50,6 +50,7 @@ type Configuration struct { Kafka kafkaConfig Pagerduty PagerdutyConfig Kubeless kubelessConfig + Openfaas openfaasConfig WebUI WebUIOutputConfig } @@ -274,6 +275,16 @@ type kubelessConfig struct { MinimumPriority string } +type openfaasConfig struct { + GatewayNamespace string + GatewayService string + FunctionName string + FunctionNamespace string + GatewayPort int + Kubeconfig string + MinimumPriority string +} + // WebUIOutputConfig represents parameters for WebUI type WebUIOutputConfig struct { URL string @@ -314,6 +325,7 @@ type Statistics struct { Pagerduty *expvar.Map CloudEvents *expvar.Map Kubeless *expvar.Map + Openfaas *expvar.Map WebUI *expvar.Map }