From d24a4e9c8b26002a9152c9f7943f9e106a3d6c24 Mon Sep 17 00:00:00 2001 From: Thomas Labarussias Date: Mon, 24 Apr 2023 19:03:34 +0200 Subject: [PATCH] add n8n output Signed-off-by: Thomas Labarussias --- README.md | 18 ++++++++++++++++++ config.go | 8 ++++++++ config_example.yaml | 9 +++++++++ handlers.go | 4 ++++ main.go | 11 +++++++++++ outputs/n8n.go | 38 ++++++++++++++++++++++++++++++++++++++ stats.go | 1 + types/types.go | 13 +++++++++++++ 8 files changed, 102 insertions(+) create mode 100644 outputs/n8n.go diff --git a/README.md b/README.md index 29c068c3b..fc4e6acbc 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,10 @@ It works as a single endpoint for as many as you want `Falco` instances : - [**AWS Security Lake**](https://aws.amazon.com/security-lake/) +### Workflow + +- [**n8n**](https://n8n.io/) + ### Other - [**Policy Report**](https://github.com/kubernetes-sigs/wg-policy-prototypes/tree/master/policy-report/falco-adapter) @@ -585,6 +589,15 @@ telegram: # chatid: "" # telegram Identifier of the shared chat # checkcert: true # check if ssl certificate of the output is valid (default: true) # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + +n8n: + # address: "" # N8N address, if not empty, N8N output is enabled + # user: "" # Username to authenticate with N8N in basic auth + # password: "" # Password to authenticate with N8N in basic auth + # headerauthname: "" # Header Auth Key to authenticate with N8N + # headerauthvalue: "" # Header Auth Value to authenticate with N8N + # checkcert: true # check if ssl certificate of the output is valid (default: true) + # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) ``` Usage : @@ -1076,6 +1089,11 @@ order is - **TELEGRAM_CHATID**: telegram Identifier of the shared chat - **TELEGRAM_CHECKCERT**: check if ssl certificate of the output is valid (default: true) - **TELEGRAM_MINIMUMPRIORITY**: minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) +- **N8N_ADDRESS**: N8N address, if not empty, N8N output is enabled +- **N8N_USER**: Password to authenticate with N8N in basic auth +- **N8N_PASSWORD**: Header Auth Value to authenticate with N8N +- **N8N_CHECKCERT**: check if ssl certificate of the output is valid (default: true) +- **N8N_MINIMUMPRIORITY**: minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) #### Slack/Rocketchat/Mattermost/Googlechat Message Formatting diff --git a/config.go b/config.go index c9f4ca287..144bfd2c1 100644 --- a/config.go +++ b/config.go @@ -416,6 +416,14 @@ func getConfig() *types.Configuration { v.SetDefault("Redis.MutualTls", false) v.SetDefault("Redis.CheckCert", true) + v.SetDefault("N8n.Address", "") + v.SetDefault("N8n.User", "") + v.SetDefault("N8n.Password", "") + v.SetDefault("N8n.HeaderAuthName", "") + v.SetDefault("N8n.HeaderAuthValue", "") + v.SetDefault("N8n.MinimumPriority", "") + v.SetDefault("N8n.CheckCert", true) + v.SetDefault("Slack.Token", "") v.SetDefault("Slack.ChatID", "") v.SetDefault("Slack.MinimumPriority", "") diff --git a/config_example.yaml b/config_example.yaml index 67ecae785..951e0d4ca 100644 --- a/config_example.yaml +++ b/config_example.yaml @@ -422,3 +422,12 @@ telegram: # chatid: "" # telegram Identifier of the shared chat # checkcert: true # check if ssl certificate of the output is valid (default: true) # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) + +n8n: + # address: "" # N8N address, if not empty, N8N output is enabled + # user: "" # Username to authenticate with N8N in basic auth + # password: "" # Password to authenticate with N8N in basic auth + # headerauthname: "" # Header Auth Key to authenticate with N8N + # headerauthvalue: "" # Header Auth Value to authenticate with N8N + # checkcert: true # check if ssl certificate of the output is valid (default: true) + # minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default) diff --git a/handlers.go b/handlers.go index 3582c13a7..a4465db70 100644 --- a/handlers.go +++ b/handlers.go @@ -386,4 +386,8 @@ func forwardEvent(falcopayload types.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) + } } diff --git a/main.go b/main.go index 16ffe5078..1c97dd9a3 100644 --- a/main.go +++ b/main.go @@ -66,6 +66,7 @@ var ( timescaleDBClient *outputs.Client redisClient *outputs.Client telegramClient *outputs.Client + n8nClient *outputs.Client statsdClient, dogstatsdClient *statsd.Client config *types.Configuration @@ -680,6 +681,16 @@ func init() { } } + if config.N8N.Address != "" { + var err error + n8nClient, err = outputs.NewClient("n8n", config.N8N.Address, false, config.N8N.CheckCert, config, stats, promStats, statsdClient, dogstatsdClient) + if err != nil { + config.N8N.Address = "" + } else { + outputs.EnabledOutputs = append(outputs.EnabledOutputs, "n8n") + } + } + log.Printf("[INFO] : Falco Sidekick version: %s\n", GetVersionInfo().GitVersion) log.Printf("[INFO] : Enabled Outputs : %s\n", outputs.EnabledOutputs) diff --git a/outputs/n8n.go b/outputs/n8n.go new file mode 100644 index 000000000..498499b00 --- /dev/null +++ b/outputs/n8n.go @@ -0,0 +1,38 @@ +package outputs + +import ( + "log" + + "github.com/falcosecurity/falcosidekick/types" +) + +// N8NPost posts event to an URL +func (c *Client) N8NPost(falcopayload types.FalcoPayload) { + c.Stats.N8N.Add(Total, 1) + + if c.Config.N8N.User != "" && c.Config.N8N.Password != "" { + c.httpClientLock.Lock() + defer c.httpClientLock.Unlock() + c.BasicAuth(c.Config.N8N.User, c.Config.N8N.Password) + } + + if c.Config.N8N.HeaderAuthName != "" && c.Config.N8N.HeaderAuthValue != "" { + c.httpClientLock.Lock() + defer c.httpClientLock.Unlock() + c.AddHeader(c.Config.N8N.HeaderAuthName, c.Config.N8N.HeaderAuthValue) + } + + err := c.Post(falcopayload) + if err != nil { + go c.CountMetric(Outputs, 1, []string{"output:n8n", "status:error"}) + c.Stats.N8N.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "n8n", "status": Error}).Inc() + log.Printf("[ERROR] : N8N - %v\n", err.Error()) + return + } + + // Setting the success status + go c.CountMetric(Outputs, 1, []string{"output:n8n", "status:ok"}) + c.Stats.N8N.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "n8n", "status": OK}).Inc() +} diff --git a/stats.go b/stats.go index 98fa85821..f0ff8fd64 100644 --- a/stats.go +++ b/stats.go @@ -77,6 +77,7 @@ func getInitStats() *types.Statistics { TimescaleDB: getOutputNewMap("timescaledb"), Redis: getOutputNewMap("redis"), Telegram: getOutputNewMap("telegram"), + N8N: getOutputNewMap("n8n"), } stats.Falco.Add(outputs.Emergency, 0) stats.Falco.Add(outputs.Alert, 0) diff --git a/types/types.go b/types/types.go index ace26ec11..ec3380eb6 100644 --- a/types/types.go +++ b/types/types.go @@ -100,6 +100,7 @@ type Configuration struct { TimescaleDB TimescaleDBConfig Redis RedisConfig Telegram TelegramConfig + N8N N8NConfig } // SlackOutputConfig represents parameters for Slack @@ -649,6 +650,17 @@ type TelegramConfig struct { CheckCert bool } +// N8NConfig represents config parameters for N8N +type N8NConfig struct { + Address string + User string + Password string + HeaderAuthName string + HeaderAuthValue string + MinimumPriority string + CheckCert bool +} + // Statistics is a struct to store stastics type Statistics struct { Requests *expvar.Map @@ -710,6 +722,7 @@ type Statistics struct { TimescaleDB *expvar.Map Redis *expvar.Map Telegram *expvar.Map + N8N *expvar.Map } // PromStatistics is a struct to store prometheus metrics