diff --git a/config.go b/config.go index 8214416154..951f4351ca 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.GatewayAddress", "gateway") + v.SetDefault("Openfaas.FunctionName", "") + v.SetDefault("Openfaas.FunctionNamespace", "openfaas-fn") + v.SetDefault("Openfaas.Port", 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/handlers.go b/handlers.go index 5cb8ce2b5f..cf28d6397f 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 10351615c2..dbd547277f 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/constants.go b/outputs/constants.go index 002d9ad28a..ded783596d 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 0000000000..d4a123198a --- /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.GatewayAddress+"."+config.Openfaas.GatewayNamespace+":"+strconv.Itoa(config.Openfaas.Port)+"/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.GatewayAddress + ":" + strconv.Itoa(c.Config.Openfaas.Port) + "/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] : Openfaas - %v\n", err) + return + } + log.Printf("[INFO] : OpenFaaS - Function Response : %v\n", 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] : OpenFaaS - %v\n", err) + return + } + } + log.Printf("[INFO] : OpenFaaS - Call Function \"%v\" OK\n", 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/types/types.go b/types/types.go index 8054fe48d7..ef12dc1eb9 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 + GatewayAddress string + FunctionName string + FunctionNamespace string + Port 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 }