From 1f60214d9355741bb2001e8a5cbc0d81f8d60177 Mon Sep 17 00:00:00 2001 From: Carlos Panato Date: Sun, 15 Nov 2020 18:14:43 +0100 Subject: [PATCH 1/3] add golangci-lint Signed-off-by: Carlos Panato --- .gitignore | 1 + .golangci.yml | 28 ++++++++++++++++++++++ Makefile | 60 ++++++++++++++++++++++++++++++++++++++++++++-- hack/go_install.sh | 43 +++++++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 .golangci.yml create mode 100755 hack/go_install.sh diff --git a/.gitignore b/.gitignore index b64891130..f6af33033 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ falcosidekick .vscode .idea *.swp +/hack/tools/bin/* diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 000000000..d80135709 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,28 @@ +run: + deadline: 5m + skip-files: + - "zz_generated.*\\.go$" +linters: + disable-all: true + enable: + - deadcode + - goconst + - gofmt + - golint + - gosec + - govet + - ineffassign + - interfacer + - maligned + - misspell + - nakedret + - prealloc + - structcheck + - unconvert + - varcheck + # Run with --fast=false for more extensive checks + fast: true + include: + - EXC0002 # include "missing comments" issues from golint + max-issues-per-linter: 0 + max-same-issues: 0 diff --git a/Makefile b/Makefile index 1531aabff..538460028 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,70 @@ +# Ensure Make is run with bash shell as some syntax below is bash-specific SHELL=/bin/bash -o pipefail -GO ?= go +.DEFAULT_GOAL:=help +GOPATH := $(shell go env GOPATH) +GOARCH := $(shell go env GOARCH) +GOOS := $(shell go env GOOS) +GOPROXY := $(shell go env GOPROXY) +ifeq ($(GOPROXY),) +GOPROXY := https://proxy.golang.org +endif +export GOPROXY +GO ?= go TEST_FLAGS ?= -v -race +# Directories. +ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) +TOOLS_DIR := hack/tools +TOOLS_BIN_DIR := $(abspath $(TOOLS_DIR)/bin) +GO_INSTALL = ./hack/go_install.sh + +# Binaries. +GOLANGCI_LINT_VER := v1.32.2 +GOLANGCI_LINT_BIN := golangci-lint +GOLANGCI_LINT := $(TOOLS_BIN_DIR)/$(GOLANGCI_LINT_BIN)-$(GOLANGCI_LINT_VER) + +## -------------------------------------- +## Build +## -------------------------------------- + .PHONY: falcosidekick falcosidekick: $(GO) build -o $@ +## -------------------------------------- +## Test +## -------------------------------------- + .PHONY: test test: $(GO) vet ./... - $(GO) test ${TEST_FLAGS} ./... \ No newline at end of file + $(GO) test ${TEST_FLAGS} ./... + +## -------------------------------------- +## Linting +## -------------------------------------- + +.PHONY: lint +lint: $(GOLANGCI_LINT) ## Lint codebase + $(GOLANGCI_LINT) run -v + +lint-full: $(GOLANGCI_LINT) ## Run slower linters to detect possible issues + $(GOLANGCI_LINT) run -v --fast=false + +## -------------------------------------- +## Tooling Binaries +## -------------------------------------- + +$(GOLANGCI_LINT): ## Build golangci-lint from tools folder. + GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) github.com/golangci/golangci-lint/cmd/golangci-lint $(GOLANGCI_LINT_BIN) $(GOLANGCI_LINT_VER) + + +## -------------------------------------- +## Cleanup / Verification +## -------------------------------------- + +.PHONY: clean +clean: + rm -rf hack/tools/bin diff --git a/hack/go_install.sh b/hack/go_install.sh new file mode 100755 index 000000000..d7f58cbcd --- /dev/null +++ b/hack/go_install.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +if [ -z "${1}" ]; then + echo "must provide module as first parameter" + exit 1 +fi + +if [ -z "${2}" ]; then + echo "must provide binary name as second parameter" + exit 1 +fi + +if [ -z "${3}" ]; then + echo "must provide version as third parameter" + exit 1 +fi + +if [ -z "${GOBIN}" ]; then + echo "GOBIN is not set. Must set GOBIN to install the bin in a specified directory." + exit 1 +fi + +tmp_dir=$(mktemp -d -t goinstall_XXXXXXXXXX) +function clean { + rm -rf "${tmp_dir}" +} +trap clean EXIT + +rm "${GOBIN}/${2}"* || true + +cd "${tmp_dir}" + +# create a new module in the tmp directory +go mod init fake/mod + +# install the golang module specified as the first argument +go get -tags tools "${1}@${3}" +mv "${GOBIN}/${2}" "${GOBIN}/${2}-${3}" +ln -sf "${GOBIN}/${2}-${3}" "${GOBIN}/${2}" From abd519392fabf09db18b0565c871d212a1f87957 Mon Sep 17 00:00:00 2001 From: Carlos Panato Date: Sun, 15 Nov 2020 18:14:58 +0100 Subject: [PATCH 2/3] Fix lints Signed-off-by: Carlos Panato --- config.go | 5 + handlers.go | 61 ++++-- main.go | 19 ++ outputs/alertmanager.go | 24 +-- outputs/alertmanager_test.go | 2 +- outputs/aws.go | 381 ++++++++++++++++++----------------- outputs/azure.go | 20 +- outputs/client.go | 7 +- outputs/client_test.go | 13 +- outputs/constants.go | 35 ++++ outputs/datadog.go | 20 +- outputs/datadog_test.go | 3 +- outputs/discord.go | 34 ++-- outputs/elasticsearch.go | 13 +- outputs/gcp.go | 20 +- outputs/influxdb.go | 81 ++++---- outputs/influxdb_test.go | 41 ++-- outputs/loki.go | 13 +- outputs/loki_test.go | 65 +++--- outputs/mattermost.go | 55 ++--- outputs/nats.go | 90 +++++---- outputs/opsgenie.go | 17 +- outputs/opsgenie_test.go | 57 +++--- outputs/rocketchat.go | 57 +++--- outputs/slack.go | 57 +++--- outputs/slack_test.go | 7 +- outputs/smtp.go | 220 ++++++++++---------- outputs/smtp_templates.go | 162 +++++++-------- outputs/statsd.go | 116 ++++++----- outputs/teams.go | 225 +++++++++++---------- outputs/teams_test.go | 93 ++++----- outputs/webhook.go | 11 +- types/types.go | 6 +- 33 files changed, 1075 insertions(+), 955 deletions(-) create mode 100644 outputs/constants.go diff --git a/config.go b/config.go index 772fb02af..932258166 100644 --- a/config.go +++ b/config.go @@ -125,6 +125,7 @@ func getConfig() *types.Configuration { log.Printf("[ERROR] : Error when reading config file : %v\n", err) } } + v.GetStringMapString("customfields") v.GetStringMapString("Webhook.CustomHeaders") v.Unmarshal(c) @@ -138,6 +139,7 @@ func getConfig() *types.Configuration { } } } + if value, present := os.LookupEnv("WEBHOOK_CUSTOMHEADERS"); present { customfields := strings.Split(value, ",") for _, label := range customfields { @@ -182,6 +184,7 @@ func checkPriority(prio string) string { if match { return prio } + return "" } @@ -192,7 +195,9 @@ func getMessageFormatTemplate(output, temp string) *template.Template { if err != nil { log.Fatalf("[ERROR] : Error compiling %v message template : %v\n", output, err) } + return t } + return nil } diff --git a/handlers.go b/handlers.go index c947f903e..f001372c3 100644 --- a/handlers.go +++ b/handlers.go @@ -13,6 +13,8 @@ import ( "github.com/falcosecurity/falcosidekick/types" ) +const TestRule string = "Test rule" + func getPriorityMap() map[string]int { return map[string]int{ "emergency": 8, @@ -37,6 +39,7 @@ func mainHandler(w http.ResponseWriter, r *http.Request) { 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 } @@ -46,6 +49,7 @@ func mainHandler(w http.ResponseWriter, r *http.Request) { 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 } @@ -125,61 +129,78 @@ func newFalcoPayload(payload io.Reader) (types.FalcoPayload, error) { } func forwardEvent(falcopayload types.FalcoPayload) { - if config.Slack.WebhookURL != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Slack.MinimumPriority)] || falcopayload.Rule == "Test rule") { + if config.Slack.WebhookURL != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Slack.MinimumPriority)] || falcopayload.Rule == TestRule) { go slackClient.SlackPost(falcopayload) } - if config.Rocketchat.WebhookURL != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Rocketchat.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.Rocketchat.WebhookURL != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Rocketchat.MinimumPriority)] || falcopayload.Rule == TestRule) { go rocketchatClient.RocketchatPost(falcopayload) } - if config.Mattermost.WebhookURL != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Mattermost.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.Mattermost.WebhookURL != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Mattermost.MinimumPriority)] || falcopayload.Rule == TestRule) { go mattermostClient.MattermostPost(falcopayload) } - if config.Teams.WebhookURL != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Teams.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.Teams.WebhookURL != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Teams.MinimumPriority)] || falcopayload.Rule == TestRule) { go teamsClient.TeamsPost(falcopayload) } - if config.Datadog.APIKey != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Datadog.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.Datadog.APIKey != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Datadog.MinimumPriority)] || falcopayload.Rule == TestRule) { go datadogClient.DatadogPost(falcopayload) } - if config.Discord.WebhookURL != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Discord.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.Discord.WebhookURL != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Discord.MinimumPriority)] || falcopayload.Rule == TestRule) { go discordClient.DiscordPost(falcopayload) } - if config.Alertmanager.HostPort != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Alertmanager.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.Alertmanager.HostPort != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Alertmanager.MinimumPriority)] || falcopayload.Rule == TestRule) { go alertmanagerClient.AlertmanagerPost(falcopayload) } - if config.Elasticsearch.HostPort != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Elasticsearch.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.Elasticsearch.HostPort != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Elasticsearch.MinimumPriority)] || falcopayload.Rule == TestRule) { go elasticsearchClient.ElasticsearchPost(falcopayload) } - if config.Influxdb.HostPort != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Influxdb.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.Influxdb.HostPort != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Influxdb.MinimumPriority)] || falcopayload.Rule == TestRule) { go influxdbClient.InfluxdbPost(falcopayload) } - if config.Loki.HostPort != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Loki.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.Loki.HostPort != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Loki.MinimumPriority)] || falcopayload.Rule == TestRule) { go lokiClient.LokiPost(falcopayload) } - if config.Nats.HostPort != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Nats.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.Nats.HostPort != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Nats.MinimumPriority)] || falcopayload.Rule == TestRule) { go natsClient.NatsPublish(falcopayload) } - if config.AWS.Lambda.FunctionName != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.AWS.Lambda.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.AWS.Lambda.FunctionName != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.AWS.Lambda.MinimumPriority)] || falcopayload.Rule == TestRule) { go awsClient.InvokeLambda(falcopayload) } - if config.AWS.SQS.URL != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.AWS.SQS.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.AWS.SQS.URL != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.AWS.SQS.MinimumPriority)] || falcopayload.Rule == TestRule) { go awsClient.SendMessage(falcopayload) } - if config.AWS.SNS.TopicArn != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.AWS.SNS.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.AWS.SNS.TopicArn != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.AWS.SNS.MinimumPriority)] || falcopayload.Rule == TestRule) { go awsClient.PublishTopic(falcopayload) } - if config.SMTP.HostPort != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.SMTP.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.SMTP.HostPort != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.SMTP.MinimumPriority)] || falcopayload.Rule == TestRule) { go smtpClient.SendMail(falcopayload) } - if config.Opsgenie.APIKey != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Opsgenie.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.Opsgenie.APIKey != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Opsgenie.MinimumPriority)] || falcopayload.Rule == TestRule) { go opsgenieClient.OpsgeniePost(falcopayload) } - if config.Webhook.Address != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Webhook.MinimumPriority)] || falcopayload.Rule == "Test rule") { + if config.Webhook.Address != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Webhook.MinimumPriority)] || falcopayload.Rule == TestRule) { go webhookClient.WebhookPost(falcopayload) } - if config.Azure.EventHub.Name != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Azure.EventHub.MinimumPriority)] || falcopayload.Rule == "Test rule") { + + if config.Azure.EventHub.Name != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Azure.EventHub.MinimumPriority)] || falcopayload.Rule == TestRule) { go azureClient.EventHubPost(falcopayload) } - if config.GCP.PubSub.Topic != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.GCP.PubSub.MinimumPriority)] || falcopayload.Rule == "Test rule") { - go gcpClient.GCPPublishTopic(falcopayload) + + if config.GCP.PubSub.Topic != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.GCP.PubSub.MinimumPriority)] || falcopayload.Rule == TestRule) { + go gcpClient.GCPPublishTopic(falcopayload) } } diff --git a/main.go b/main.go index 1ecc2b269..ba3eba549 100644 --- a/main.go +++ b/main.go @@ -56,6 +56,7 @@ func init() { enabledOutputsText += "StatsD " } } + if config.Dogstatsd.Forwarder != "" { var err error dogstatsdClient, err = outputs.NewStatsdClient("DogStatsD", config, stats) @@ -85,6 +86,7 @@ func init() { enabledOutputsText += "Slack " } } + if config.Rocketchat.WebhookURL != "" { var err error rocketchatClient, err = outputs.NewClient("Rocketchat", config.Rocketchat.WebhookURL, config, stats, promStats, statsdClient, dogstatsdClient) @@ -94,6 +96,7 @@ func init() { enabledOutputsText += "Rocketchat " } } + if config.Mattermost.WebhookURL != "" { var err error mattermostClient, err = outputs.NewClient("Mattermost", config.Mattermost.WebhookURL, config, stats, promStats, statsdClient, dogstatsdClient) @@ -103,6 +106,7 @@ func init() { enabledOutputsText += "Mattermost " } } + if config.Teams.WebhookURL != "" { var err error teamsClient, err = outputs.NewClient("Teams", config.Teams.WebhookURL, config, stats, promStats, statsdClient, dogstatsdClient) @@ -112,6 +116,7 @@ func init() { enabledOutputsText += "Teams " } } + if config.Datadog.APIKey != "" { var err error datadogClient, err = outputs.NewClient("Datadog", config.Datadog.Host+outputs.DatadogPath+"?api_key="+config.Datadog.APIKey, config, stats, promStats, statsdClient, dogstatsdClient) @@ -121,6 +126,7 @@ func init() { enabledOutputsText += "Datadog " } } + if config.Discord.WebhookURL != "" { var err error discordClient, err = outputs.NewClient("Discord", config.Discord.WebhookURL, config, stats, promStats, statsdClient, dogstatsdClient) @@ -130,6 +136,7 @@ func init() { enabledOutputsText += "Discord " } } + if config.Alertmanager.HostPort != "" { var err error alertmanagerClient, err = outputs.NewClient("AlertManager", config.Alertmanager.HostPort+outputs.AlertmanagerURI, config, stats, promStats, statsdClient, dogstatsdClient) @@ -139,6 +146,7 @@ func init() { enabledOutputsText += "AlertManager " } } + if config.Elasticsearch.HostPort != "" { var err error elasticsearchClient, err = outputs.NewClient("Elasticsearch", config.Elasticsearch.HostPort+"/"+config.Elasticsearch.Index+"/"+config.Elasticsearch.Type, config, stats, promStats, statsdClient, dogstatsdClient) @@ -148,6 +156,7 @@ func init() { enabledOutputsText += "Elasticsearch " } } + if config.Loki.HostPort != "" { var err error lokiClient, err = outputs.NewClient("Loki", config.Loki.HostPort+"/api/prom/push", config, stats, promStats, statsdClient, dogstatsdClient) @@ -157,6 +166,7 @@ func init() { enabledOutputsText += "Loki " } } + if config.Nats.HostPort != "" { var err error natsClient, err = outputs.NewClient("NATS", config.Nats.HostPort, config, stats, promStats, statsdClient, dogstatsdClient) @@ -166,11 +176,13 @@ func init() { enabledOutputsText += "NATS " } } + if config.Influxdb.HostPort != "" { var credentials string if config.Influxdb.User != "" && config.Influxdb.Password != "" { credentials = "&u=" + config.Influxdb.User + "&p=" + config.Influxdb.Password } + var err error influxdbClient, err = outputs.NewClient("Influxdb", config.Influxdb.HostPort+"/write?db="+config.Influxdb.Database+credentials, config, stats, promStats, statsdClient, dogstatsdClient) if err != nil { @@ -179,6 +191,7 @@ func init() { enabledOutputsText += "Influxdb " } } + if config.AWS.Lambda.FunctionName != "" || config.AWS.SQS.URL != "" || config.AWS.SNS.TopicArn != "" { var err error awsClient, err = outputs.NewAWSClient(config, stats, promStats, statsdClient, dogstatsdClient) @@ -201,6 +214,7 @@ func init() { } } } + if config.SMTP.HostPort != "" && config.SMTP.From != "" && config.SMTP.To != "" { var err error smtpClient, err = outputs.NewSMTPClient(config, stats, promStats, statsdClient, dogstatsdClient) @@ -210,6 +224,7 @@ func init() { enabledOutputsText += "SMTP " } } + if config.Opsgenie.APIKey != "" { var err error url := "https://api.opsgenie.com/v2/alerts" @@ -223,6 +238,7 @@ func init() { enabledOutputsText += "Opsgenie " } } + if config.Webhook.Address != "" { var err error webhookClient, err = outputs.NewClient("Webhook", config.Webhook.Address, config, stats, promStats, statsdClient, dogstatsdClient) @@ -232,6 +248,7 @@ func init() { enabledOutputsText += "Webhook " } } + if config.Azure.EventHub.Name != "" { var err error azureClient, err = outputs.NewEventHubClient(config, stats, promStats, statsdClient, dogstatsdClient) @@ -244,6 +261,7 @@ func init() { } } } + if config.GCP.PubSub.ProjectID != "" && config.GCP.PubSub.Topic != "" && config.GCP.Credentials != "" { var err error gcpClient, err = outputs.NewGCPClient(config, stats, promStats, statsdClient, dogstatsdClient) @@ -267,6 +285,7 @@ func main() { if config.Debug { log.Printf("[INFO] : Debug mode : %v\n", config.Debug) } + if err := http.ListenAndServe(":"+strconv.Itoa(config.ListenPort), nil); err != nil { log.Fatalf("[ERROR] : %v\n", err.Error()) } diff --git a/outputs/alertmanager.go b/outputs/alertmanager.go index 5c9749660..6f62405f4 100644 --- a/outputs/alertmanager.go +++ b/outputs/alertmanager.go @@ -35,25 +35,25 @@ func newAlertmanagerPayload(falcopayload types.FalcoPayload) []alertmanagerPaylo switch { case d == 0: jj = "0" - falcopayload.Priority = "warning" + falcopayload.Priority = Warning case d < 10: jj = "<10" - falcopayload.Priority = "warning" + falcopayload.Priority = Warning case d > 10000: jj = ">10000" - falcopayload.Priority = "critical" + falcopayload.Priority = Critical case d > 1000: jj = ">1000" - falcopayload.Priority = "critical" + falcopayload.Priority = Critical case d > 100: jj = ">100" - falcopayload.Priority = "critical" + falcopayload.Priority = Critical case d > 10: jj = ">10" - falcopayload.Priority = "warning" + falcopayload.Priority = Warning default: jj = j.(string) - falcopayload.Priority = "critical" + falcopayload.Priority = Critical } amPayload.Labels[i] = jj @@ -86,11 +86,11 @@ func newAlertmanagerPayload(falcopayload types.FalcoPayload) []alertmanagerPaylo func (c *Client) AlertmanagerPost(falcopayload types.FalcoPayload) { err := c.Post(newAlertmanagerPayload(falcopayload)) if err != nil { - c.Stats.Alertmanager.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "alertmanager", "status": "error"}).Inc() + c.Stats.Alertmanager.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "alertmanager", "status": Error}).Inc() } else { - c.Stats.Alertmanager.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "alertmanager", "status": "ok"}).Inc() + c.Stats.Alertmanager.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "alertmanager", "status": OK}).Inc() } - c.Stats.Alertmanager.Add("total", 1) + c.Stats.Alertmanager.Add(Total, 1) } diff --git a/outputs/alertmanager_test.go b/outputs/alertmanager_test.go index db7ae4c6f..1e1f6eb80 100644 --- a/outputs/alertmanager_test.go +++ b/outputs/alertmanager_test.go @@ -17,7 +17,7 @@ func TestNewAlertmanagerPayload(t *testing.T) { var o1, o2 alertmanagerPayload json.Unmarshal([]byte(expectedOutput), &o1) - json.Unmarshal([]byte(s), &o2) + json.Unmarshal(s, &o2) if !reflect.DeepEqual(o1, o2) { // t.Fatalf("\nexpected payload: \n%v\ngot: \n%v\n", o1, o2) diff --git a/outputs/aws.go b/outputs/aws.go index 1a6eb19f9..fbfb40a59 100644 --- a/outputs/aws.go +++ b/outputs/aws.go @@ -1,190 +1,191 @@ -package outputs - -import ( - "encoding/base64" - "encoding/json" - "errors" - "log" - "net/url" - "os" - - "github.com/DataDog/datadog-go/statsd" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/lambda" - "github.com/aws/aws-sdk-go/service/sns" - "github.com/aws/aws-sdk-go/service/sqs" - "github.com/aws/aws-sdk-go/service/sts" - "github.com/falcosecurity/falcosidekick/types" -) - -// NewAWSClient returns a new output.Client for accessing the AWS API. -func NewAWSClient(config *types.Configuration, stats *types.Statistics, promStats *types.PromStatistics, statsdClient, dogstatsdClient *statsd.Client) (*Client, error) { - - if config.AWS.AccessKeyID != "" && config.AWS.SecretAccessKey != "" && config.AWS.Region != "" { - os.Setenv("AWS_ACCESS_KEY_ID", config.AWS.AccessKeyID) - os.Setenv("AWS_SECRET_ACCESS_KEY", config.AWS.SecretAccessKey) - os.Setenv("AWS_DEFAULT_REGION", config.AWS.Region) - } - - sess, err := session.NewSession(&aws.Config{ - Region: aws.String(config.AWS.Region)}, - ) - if err != nil { - log.Printf("[ERROR] : AWS - %v\n", "Error while creating AWS Session") - return nil, errors.New("Error while creating AWS Session") - } - - _, err = sts.New(session.New()).GetCallerIdentity(&sts.GetCallerIdentityInput{}) - if err != nil { - log.Printf("[ERROR] : AWS - %v\n", "Error while getting AWS Token") - return nil, errors.New("Error while getting AWS Token") - } - - var endpointURL *url.URL - endpointURL, err = url.Parse(config.AWS.SQS.URL) - if err != nil { - log.Printf("[ERROR] : AWS SQS - %v\n", err.Error()) - return nil, ErrClientCreation - } - - return &Client{ - OutputType: "AWS", - EndpointURL: endpointURL, - Config: config, - AWSSession: sess, - Stats: stats, - PromStats: promStats, - StatsdClient: statsdClient, - DogstatsdClient: dogstatsdClient, - }, nil -} - -// InvokeLambda invokes a lambda function -func (c *Client) InvokeLambda(falcopayload types.FalcoPayload) { - svc := lambda.New(c.AWSSession) - - f, _ := json.Marshal(falcopayload) - - input := &lambda.InvokeInput{ - FunctionName: aws.String(c.Config.AWS.Lambda.FunctionName), - InvocationType: aws.String(c.Config.AWS.Lambda.InvocationType), - LogType: aws.String(c.Config.AWS.Lambda.LogType), - Payload: f, - } - - c.Stats.AWSLambda.Add("total", 1) - - resp, err := svc.Invoke(input) - if err != nil { - go c.CountMetric("outputs", 1, []string{"output:awslambda", "status:error"}) - c.Stats.AWSLambda.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "awslambda", "status": "error"}).Inc() - log.Printf("[ERROR] : %v Lambda - %v\n", c.OutputType, err.Error()) - return - } - - if c.Config.Debug == true { - r, _ := base64.StdEncoding.DecodeString(*resp.LogResult) - log.Printf("[DEBUG] : %v Lambda result : %v\n", c.OutputType, string(r)) - } - - log.Printf("[INFO] : %v Lambda - Invoke OK (%v)\n", c.OutputType, *resp.StatusCode) - go c.CountMetric("outputs", 1, []string{"output:awslambda", "status:ok"}) - c.Stats.AWSLambda.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "awslambda", "status": "ok"}).Inc() -} - -// SendMessage sends a message to SQS Queue -func (c *Client) SendMessage(falcopayload types.FalcoPayload) { - svc := sqs.New(c.AWSSession) - - f, _ := json.Marshal(falcopayload) - - input := &sqs.SendMessageInput{ - MessageBody: aws.String(string(f)), - QueueUrl: aws.String(c.Config.AWS.SQS.URL), - } - - c.Stats.AWSSQS.Add("total", 1) - - resp, err := svc.SendMessage(input) - if err != nil { - go c.CountMetric("outputs", 1, []string{"output:awssqs", "status:error"}) - c.Stats.AWSSQS.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "awssqs", "status": "error"}).Inc() - log.Printf("[ERROR] : %v SQS - %v\n", c.OutputType, err.Error()) - return - } - - if c.Config.Debug == true { - log.Printf("[DEBUG] : %v SQS - MD5OfMessageBody : %v\n", c.OutputType, *resp.MD5OfMessageBody) - } - - log.Printf("[INFO] : %v SQS - Send Message OK (%v)\n", c.OutputType, *resp.MessageId) - go c.CountMetric("outputs", 1, []string{"output:awssqs", "status:ok"}) - c.Stats.AWSSQS.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "awssqs", "status": "ok"}).Inc() -} - -// PublishTopic sends a message to a SNS Topic -func (c *Client) PublishTopic(falcopayload types.FalcoPayload) { - svc := sns.New(c.AWSSession) - - var msg *sns.PublishInput - - if c.Config.AWS.SNS.RawJSON == true { - f, _ := json.Marshal(falcopayload) - msg = &sns.PublishInput{ - Message: aws.String(string(f)), - TopicArn: aws.String(c.Config.AWS.SNS.TopicArn), - } - } else { - msg = &sns.PublishInput{ - Message: aws.String(string(falcopayload.Output)), - MessageAttributes: map[string]*sns.MessageAttributeValue{ - "priority": &sns.MessageAttributeValue{ - DataType: aws.String("String"), - StringValue: aws.String(falcopayload.Priority), - }, - "rule": &sns.MessageAttributeValue{ - DataType: aws.String("String"), - StringValue: aws.String(falcopayload.Rule), - }, - }, - TopicArn: aws.String(c.Config.AWS.SNS.TopicArn), - } - - for i, j := range falcopayload.OutputFields { - switch j.(type) { - case string: - msg.MessageAttributes[i] = &sns.MessageAttributeValue{ - DataType: aws.String("String"), - StringValue: aws.String(j.(string)), - } - default: - continue - } - } - } - - if c.Config.Debug == true { - p, _ := json.Marshal(msg) - log.Printf("[DEBUG] : %v SNS - Message : %v\n", c.OutputType, string(p)) - } - - c.Stats.AWSSNS.Add("total", 1) - resp, err := svc.Publish(msg) - if err != nil { - go c.CountMetric("outputs", 1, []string{"output:awssns", "status:error"}) - c.Stats.AWSSNS.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "awssns", "status": "error"}).Inc() - log.Printf("[ERROR] : %v - %v\n", c.OutputType, err.Error()) - return - } - - log.Printf("[INFO] : %v SNS - Send to topic OK (%v)\n", c.OutputType, *resp.MessageId) - go c.CountMetric("outputs", 1, []string{"output:awssns", "status:ok"}) - c.Stats.AWSSNS.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "awssns", "status": "ok"}).Inc() -} +package outputs + +import ( + "encoding/base64" + "encoding/json" + "errors" + "log" + "net/url" + "os" + + "github.com/falcosecurity/falcosidekick/types" + + "github.com/DataDog/datadog-go/statsd" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/lambda" + "github.com/aws/aws-sdk-go/service/sns" + "github.com/aws/aws-sdk-go/service/sqs" + "github.com/aws/aws-sdk-go/service/sts" +) + +// NewAWSClient returns a new output.Client for accessing the AWS API. +func NewAWSClient(config *types.Configuration, stats *types.Statistics, promStats *types.PromStatistics, statsdClient, dogstatsdClient *statsd.Client) (*Client, error) { + + if config.AWS.AccessKeyID != "" && config.AWS.SecretAccessKey != "" && config.AWS.Region != "" { + os.Setenv("AWS_ACCESS_KEY_ID", config.AWS.AccessKeyID) + os.Setenv("AWS_SECRET_ACCESS_KEY", config.AWS.SecretAccessKey) + os.Setenv("AWS_DEFAULT_REGION", config.AWS.Region) + } + + sess, err := session.NewSession(&aws.Config{ + Region: aws.String(config.AWS.Region)}, + ) + if err != nil { + log.Printf("[ERROR] : AWS - %v\n", "Error while creating AWS Session") + return nil, errors.New("Error while creating AWS Session") + } + + _, err = sts.New(session.New()).GetCallerIdentity(&sts.GetCallerIdentityInput{}) + if err != nil { + log.Printf("[ERROR] : AWS - %v\n", "Error while getting AWS Token") + return nil, errors.New("Error while getting AWS Token") + } + + var endpointURL *url.URL + endpointURL, err = url.Parse(config.AWS.SQS.URL) + if err != nil { + log.Printf("[ERROR] : AWS SQS - %v\n", err.Error()) + return nil, ErrClientCreation + } + + return &Client{ + OutputType: "AWS", + EndpointURL: endpointURL, + Config: config, + AWSSession: sess, + Stats: stats, + PromStats: promStats, + StatsdClient: statsdClient, + DogstatsdClient: dogstatsdClient, + }, nil +} + +// InvokeLambda invokes a lambda function +func (c *Client) InvokeLambda(falcopayload types.FalcoPayload) { + svc := lambda.New(c.AWSSession) + + f, _ := json.Marshal(falcopayload) + + input := &lambda.InvokeInput{ + FunctionName: aws.String(c.Config.AWS.Lambda.FunctionName), + InvocationType: aws.String(c.Config.AWS.Lambda.InvocationType), + LogType: aws.String(c.Config.AWS.Lambda.LogType), + Payload: f, + } + + c.Stats.AWSLambda.Add("total", 1) + + resp, err := svc.Invoke(input) + if err != nil { + go c.CountMetric("outputs", 1, []string{"output:awslambda", "status:error"}) + c.Stats.AWSLambda.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "awslambda", "status": Error}).Inc() + log.Printf("[ERROR] : %v Lambda - %v\n", c.OutputType, err.Error()) + return + } + + if c.Config.Debug == true { + r, _ := base64.StdEncoding.DecodeString(*resp.LogResult) + log.Printf("[DEBUG] : %v Lambda result : %v\n", c.OutputType, string(r)) + } + + log.Printf("[INFO] : %v Lambda - Invoke OK (%v)\n", c.OutputType, *resp.StatusCode) + go c.CountMetric("outputs", 1, []string{"output:awslambda", "status:ok"}) + c.Stats.AWSLambda.Add("ok", 1) + c.PromStats.Outputs.With(map[string]string{"destination": "awslambda", "status": "ok"}).Inc() +} + +// SendMessage sends a message to SQS Queue +func (c *Client) SendMessage(falcopayload types.FalcoPayload) { + svc := sqs.New(c.AWSSession) + + f, _ := json.Marshal(falcopayload) + + input := &sqs.SendMessageInput{ + MessageBody: aws.String(string(f)), + QueueUrl: aws.String(c.Config.AWS.SQS.URL), + } + + c.Stats.AWSSQS.Add("total", 1) + + resp, err := svc.SendMessage(input) + if err != nil { + go c.CountMetric("outputs", 1, []string{"output:awssqs", "status:error"}) + c.Stats.AWSSQS.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "awssqs", "status": Error}).Inc() + log.Printf("[ERROR] : %v SQS - %v\n", c.OutputType, err.Error()) + return + } + + if c.Config.Debug == true { + log.Printf("[DEBUG] : %v SQS - MD5OfMessageBody : %v\n", c.OutputType, *resp.MD5OfMessageBody) + } + + log.Printf("[INFO] : %v SQS - Send Message OK (%v)\n", c.OutputType, *resp.MessageId) + go c.CountMetric("outputs", 1, []string{"output:awssqs", "status:ok"}) + c.Stats.AWSSQS.Add("ok", 1) + c.PromStats.Outputs.With(map[string]string{"destination": "awssqs", "status": "ok"}).Inc() +} + +// PublishTopic sends a message to a SNS Topic +func (c *Client) PublishTopic(falcopayload types.FalcoPayload) { + svc := sns.New(c.AWSSession) + + var msg *sns.PublishInput + + if c.Config.AWS.SNS.RawJSON == true { + f, _ := json.Marshal(falcopayload) + 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), + }, + "rule": { + DataType: aws.String("String"), + StringValue: aws.String(falcopayload.Rule), + }, + }, + TopicArn: aws.String(c.Config.AWS.SNS.TopicArn), + } + + for i, j := range falcopayload.OutputFields { + switch j.(type) { + case string: + msg.MessageAttributes[i] = &sns.MessageAttributeValue{ + DataType: aws.String("String"), + StringValue: aws.String(j.(string)), + } + default: + continue + } + } + } + + if c.Config.Debug == true { + p, _ := json.Marshal(msg) + log.Printf("[DEBUG] : %v SNS - Message : %v\n", c.OutputType, string(p)) + } + + c.Stats.AWSSNS.Add("total", 1) + resp, err := svc.Publish(msg) + if err != nil { + go c.CountMetric("outputs", 1, []string{"output:awssns", "status:error"}) + c.Stats.AWSSNS.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "awssns", "status": Error}).Inc() + log.Printf("[ERROR] : %v - %v\n", c.OutputType, err.Error()) + return + } + + log.Printf("[INFO] : %v SNS - Send to topic OK (%v)\n", c.OutputType, *resp.MessageId) + go c.CountMetric("outputs", 1, []string{"output:awssns", "status:ok"}) + c.Stats.AWSSNS.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "awssns", "status": OK}).Inc() +} diff --git a/outputs/azure.go b/outputs/azure.go index 1dd9b5147..588c50a4d 100644 --- a/outputs/azure.go +++ b/outputs/azure.go @@ -28,8 +28,8 @@ func (c *Client) EventHubPost(falcopayload types.FalcoPayload) { log.Printf("[INFO] : Try sending event") hub, err := eventhub.NewHubWithNamespaceNameAndEnvironment(c.Config.Azure.EventHub.Namespace, c.Config.Azure.EventHub.Name) if err != nil { - c.Stats.AzureEventHub.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "azureeventhub", "status": "error"}).Inc() + c.Stats.AzureEventHub.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "azureeventhub", "status": Error}).Inc() log.Printf("[ERROR] : %v EventHub - %v\n", c.OutputType, err.Error()) return } @@ -39,21 +39,21 @@ func (c *Client) EventHubPost(falcopayload types.FalcoPayload) { defer cancel() data, err := json.Marshal(falcopayload) if err != nil { - c.Stats.AzureEventHub.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "azureeventhub", "status": "error"}).Inc() + c.Stats.AzureEventHub.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "azureeventhub", "status": Error}).Inc() log.Printf("[ERROR] : Cannot marshal payload: %v", err.Error()) return } err = hub.Send(ctx, eventhub.NewEvent(data)) if err != nil { - c.Stats.AzureEventHub.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "azureeventhub", "status": "error"}).Inc() + c.Stats.AzureEventHub.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "azureeventhub", "status": Error}).Inc() log.Printf("[ERROR] : %v EventHub - %v\n", c.OutputType, err.Error()) return } - c.Stats.AzureEventHub.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "azureeventhub", "status": "ok"}).Inc() - log.Printf("[INFO] : Succesfully sent event") + c.Stats.AzureEventHub.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "azureeventhub", "status": OK}).Inc() + log.Printf("[INFO] : Successfully sent event") - c.Stats.AzureEventHub.Add("total", 1) + c.Stats.AzureEventHub.Add(Total, 1) } diff --git a/outputs/client.go b/outputs/client.go index dc636f91f..a5fb9dab3 100644 --- a/outputs/client.go +++ b/outputs/client.go @@ -12,9 +12,9 @@ import ( "regexp" "strings" + "cloud.google.com/go/pubsub" "github.com/DataDog/datadog-go/statsd" "github.com/aws/aws-sdk-go/aws/session" - "cloud.google.com/go/pubsub" "github.com/falcosecurity/falcosidekick/types" ) @@ -94,7 +94,10 @@ func (c *Client) Post(payload interface{}) error { customTransport := http.DefaultTransport.(*http.Transport).Clone() if c.Config.CheckCert == false { - customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + customTransport.TLSClientConfig = &tls.Config{ + // nolint: gosec + InsecureSkipVerify: true, + } } client := &http.Client{ diff --git a/outputs/client_test.go b/outputs/client_test.go index 1a0ffa37b..55b42a325 100644 --- a/outputs/client_test.go +++ b/outputs/client_test.go @@ -15,14 +15,17 @@ var falcoTestInput = `{"output":"This is a test from falcosidekick","priority":" func TestNewClient(t *testing.T) { u, _ := url.Parse("http://localhost") + config := &types.Configuration{} stats := &types.Statistics{} promStats := &types.PromStatistics{} + testClientOutput := Client{OutputType: "test", EndpointURL: u, Config: config, Stats: stats, PromStats: promStats} _, err := NewClient("test", "localhost/%*$ยจ^!/:;", config, stats, promStats, nil, nil) if err == nil { t.Fatalf("error while creating client object : %v\n", err) } + nc, _ := NewClient("test", "http://localhost", config, stats, promStats, nil, nil) if !reflect.DeepEqual(&testClientOutput, nc) { t.Fatalf("expected: %v, got: %v\n", testClientOutput, nc) @@ -56,7 +59,15 @@ func TestPost(t *testing.T) { nc, _ := NewClient("", "", &types.Configuration{}, &types.Statistics{}, &types.PromStatistics{}, nil, nil) - for i, j := range map[string]error{"/200": nil, "/400": ErrHeaderMissing, "/401": ErrClientAuthenticationError, "/403": ErrForbidden, "/404": ErrNotFound, "/422": ErrUnprocessableEntityError, "/429": ErrTooManyRequest, "/502": errors.New("502 Bad Gateway")} { + for i, j := range map[string]error{ + "/200": nil, "/400": ErrHeaderMissing, + "/401": ErrClientAuthenticationError, + "/403": ErrForbidden, + "/404": ErrNotFound, + "/422": ErrUnprocessableEntityError, + "/429": ErrTooManyRequest, + "/502": errors.New("502 Bad Gateway"), + } { nc, _ = NewClient("", ts.URL+i, &types.Configuration{}, &types.Statistics{}, &types.PromStatistics{}, nil, nil) err := nc.Post("") if !reflect.DeepEqual(err, j) { diff --git a/outputs/constants.go b/outputs/constants.go new file mode 100644 index 000000000..4550ea063 --- /dev/null +++ b/outputs/constants.go @@ -0,0 +1,35 @@ +package outputs + +const ( + OK string = "ok" + Warning string = "warning" + Alert string = "alert" + Error string = "error" + Critical string = "critical" + Emergency string = "emergency" + Notice string = "notice" + Informational string = "informational" + Debug string = "debug" + Info string = "info" + None string = "none" + + All string = "all" + Fields string = "fields" + Total string = "total" + + Rule string = "rule" + Priority string = "priority" + Time string = "time" + Text string = "text" + + DefaultFooter string = "https://github.com/falcosecurity/falcosidekick" + DefaultIconURL string = "https://raw.githubusercontent.com/falcosecurity/falcosidekick/master/imgs/falcosidekick.png" + + // Colors + PaleCyan string = "#ccfff2" + Yellow string = "#ffc700" + Red string = "#e20b0b" + LigthBlue string = "#68c2ff" + Lightcyan string = "#5bffb5" + Orange string = "#ff5400" +) diff --git a/outputs/datadog.go b/outputs/datadog.go index bbafa08be..485fbef56 100644 --- a/outputs/datadog.go +++ b/outputs/datadog.go @@ -39,12 +39,12 @@ func newDatadogPayload(falcopayload types.FalcoPayload) datadogPayload { var status string switch strings.ToLower(falcopayload.Priority) { - case "emergency", "alert", "critical", "error": - status = "error" - case "warning": - status = "warning" + case Emergency, Alert, Critical, Error: + status = Error + case Warning: + status = Warning default: - status = "info" + status = Info } d.AlertType = status @@ -55,11 +55,11 @@ func newDatadogPayload(falcopayload types.FalcoPayload) datadogPayload { func (c *Client) DatadogPost(falcopayload types.FalcoPayload) { err := c.Post(newDatadogPayload(falcopayload)) if err != nil { - c.Stats.Datadog.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "datadog", "status": "error"}).Inc() + c.Stats.Datadog.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "datadog", "status": Error}).Inc() } else { - c.Stats.Datadog.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "datadog", "status": "ok"}).Inc() + c.Stats.Datadog.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "datadog", "status": OK}).Inc() } - c.Stats.Datadog.Add("total", 1) + c.Stats.Datadog.Add(Total, 1) } diff --git a/outputs/datadog_test.go b/outputs/datadog_test.go index 7af24310f..ee5f167d2 100644 --- a/outputs/datadog_test.go +++ b/outputs/datadog_test.go @@ -17,10 +17,9 @@ func TestNewDatadogPayload(t *testing.T) { var o1, o2 datadogPayload json.Unmarshal([]byte(expectedOutput), &o1) - json.Unmarshal([]byte(s), &o2) + json.Unmarshal(s, &o2) if !reflect.DeepEqual(o1, o2) { - // t.Fatalf("\nexpected payload: \n%v\ngot: \n%v\n", o1, o2) t.Fatalf("\nexpected payload: \n%v\ngot: \n%v\n", expectedOutput, string(s)) } } diff --git a/outputs/discord.go b/outputs/discord.go index 1d946bf7a..d9a7096ea 100644 --- a/outputs/discord.go +++ b/outputs/discord.go @@ -31,26 +31,26 @@ func newDiscordPayload(falcopayload types.FalcoPayload, config *types.Configurat if config.Discord.Icon != "" { iconURL = config.Discord.Icon } else { - iconURL = "https://raw.githubusercontent.com/falcosecurity/falcosidekick/master/imgs/falcosidekick.png" + iconURL = DefaultIconURL } var color string switch strings.ToLower(falcopayload.Priority) { - case "emergency": + case Emergency: color = "15158332" // red - case "alert": + case Alert: color = "11027200" // dark orange - case "critical": + case Critical: color = "15105570" // orange - case "error": + case Error: color = "15844367" // gold - case "warning": + case Warning: color = "12745742" // dark gold - case "notice": + case Notice: color = "3066993" // teal - case "informational": + case Informational: color = "3447003" // blue - case "debug": + case Debug: color = "12370112" // light grey } @@ -70,15 +70,15 @@ func newDiscordPayload(falcopayload types.FalcoPayload, config *types.Configurat } embedFields = append(embedFields, embedField) } - embedField.Name = "rule" + embedField.Name = Rule embedField.Value = falcopayload.Rule embedField.Inline = true embedFields = append(embedFields, embedField) - embedField.Name = "priority" + embedField.Name = Priority embedField.Value = falcopayload.Priority embedField.Inline = true embedFields = append(embedFields, embedField) - embedField.Name = "time" + embedField.Name = Time embedField.Value = falcopayload.Time.String() embedField.Inline = true embedFields = append(embedFields, embedField) @@ -103,11 +103,11 @@ func newDiscordPayload(falcopayload types.FalcoPayload, config *types.Configurat func (c *Client) DiscordPost(falcopayload types.FalcoPayload) { err := c.Post(newDiscordPayload(falcopayload, c.Config)) if err != nil { - c.Stats.Discord.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "discord", "status": "error"}).Inc() + c.Stats.Discord.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "discord", "status": Error}).Inc() } else { - c.Stats.Discord.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "azureeventhub", "status": "ok"}).Inc() + c.Stats.Discord.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "azureeventhub", "status": OK}).Inc() } - c.Stats.Discord.Add("total", 1) + c.Stats.Discord.Add(Total, 1) } diff --git a/outputs/elasticsearch.go b/outputs/elasticsearch.go index 2dfc50f3c..7b14d1860 100644 --- a/outputs/elasticsearch.go +++ b/outputs/elasticsearch.go @@ -22,18 +22,21 @@ func (c *Client) ElasticsearchPost(falcopayload types.FalcoPayload) { default: eURL = c.Config.Elasticsearch.HostPort + "/" + c.Config.Elasticsearch.Index + "-" + current.Format("2006.01.02") + "/" + c.Config.Elasticsearch.Type } + endpointURL, err := url.Parse(eURL) if err != nil { log.Printf("[ERROR] : %v - %v\n", c.OutputType, err.Error()) } + c.EndpointURL = endpointURL err = c.Post(falcopayload) if err != nil { - c.Stats.Elasticsearch.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "elasticsearch", "status": "error"}).Inc() + c.Stats.Elasticsearch.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "elasticsearch", "status": Error}).Inc() } else { - c.Stats.Elasticsearch.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "elasticsearch", "status": "ok"}).Inc() + c.Stats.Elasticsearch.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "elasticsearch", "status": OK}).Inc() } - c.Stats.Elasticsearch.Add("total", 1) + + c.Stats.Elasticsearch.Add(Total, 1) } diff --git a/outputs/gcp.go b/outputs/gcp.go index 088d60b3f..ceb86ee86 100644 --- a/outputs/gcp.go +++ b/outputs/gcp.go @@ -1,16 +1,17 @@ package outputs import ( - "cloud.google.com/go/pubsub" "context" "encoding/base64" "encoding/json" "errors" + "log" + + "cloud.google.com/go/pubsub" "github.com/DataDog/datadog-go/statsd" "github.com/falcosecurity/falcosidekick/types" "golang.org/x/oauth2/google" "google.golang.org/api/option" - "log" ) // NewGCPClient returns a new output.Client for accessing the GCP API. @@ -20,6 +21,7 @@ func NewGCPClient(config *types.Configuration, stats *types.Statistics, promStat log.Printf("[ERROR] : GCP - %v\n", "Error while base64-decoding GCP Credentials") return nil, errors.New("Error while base64-decoding GCP Credentials") } + googleCredentialsData := string(base64decodedCredentialsData) var topicClient *pubsub.Topic @@ -36,6 +38,7 @@ func NewGCPClient(config *types.Configuration, stats *types.Statistics, promStat } topicClient = pubSubClient.Topic(config.GCP.PubSub.Topic) } + return &Client{ OutputType: "GCP", Config: config, @@ -49,23 +52,26 @@ func NewGCPClient(config *types.Configuration, stats *types.Statistics, promStat // GCPPublishTopic sends a message to a GCP PubSub Topic func (c *Client) GCPPublishTopic(falcopayload types.FalcoPayload) { - c.Stats.GCPPubSub.Add("total", 1) + c.Stats.GCPPubSub.Add(Total, 1) payload, _ := json.Marshal(falcopayload) message := &pubsub.Message{ Data: payload, } + result := c.GCPTopicClient.Publish(context.Background(), message) id, err := result.Get(context.Background()) if err != nil { log.Printf("[ERROR] : GCPPubSub - %v - %v\n", "Error while publishing message", err.Error()) - c.Stats.GCPPubSub.Add("error", 1) + c.Stats.GCPPubSub.Add(Error, 1) go c.CountMetric("outputs", 1, []string{"output:gcppubsub", "status:error"}) - c.PromStats.Outputs.With(map[string]string{"destination": "gcppubsub", "status": "error"}).Inc() + c.PromStats.Outputs.With(map[string]string{"destination": "gcppubsub", "status": Error}).Inc() + return } + log.Printf("[INFO] : GCPPubSub - Send to topic OK (%v)\n", id) - c.Stats.GCPPubSub.Add("ok", 1) + c.Stats.GCPPubSub.Add(OK, 1) go c.CountMetric("outputs", 1, []string{"output:gcppubsub", "status:ok"}) - c.PromStats.Outputs.With(map[string]string{"destination": "gcppubsub", "status": "ok"}).Inc() + c.PromStats.Outputs.With(map[string]string{"destination": "gcppubsub", "status": OK}).Inc() } diff --git a/outputs/influxdb.go b/outputs/influxdb.go index 733889a3b..25f1f78cf 100644 --- a/outputs/influxdb.go +++ b/outputs/influxdb.go @@ -1,41 +1,40 @@ -package outputs - -import ( - "strings" - - "github.com/falcosecurity/falcosidekick/types" -) - -type influxdbPayload string - -func newInfluxdbPayload(falcopayload types.FalcoPayload, config *types.Configuration) influxdbPayload { - var s string - - s = "events,rule=" + strings.Replace(falcopayload.Rule, " ", "_", -1) + ",priority=" + strings.Replace(falcopayload.Priority, " ", "_", -1) - - for i, j := range falcopayload.OutputFields { - switch j.(type) { - case string: - s += "," + i + "=" + strings.Replace(j.(string), " ", "_", -1) - default: - continue - } - } - - s += " value=\"" + falcopayload.Output + "\"" - - return influxdbPayload(s) -} - -// InfluxdbPost posts event to InfluxDB -func (c *Client) InfluxdbPost(falcopayload types.FalcoPayload) { - err := c.Post(newInfluxdbPayload(falcopayload, c.Config)) - if err != nil { - c.Stats.Influxdb.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "influxdb", "status": "error"}).Inc() - } else { - c.Stats.Influxdb.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "influxdb", "status": "ok"}).Inc() - } - c.Stats.Influxdb.Add("total", 1) -} +package outputs + +import ( + "strings" + + "github.com/falcosecurity/falcosidekick/types" +) + +type influxdbPayload string + +func newInfluxdbPayload(falcopayload types.FalcoPayload, config *types.Configuration) influxdbPayload { + s := "events,rule=" + strings.Replace(falcopayload.Rule, " ", "_", -1) + ",priority=" + strings.Replace(falcopayload.Priority, " ", "_", -1) + + for i, j := range falcopayload.OutputFields { + switch j.(type) { + case string: + s += "," + i + "=" + strings.Replace(j.(string), " ", "_", -1) + default: + continue + } + } + + s += " value=\"" + falcopayload.Output + "\"" + + return influxdbPayload(s) +} + +// InfluxdbPost posts event to InfluxDB +func (c *Client) InfluxdbPost(falcopayload types.FalcoPayload) { + err := c.Post(newInfluxdbPayload(falcopayload, c.Config)) + if err != nil { + c.Stats.Influxdb.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "influxdb", "status": Error}).Inc() + } else { + c.Stats.Influxdb.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "influxdb", "status": OK}).Inc() + } + + c.Stats.Influxdb.Add(Total, 1) +} diff --git a/outputs/influxdb_test.go b/outputs/influxdb_test.go index 2f515acad..da0e5b68e 100644 --- a/outputs/influxdb_test.go +++ b/outputs/influxdb_test.go @@ -1,21 +1,20 @@ -package outputs - -import ( - "encoding/json" - "testing" - - "github.com/falcosecurity/falcosidekick/types" -) - -func TestNewInfluxdbPayload(t *testing.T) { - expectedOutput := `"events,rule=Test_rule,priority=Debug,proc.name=falcosidekick value=\"This is a test from falcosidekick\""` - - var f types.FalcoPayload - json.Unmarshal([]byte(falcoTestInput), &f) - influxdbPayload, _ := json.Marshal(newInfluxdbPayload(f, &types.Configuration{})) - - if string(influxdbPayload) != expectedOutput { - // t.Fatalf("\nexpected payload: \n%v\ngot: \n%v\n", o1, o2) - t.Fatalf("\nexpected payload: \n%v\ngot: \n%v\n", expectedOutput, string(influxdbPayload)) - } -} +package outputs + +import ( + "encoding/json" + "testing" + + "github.com/falcosecurity/falcosidekick/types" +) + +func TestNewInfluxdbPayload(t *testing.T) { + expectedOutput := `"events,rule=Test_rule,priority=Debug,proc.name=falcosidekick value=\"This is a test from falcosidekick\""` + + var f types.FalcoPayload + json.Unmarshal([]byte(falcoTestInput), &f) + influxdbPayload, _ := json.Marshal(newInfluxdbPayload(f, &types.Configuration{})) + + if string(influxdbPayload) != expectedOutput { + t.Fatalf("\nexpected payload: \n%v\ngot: \n%v\n", expectedOutput, string(influxdbPayload)) + } +} diff --git a/outputs/loki.go b/outputs/loki.go index 90b2ce574..735096568 100644 --- a/outputs/loki.go +++ b/outputs/loki.go @@ -22,7 +22,6 @@ type lokiEntry struct { } func newLokiPayload(falcopayload types.FalcoPayload, config *types.Configuration) lokiPayload { - le := lokiEntry{Ts: falcopayload.Time.Format(time.RFC3339), Line: falcopayload.Output} ls := lokiStream{Entries: []lokiEntry{le}} @@ -35,6 +34,7 @@ func newLokiPayload(falcopayload types.FalcoPayload, config *types.Configuration continue } } + s += "rule=\"" + falcopayload.Rule + "\"," s += "priority=\"" + falcopayload.Priority + "\"," @@ -47,11 +47,12 @@ func newLokiPayload(falcopayload types.FalcoPayload, config *types.Configuration func (c *Client) LokiPost(falcopayload types.FalcoPayload) { err := c.Post(newLokiPayload(falcopayload, c.Config)) if err != nil { - c.Stats.Loki.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "loki", "status": "error"}).Inc() + c.Stats.Loki.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "loki", "status": Error}).Inc() } else { - c.Stats.Loki.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "loki", "status": "ok"}).Inc() + c.Stats.Loki.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "loki", "status": OK}).Inc() } - c.Stats.Loki.Add("total", 1) + + c.Stats.Loki.Add(Total, 1) } diff --git a/outputs/loki_test.go b/outputs/loki_test.go index 79c9676bf..7e970fe92 100644 --- a/outputs/loki_test.go +++ b/outputs/loki_test.go @@ -1,32 +1,33 @@ -package outputs - -import ( - "encoding/json" - "reflect" - "testing" - - "github.com/falcosecurity/falcosidekick/types" -) - -func TestNewLokiPayload(t *testing.T) { - expectedOutput := lokiPayload{ - Streams: []lokiStream{ - lokiStream{ - Labels: "{procname=\"falcosidekick\",rule=\"Test rule\",priority=\"Debug\"}", - Entries: []lokiEntry{ - lokiEntry{ - Ts: "2001-01-01T01:10:00Z", - Line: "This is a test from falcosidekick", - }, - }, - }, - }, - } - - var f types.FalcoPayload - json.Unmarshal([]byte(falcoTestInput), &f) - output := newLokiPayload(f, &types.Configuration{}) - if !reflect.DeepEqual(output, expectedOutput) { - t.Fatalf("\nexpected payload: \n%#v\ngot: \n%#v\n", expectedOutput, output) - } -} +package outputs + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/falcosecurity/falcosidekick/types" +) + +func TestNewLokiPayload(t *testing.T) { + expectedOutput := lokiPayload{ + Streams: []lokiStream{ + { + Labels: "{procname=\"falcosidekick\",rule=\"Test rule\",priority=\"Debug\"}", + Entries: []lokiEntry{ + { + Ts: "2001-01-01T01:10:00Z", + Line: "This is a test from falcosidekick", + }, + }, + }, + }, + } + + var f types.FalcoPayload + json.Unmarshal([]byte(falcoTestInput), &f) + output := newLokiPayload(f, &types.Configuration{}) + + if !reflect.DeepEqual(output, expectedOutput) { + t.Fatalf("\nexpected payload: \n%#v\ngot: \n%#v\n", expectedOutput, output) + } +} diff --git a/outputs/mattermost.go b/outputs/mattermost.go index efebcf76d..26ab85fa0 100644 --- a/outputs/mattermost.go +++ b/outputs/mattermost.go @@ -15,7 +15,7 @@ func newMattermostPayload(falcopayload types.FalcoPayload, config *types.Configu var fields []slackAttachmentField var field slackAttachmentField - if config.Mattermost.OutputFormat == "all" || config.Mattermost.OutputFormat == "fields" || config.Mattermost.OutputFormat == "" { + if config.Mattermost.OutputFormat == All || config.Mattermost.OutputFormat == Fields || config.Mattermost.OutputFormat == "" { for i, j := range falcopayload.OutputFields { switch j.(type) { case string: @@ -32,20 +32,20 @@ func newMattermostPayload(falcopayload types.FalcoPayload, config *types.Configu fields = append(fields, field) } - field.Title = "rule" + field.Title = Rule field.Value = falcopayload.Rule field.Short = true fields = append(fields, field) - field.Title = "priority" + field.Title = Priority field.Value = falcopayload.Priority field.Short = true fields = append(fields, field) - field.Title = "time" + field.Title = Time field.Short = false field.Value = falcopayload.Time.String() fields = append(fields, field) - attachment.Footer = "https://github.com/falcosecurity/falcosidekick" + attachment.Footer = DefaultFooter if config.Mattermost.Footer != "" { attachment.Footer = config.Mattermost.Footer } @@ -53,7 +53,7 @@ func newMattermostPayload(falcopayload types.FalcoPayload, config *types.Configu attachment.Fallback = falcopayload.Output attachment.Fields = fields - if config.Mattermost.OutputFormat == "all" || config.Mattermost.OutputFormat == "fields" || config.Mattermost.OutputFormat == "" { + if config.Mattermost.OutputFormat == All || config.Mattermost.OutputFormat == Fields || config.Mattermost.OutputFormat == "" { attachment.Text = falcopayload.Output } @@ -69,27 +69,27 @@ func newMattermostPayload(falcopayload types.FalcoPayload, config *types.Configu var color string switch strings.ToLower(falcopayload.Priority) { case "emergency": - color = "#e20b0b" - case "alert": - color = "#ff5400" - case "critical": - color = "#ff9000" - case "error": - color = "#ffc700" - case "warning": - color = "#ffff00" - case "notice": - color = "#5bffb5" - case "informational": - color = "#68c2ff" - case "debug": - color = "#ccfff2" + color = Red + case Alert: + color = Orange + case Critical: + color = Orange + case Error: + color = Red + case Warning: + color = Yellow + case Notice: + color = Lightcyan + case Informational: + color = LigthBlue + case Debug: + color = PaleCyan } attachment.Color = color attachments = append(attachments, attachment) - iconURL := "https://raw.githubusercontent.com/falcosecurity/falcosidekick/master/imgs/falcosidekick.png" + iconURL := DefaultIconURL if config.Mattermost.Icon != "" { iconURL = config.Mattermost.Icon } @@ -108,11 +108,12 @@ func newMattermostPayload(falcopayload types.FalcoPayload, config *types.Configu func (c *Client) MattermostPost(falcopayload types.FalcoPayload) { err := c.Post(newMattermostPayload(falcopayload, c.Config)) if err != nil { - c.Stats.Mattermost.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "mattermost", "status": "error"}).Inc() + c.Stats.Mattermost.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "mattermost", "status": Error}).Inc() } else { - c.Stats.Mattermost.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "mattermost", "status": "ok"}).Inc() + c.Stats.Mattermost.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "mattermost", "status": OK}).Inc() } - c.Stats.Mattermost.Add("total", 1) + + c.Stats.Mattermost.Add(Total, 1) } diff --git a/outputs/nats.go b/outputs/nats.go index 54fc72150..90a2abef8 100644 --- a/outputs/nats.go +++ b/outputs/nats.go @@ -1,44 +1,46 @@ -package outputs - -import ( - "encoding/json" - "log" - "regexp" - "strings" - - "github.com/falcosecurity/falcosidekick/types" - nats "github.com/nats-io/nats.go" -) - -var slugRegularExpression = regexp.MustCompile("[^a-z0-9]+") - -// NatsPublish publishes event to NATS -func (c *Client) NatsPublish(falcopayload types.FalcoPayload) { - nc, err := nats.Connect(c.EndpointURL.String()) - if err != nil { - go c.CountMetric("outputs", 1, []string{"output:nats", "status:error"}) - c.Stats.Nats.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "nats", "status": "error"}).Inc() - c.Stats.Nats.Add("total", 1) - log.Printf("[ERROR] : NATS - %v\n", err) - return - } - - r := strings.Trim(slugRegularExpression.ReplaceAllString(strings.ToLower(falcopayload.Rule), "_"), "_") - j, _ := json.Marshal(falcopayload) - err = nc.Publish("falco."+strings.ToLower(falcopayload.Priority)+"."+r, j) - nc.Flush() - nc.Close() - if err != nil { - go c.CountMetric("outputs", 1, []string{"output:nats", "status:error"}) - c.Stats.Nats.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "nats", "status": "error"}).Inc() - log.Printf("[ERROR] : NATS - %v\n", err) - } else { - go c.CountMetric("outputs", 1, []string{"output:nats", "status:ok"}) - c.Stats.Nats.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "nats", "status": "ok"}).Inc() - log.Printf("[INFO] : NATS - Publish OK\n") - } - c.Stats.Nats.Add("total", 1) -} +package outputs + +import ( + "encoding/json" + "log" + "regexp" + "strings" + + "github.com/falcosecurity/falcosidekick/types" + nats "github.com/nats-io/nats.go" +) + +var slugRegularExpression = regexp.MustCompile("[^a-z0-9]+") + +// NatsPublish publishes event to NATS +func (c *Client) NatsPublish(falcopayload types.FalcoPayload) { + nc, err := nats.Connect(c.EndpointURL.String()) + if err != nil { + go c.CountMetric("outputs", 1, []string{"output:nats", "status:error"}) + c.Stats.Nats.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "nats", "status": Error}).Inc() + c.Stats.Nats.Add("total", 1) + log.Printf("[ERROR] : NATS - %v\n", err) + return + } + + r := strings.Trim(slugRegularExpression.ReplaceAllString(strings.ToLower(falcopayload.Rule), "_"), "_") + j, _ := json.Marshal(falcopayload) + + err = nc.Publish("falco."+strings.ToLower(falcopayload.Priority)+"."+r, j) + if err != nil { + go c.CountMetric("outputs", 1, []string{"output:nats", "status:error"}) + c.Stats.Nats.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "nats", "status": Error}).Inc() + log.Printf("[ERROR] : NATS - %v\n", err) + } else { + go c.CountMetric("outputs", 1, []string{"output:nats", "status:ok"}) + c.Stats.Nats.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "nats", "status": OK}).Inc() + log.Printf("[INFO] : NATS - Publish OK\n") + } + defer nc.Flush() + defer nc.Close() + + c.Stats.Nats.Add(Total, 1) +} diff --git a/outputs/opsgenie.go b/outputs/opsgenie.go index abbf607ad..66e4c8c52 100644 --- a/outputs/opsgenie.go +++ b/outputs/opsgenie.go @@ -27,13 +27,13 @@ func newOpsgeniePayload(falcopayload types.FalcoPayload, config *types.Configura var prio string switch strings.ToLower(falcopayload.Priority) { - case "emergency", "alert": + case "emergency", Alert: prio = "P1" - case "critical": + case Critical: prio = "P2" - case "error": + case Error: prio = "P3" - case "warning": + case Warning: prio = "P4" default: prio = "P5" @@ -52,11 +52,12 @@ func newOpsgeniePayload(falcopayload types.FalcoPayload, config *types.Configura func (c *Client) OpsgeniePost(falcopayload types.FalcoPayload) { err := c.Post(newOpsgeniePayload(falcopayload, c.Config)) if err != nil { - c.Stats.Opsgenie.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "opsgenie", "status": "error"}).Inc() + c.Stats.Opsgenie.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "opsgenie", "status": Error}).Inc() } else { c.Stats.Opsgenie.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "opsgenie", "status": "ok"}).Inc() + c.PromStats.Outputs.With(map[string]string{"destination": "opsgenie", "status": OK}).Inc() } - c.Stats.Opsgenie.Add("total", 1) + + c.Stats.Opsgenie.Add(Total, 1) } diff --git a/outputs/opsgenie_test.go b/outputs/opsgenie_test.go index f9b48f0bb..297693d9f 100644 --- a/outputs/opsgenie_test.go +++ b/outputs/opsgenie_test.go @@ -1,28 +1,29 @@ -package outputs - -import ( - "encoding/json" - "reflect" - "testing" - - "github.com/falcosecurity/falcosidekick/types" -) - -func TestNewOpsgeniePayload(t *testing.T) { - expectedOutput := opsgeniePayload{ - Message: "This is a test from falcosidekick", - Entity: "Falcosidekick", - Description: "Test rule", - Details: map[string]string{ - "proc.name": "falcosidekick", - }, - Priority: "P5", - } - - var f types.FalcoPayload - json.Unmarshal([]byte(falcoTestInput), &f) - output := newOpsgeniePayload(f, &types.Configuration{}) - if !reflect.DeepEqual(output, expectedOutput) { - t.Fatalf("\nexpected payload: \n%#v\ngot: \n%#v\n", expectedOutput, output) - } -} +package outputs + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/falcosecurity/falcosidekick/types" +) + +func TestNewOpsgeniePayload(t *testing.T) { + expectedOutput := opsgeniePayload{ + Message: "This is a test from falcosidekick", + Entity: "Falcosidekick", + Description: "Test rule", + Details: map[string]string{ + "proc.name": "falcosidekick", + }, + Priority: "P5", + } + + var f types.FalcoPayload + json.Unmarshal([]byte(falcoTestInput), &f) + output := newOpsgeniePayload(f, &types.Configuration{}) + + if !reflect.DeepEqual(output, expectedOutput) { + t.Fatalf("\nexpected payload: \n%#v\ngot: \n%#v\n", expectedOutput, output) + } +} diff --git a/outputs/rocketchat.go b/outputs/rocketchat.go index 3a88e06d3..b69845bcf 100644 --- a/outputs/rocketchat.go +++ b/outputs/rocketchat.go @@ -15,7 +15,7 @@ func newRocketchatPayload(falcopayload types.FalcoPayload, config *types.Configu var fields []slackAttachmentField var field slackAttachmentField - if config.Rocketchat.OutputFormat == "all" || config.Rocketchat.OutputFormat == "fields" || config.Rocketchat.OutputFormat == "" { + if config.Rocketchat.OutputFormat == All || config.Rocketchat.OutputFormat == Fields || config.Rocketchat.OutputFormat == "" { for i, j := range falcopayload.OutputFields { switch j.(type) { case string: @@ -32,15 +32,15 @@ func newRocketchatPayload(falcopayload types.FalcoPayload, config *types.Configu fields = append(fields, field) } - field.Title = "rule" + field.Title = Rule field.Value = falcopayload.Rule field.Short = true fields = append(fields, field) - field.Title = "priority" + field.Title = Priority field.Value = falcopayload.Priority field.Short = true fields = append(fields, field) - field.Title = "time" + field.Title = Time field.Short = false field.Value = falcopayload.Time.String() fields = append(fields, field) @@ -48,7 +48,7 @@ func newRocketchatPayload(falcopayload types.FalcoPayload, config *types.Configu attachment.Fallback = falcopayload.Output attachment.Fields = fields - if config.Rocketchat.OutputFormat == "all" || config.Rocketchat.OutputFormat == "fields" || config.Rocketchat.OutputFormat == "" { + if config.Rocketchat.OutputFormat == All || config.Rocketchat.OutputFormat == Fields || config.Rocketchat.OutputFormat == "" { attachment.Text = falcopayload.Output } @@ -61,32 +61,32 @@ func newRocketchatPayload(falcopayload types.FalcoPayload, config *types.Configu } } - if config.Rocketchat.OutputFormat == "all" || config.Rocketchat.OutputFormat == "fields" || config.Rocketchat.OutputFormat == "" { + if config.Rocketchat.OutputFormat == All || config.Rocketchat.OutputFormat == Fields || config.Rocketchat.OutputFormat == "" { var color string switch strings.ToLower(falcopayload.Priority) { - case "emergency": - color = "#e20b0b" - case "alert": - color = "#ff5400" - case "critical": - color = "#ff9000" - case "error": - color = "#ffc700" - case "warning": - color = "#ffff00" - case "notice": - color = "#5bffb5" - case "informational": - color = "#68c2ff" - case "debug": - color = "#ccfff2" + case Emergency: + color = Red + case Alert: + color = Orange + case Critical: + color = Orange + case Error: + color = Red + case Warning: + color = Yellow + case Notice: + color = Lightcyan + case Informational: + color = LigthBlue + case Debug: + color = PaleCyan } attachment.Color = color attachments = append(attachments, attachment) } - iconURL := "https://raw.githubusercontent.com/falcosecurity/falcosidekick/master/imgs/falcosidekick.png" + iconURL := DefaultIconURL if config.Rocketchat.Icon != "" { iconURL = config.Rocketchat.Icon } @@ -104,11 +104,12 @@ func newRocketchatPayload(falcopayload types.FalcoPayload, config *types.Configu func (c *Client) RocketchatPost(falcopayload types.FalcoPayload) { err := c.Post(newRocketchatPayload(falcopayload, c.Config)) if err != nil { - c.Stats.Rocketchat.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "rocketchat", "status": "error"}).Inc() + c.Stats.Rocketchat.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "rocketchat", "status": Error}).Inc() } else { - c.Stats.Rocketchat.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "rocketchat", "status": "ok"}).Inc() + c.Stats.Rocketchat.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "rocketchat", "status": OK}).Inc() } - c.Stats.Rocketchat.Add("total", 1) + + c.Stats.Rocketchat.Add(Total, 1) } diff --git a/outputs/slack.go b/outputs/slack.go index 9e6c1632f..676151faa 100644 --- a/outputs/slack.go +++ b/outputs/slack.go @@ -40,7 +40,7 @@ func newSlackPayload(falcopayload types.FalcoPayload, config *types.Configuratio var fields []slackAttachmentField var field slackAttachmentField - if config.Slack.OutputFormat == "all" || config.Slack.OutputFormat == "fields" || config.Slack.OutputFormat == "" { + if config.Slack.OutputFormat == All || config.Slack.OutputFormat == Fields || config.Slack.OutputFormat == "" { for i, j := range falcopayload.OutputFields { switch j.(type) { case string: @@ -57,20 +57,20 @@ func newSlackPayload(falcopayload types.FalcoPayload, config *types.Configuratio fields = append(fields, field) } - field.Title = "rule" + field.Title = Rule field.Value = falcopayload.Rule field.Short = true fields = append(fields, field) - field.Title = "priority" + field.Title = Priority field.Value = falcopayload.Priority field.Short = true fields = append(fields, field) - field.Title = "time" + field.Title = Time field.Short = false field.Value = falcopayload.Time.String() fields = append(fields, field) - attachment.Footer = "https://github.com/falcosecurity/falcosidekick" + attachment.Footer = DefaultFooter if config.Slack.Footer != "" { attachment.Footer = config.Slack.Footer } @@ -78,7 +78,7 @@ func newSlackPayload(falcopayload types.FalcoPayload, config *types.Configuratio attachment.Fallback = falcopayload.Output attachment.Fields = fields - if config.Slack.OutputFormat == "all" || config.Slack.OutputFormat == "fields" || config.Slack.OutputFormat == "" { + if config.Slack.OutputFormat == All || config.Slack.OutputFormat == Fields || config.Slack.OutputFormat == "" { attachment.Text = falcopayload.Output } @@ -93,28 +93,28 @@ func newSlackPayload(falcopayload types.FalcoPayload, config *types.Configuratio var color string switch strings.ToLower(falcopayload.Priority) { - case "emergency": - color = "#e20b0b" - case "alert": - color = "#ff5400" - case "critical": - color = "#ff9000" - case "error": - color = "#ffc700" - case "warning": - color = "#ffff00" - case "notice": - color = "#5bffb5" - case "informational": - color = "#68c2ff" - case "debug": - color = "#ccfff2" + case Emergency: + color = Red + case Alert: + color = Orange + case Critical: + color = Orange + case Error: + color = Red + case Warning: + color = Yellow + case Notice: + color = Lightcyan + case Informational: + color = LigthBlue + case Debug: + color = PaleCyan } attachment.Color = color attachments = append(attachments, attachment) - // iconURL := "https://raw.githubusercontent.com/falcosecurity/falcosidekick/master/imgs/falcosidekick.png" + // iconURL := DefaultIconURL // if config.Slack.Icon != "" { // iconURL = config.Slack.Icon // } @@ -132,11 +132,12 @@ func newSlackPayload(falcopayload types.FalcoPayload, config *types.Configuratio func (c *Client) SlackPost(falcopayload types.FalcoPayload) { err := c.Post(newSlackPayload(falcopayload, c.Config)) if err != nil { - 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() } else { - 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() } - c.Stats.Slack.Add("total", 1) + + c.Stats.Slack.Add(Total, 1) } diff --git a/outputs/slack_test.go b/outputs/slack_test.go index 00e4882f7..2af605de1 100644 --- a/outputs/slack_test.go +++ b/outputs/slack_test.go @@ -13,11 +13,11 @@ func TestNewSlackPayload(t *testing.T) { expectedOutput := slackPayload{ Text: "Rule: Test rule Priority: Debug", Username: "Falcosidekick", - IconURL: "https://raw.githubusercontent.com/falcosecurity/falcosidekick/master/imgs/falcosidekick.png", + IconURL: DefaultIconURL, Attachments: []slackAttachment{ { Fallback: "This is a test from falcosidekick", - Color: "#ccfff2", + Color: PaleCyan, Text: "This is a test from falcosidekick", Footer: "https://github.com/falcosecurity/falcosidekick", Fields: []slackAttachmentField{ @@ -51,12 +51,13 @@ func TestNewSlackPayload(t *testing.T) { config := &types.Configuration{ Slack: types.SlackOutputConfig{ Username: "Falcosidekick", - Icon: "https://raw.githubusercontent.com/falcosecurity/falcosidekick/master/imgs/falcosidekick.png", + Icon: DefaultIconURL, }, } config.Slack.MessageFormatTemplate, _ = template.New("").Parse("Rule: {{ .Rule }} Priority: {{ .Priority }}") output := newSlackPayload(f, config) + if !reflect.DeepEqual(output, expectedOutput) { t.Fatalf("\nexpected payload: \n%#v\ngot: \n%#v\n", expectedOutput, output) } diff --git a/outputs/smtp.go b/outputs/smtp.go index c6b5b74af..ee742ee23 100644 --- a/outputs/smtp.go +++ b/outputs/smtp.go @@ -1,110 +1,110 @@ -package outputs - -import ( - "bytes" - htmlTemplate "html/template" - "log" - "regexp" - "strings" - textTemplate "text/template" - - sasl "github.com/emersion/go-sasl" - smtp "github.com/emersion/go-smtp" - - "github.com/DataDog/datadog-go/statsd" - "github.com/falcosecurity/falcosidekick/types" -) - -// SMTPPayload is payload for SMTP Output -type SMTPPayload struct { - To string - Subject string - Body string -} - -// NewSMTPClient returns a new output.Client for accessing a SMTP server. -func NewSMTPClient(config *types.Configuration, stats *types.Statistics, promStats *types.PromStatistics, statsdClient, dogstatsdClient *statsd.Client) (*Client, error) { - reg := regexp.MustCompile(`.*:[0-9]+`) - if !reg.MatchString(config.SMTP.HostPort) { - log.Printf("[ERROR] : SMTP - Bad Host:Port\n") - return nil, ErrClientCreation - } - - return &Client{ - OutputType: "SMTP", - Config: config, - Stats: stats, - PromStats: promStats, - StatsdClient: statsdClient, - DogstatsdClient: dogstatsdClient, - }, nil -} - -func newSMTPPayload(falcopayload types.FalcoPayload, config *types.Configuration) SMTPPayload { - s := SMTPPayload{ - To: "To: " + config.SMTP.To, - Subject: "Subject: [" + falcopayload.Priority + "] " + falcopayload.Output, - } - - s.Body = "MIME-version: 1.0;\n" - - if config.SMTP.OutputFormat != "text" { - s.Body += "Content-Type: multipart/alternative; boundary=4t74weu9byeSdJTM\n\n\n--4t74weu9byeSdJTM\n" - } - - s.Body += "Content-Type: text/plain; charset=\"UTF-8\";\n\n" - - ttmpl := textTemplate.New("text") - ttmpl, _ = ttmpl.Parse(plaintextTmpl) - var outtext bytes.Buffer - err := ttmpl.Execute(&outtext, falcopayload) - if err != nil { - log.Printf("[ERROR] : SMTP - %v\n", err) - return s - } - s.Body += outtext.String() - - if config.SMTP.OutputFormat == "text" { - return s - } - - s.Body += "--4t74weu9byeSdJTM\nContent-Type: text/html; charset=\"UTF-8\";\n\n" - - htmpl := htmlTemplate.New("html") - htmpl, _ = htmpl.Parse(htmlTmpl) - var outhtml bytes.Buffer - err = htmpl.Execute(&outhtml, falcopayload) - if err != nil { - log.Printf("[ERROR] : SMTP - %v\n", err) - return s - } - s.Body += outhtml.String() - - return s -} - -// SendMail sends email to SMTP server -func (c *Client) SendMail(falcopayload types.FalcoPayload) { - sp := newSMTPPayload(falcopayload, c.Config) - - to := strings.Split(strings.Replace(c.Config.SMTP.To, " ", "", -1), ",") - auth := sasl.NewPlainClient("", c.Config.SMTP.User, c.Config.SMTP.Password) - body := sp.To + "\n" + sp.Subject + "\n" + sp.Body - - if c.Config.Debug == true { - log.Printf("[DEBUG] : SMTP payload : \nServer: %v\nFrom: %v\nTo: %v\nSubject: %v\n", c.Config.SMTP.HostPort, c.Config.SMTP.From, sp.To, sp.Subject) - } - - c.Stats.SMTP.Add("total", 1) - err := smtp.SendMail(c.Config.SMTP.HostPort, auth, c.Config.SMTP.From, to, strings.NewReader(body)) - if err != nil { - go c.CountMetric("outputs", 1, []string{"output:smtp", "status:error"}) - c.Stats.SMTP.Add("error", 1) - log.Printf("[ERROR] : SMTP - %v\n", err) - return - } - - log.Printf("[INFO] : SMTP - Sent OK\n") - go c.CountMetric("outputs", 1, []string{"output:smtp", "status:ok"}) - c.Stats.SMTP.Add("ok", 1) -} +package outputs + +import ( + "bytes" + htmlTemplate "html/template" + "log" + "regexp" + "strings" + textTemplate "text/template" + + sasl "github.com/emersion/go-sasl" + smtp "github.com/emersion/go-smtp" + + "github.com/DataDog/datadog-go/statsd" + "github.com/falcosecurity/falcosidekick/types" +) + +// SMTPPayload is payload for SMTP Output +type SMTPPayload struct { + To string + Subject string + Body string +} + +// NewSMTPClient returns a new output.Client for accessing a SMTP server. +func NewSMTPClient(config *types.Configuration, stats *types.Statistics, promStats *types.PromStatistics, statsdClient, dogstatsdClient *statsd.Client) (*Client, error) { + reg := regexp.MustCompile(`.*:[0-9]+`) + if !reg.MatchString(config.SMTP.HostPort) { + log.Printf("[ERROR] : SMTP - Bad Host:Port\n") + return nil, ErrClientCreation + } + + return &Client{ + OutputType: "SMTP", + Config: config, + Stats: stats, + PromStats: promStats, + StatsdClient: statsdClient, + DogstatsdClient: dogstatsdClient, + }, nil +} + +func newSMTPPayload(falcopayload types.FalcoPayload, config *types.Configuration) SMTPPayload { + s := SMTPPayload{ + To: "To: " + config.SMTP.To, + Subject: "Subject: [" + falcopayload.Priority + "] " + falcopayload.Output, + } + + s.Body = "MIME-version: 1.0;\n" + + if config.SMTP.OutputFormat != Text { + s.Body += "Content-Type: multipart/alternative; boundary=4t74weu9byeSdJTM\n\n\n--4t74weu9byeSdJTM\n" + } + + s.Body += "Content-Type: text/plain; charset=\"UTF-8\";\n\n" + + ttmpl := textTemplate.New(Text) + ttmpl, _ = ttmpl.Parse(plaintextTmpl) + var outtext bytes.Buffer + err := ttmpl.Execute(&outtext, falcopayload) + if err != nil { + log.Printf("[ERROR] : SMTP - %v\n", err) + return s + } + s.Body += outtext.String() + + if config.SMTP.OutputFormat == Text { + return s + } + + s.Body += "--4t74weu9byeSdJTM\nContent-Type: text/html; charset=\"UTF-8\";\n\n" + + htmpl := htmlTemplate.New("html") + htmpl, _ = htmpl.Parse(htmlTmpl) + var outhtml bytes.Buffer + err = htmpl.Execute(&outhtml, falcopayload) + if err != nil { + log.Printf("[ERROR] : SMTP - %v\n", err) + return s + } + s.Body += outhtml.String() + + return s +} + +// SendMail sends email to SMTP server +func (c *Client) SendMail(falcopayload types.FalcoPayload) { + sp := newSMTPPayload(falcopayload, c.Config) + + to := strings.Split(strings.Replace(c.Config.SMTP.To, " ", "", -1), ",") + auth := sasl.NewPlainClient("", c.Config.SMTP.User, c.Config.SMTP.Password) + body := sp.To + "\n" + sp.Subject + "\n" + sp.Body + + if c.Config.Debug == true { + log.Printf("[DEBUG] : SMTP payload : \nServer: %v\nFrom: %v\nTo: %v\nSubject: %v\n", c.Config.SMTP.HostPort, c.Config.SMTP.From, sp.To, sp.Subject) + } + + c.Stats.SMTP.Add("total", 1) + err := smtp.SendMail(c.Config.SMTP.HostPort, auth, c.Config.SMTP.From, to, strings.NewReader(body)) + if err != nil { + go c.CountMetric("outputs", 1, []string{"output:smtp", "status:error"}) + c.Stats.SMTP.Add(Error, 1) + log.Printf("[ERROR] : SMTP - %v\n", err) + return + } + + log.Printf("[INFO] : SMTP - Sent OK\n") + go c.CountMetric("outputs", 1, []string{"output:smtp", "status:ok"}) + c.Stats.SMTP.Add(OK, 1) +} diff --git a/outputs/smtp_templates.go b/outputs/smtp_templates.go index 59b901efd..f77484626 100644 --- a/outputs/smtp_templates.go +++ b/outputs/smtp_templates.go @@ -1,81 +1,81 @@ -package outputs - -var plaintextTmpl = `Priority: {{ .Priority }} -Output: {{ .Output }} -Rule: {{ .Rule }} -Time: {{ .Time }} - ---Fields-- -{{ range $key, $value := .OutputFields }}{{ $key }}: {{ $value }} -{{ end }} - -` - -var htmlTmpl = ` -{{ $color := "#858585"}} -{{ if or (eq .Priority "Emergency") (eq .Priority "emergency") }}{{ $color = "#e20b0b" }}{{ end }} -{{ if or (eq .Priority "Alert") (eq .Priority "Alert") }}{{ $color = "#ff5400" }}{{ end }} -{{ if or (eq .Priority "Critical") (eq .Priority "critical") }}{{ $color = "#ff9000" }}{{ end }} -{{ if or (eq .Priority "Error") (eq .Priority "error") }}{{ $color = "#ffc700" }}{{ end }} -{{ if or (eq .Priority "Warning") (eq .Priority "warning") }}{{ $color = "#ffff00" }}{{ end }} -{{ if or (eq .Priority "Notice") (eq .Priority "notice") }}{{ $color = "#5bffb5" }}{{ end }} -{{ if or (eq .Priority "Informational") (eq .Priority "informational") }}{{ $color = "#68c2ff" }}{{ end }} -{{ if or (eq .Priority "Debug") (eq .Priority "debug") }}{{ $color = "#ccfff2" }}{{ end }} - - - - - - - - - - - -
-
- - - - - - -
{{ .Priority }}
- - - - - - - - - - - - - - - -
Output{{ .Output }}
Rule{{ .Rule }}
Time{{ .Time }}
-
- - - - - - - -
Fields
- - - {{ range $key, $value := .OutputFields }} - - - - - {{ end }} -
{{ $key }}{{ $value }}
- ---4t74weu9byeSdJTM--` +package outputs + +var plaintextTmpl = `Priority: {{ .Priority }} +Output: {{ .Output }} +Rule: {{ .Rule }} +Time: {{ .Time }} + +--Fields-- +{{ range $key, $value := .OutputFields }}{{ $key }}: {{ $value }} +{{ end }} + +` + +var htmlTmpl = ` +{{ $color := "#858585"}} +{{ if or (eq .Priority "Emergency") (eq .Priority "emergency") }}{{ $color = "#e20b0b" }}{{ end }} +{{ if or (eq .Priority ALERT) (eq .Priority ALERT) }}{{ $color = "#ff5400" }}{{ end }} +{{ if or (eq .Priority CRITICAL) (eq .Priority CRITICAL) }}{{ $color = "#ff9000" }}{{ end }} +{{ if or (eq .Priority ERROR) (eq .Priority ERROR) }}{{ $color = "#ffc700" }}{{ end }} +{{ if or (eq .Priority WARNING) (eq .Priority WARNING) }}{{ $color = "#ffff00" }}{{ end }} +{{ if or (eq .Priority NOTICE) (eq .Priority NOTICE) }}{{ $color = "#5bffb5" }}{{ end }} +{{ if or (eq .Priority INFORMATIONAL) (eq .Priority INFORMATIONAL) }}{{ $color = "#68c2ff" }}{{ end }} +{{ if or (eq .Priority DEBUG) (eq .Priority DEBUG) }}{{ $color = "#ccfff2" }}{{ end }} + + + + + + + + + + + +
+
+ + + + + + +
{{ .Priority }}
+ + + + + + + + + + + + + + + +
Output{{ .Output }}
Rule{{ .Rule }}
Time{{ .Time }}
+
+ + + + + + + +
Fields
+ + + {{ range $key, $value := .OutputFields }} + + + + + {{ end }} +
{{ $key }}{{ $value }}
+ +--4t74weu9byeSdJTM--` diff --git a/outputs/statsd.go b/outputs/statsd.go index 42bed42cb..25fc02aeb 100644 --- a/outputs/statsd.go +++ b/outputs/statsd.go @@ -1,55 +1,61 @@ -package outputs - -import ( - "github.com/DataDog/datadog-go/statsd" - "github.com/falcosecurity/falcosidekick/types" - - "log" - "strings" -) - -// NewStatsdClient returns a new output.Client for sending metrics to StatsD. -func NewStatsdClient(outputType string, config *types.Configuration, stats *types.Statistics) (*statsd.Client, error) { - statsdClient, err := statsd.New(config.Statsd.Forwarder, statsd.WithNamespace(config.Statsd.Namespace), statsd.WithTags(config.Statsd.Tags)) - if err != nil { - log.Printf("[ERROR] : Can't configure %v client for %v - %v", outputType, config.Statsd.Forwarder, err) - return nil, err - } - - return statsdClient, nil -} - -// CountMetric sends metrics to StatsD/DogStatsD. -func (c *Client) CountMetric(metric string, value int64, tags []string) { - if c.StatsdClient != nil { - c.Stats.Statsd.Add("total", 1) - t := "" - if len(tags) != 0 { - for _, i := range tags { - s := strings.Split(i, ":") - t += "." + strings.Replace(s[1], " ", "", -1) - } - } - if err := c.StatsdClient.Count(metric+t, value, []string{}, 1); err != nil { - c.Stats.Statsd.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "statsd", "status": "error"}).Inc() - log.Printf("[ERROR] : StatsD - Unable to send metric (%v%v%v) : %v\n", c.Config.Statsd.Namespace, metric, t, err) - return - } - c.Stats.Statsd.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "statsd", "status": "ok"}).Inc() - log.Printf("[INFO] : StatsD - Send Metric OK (%v%v%v)\n", c.Config.Statsd.Namespace, metric, t) - } - if c.DogstatsdClient != nil { - c.Stats.Dogstatsd.Add("total", 1) - if err := c.DogstatsdClient.Count(metric, value, tags, 1); err != nil { - c.Stats.Dogstatsd.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "dogstatsd", "status": "error"}).Inc() - log.Printf("[ERROR] : DogStatsD - Send Metric Error (%v%v%v) : %v\n", c.Config.Statsd.Namespace, metric, tags, err) - return - } - c.Stats.Dogstatsd.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "dogstatsd", "status": "ok"}).Inc() - log.Printf("[INFO] : DogStatsD - Send Metric OK (%v%v %v)\n", c.Config.Statsd.Namespace, metric, tags) - } -} +package outputs + +import ( + "github.com/DataDog/datadog-go/statsd" + "github.com/falcosecurity/falcosidekick/types" + + "log" + "strings" +) + +// NewStatsdClient returns a new output.Client for sending metrics to StatsD. +func NewStatsdClient(outputType string, config *types.Configuration, stats *types.Statistics) (*statsd.Client, error) { + statsdClient, err := statsd.New(config.Statsd.Forwarder, statsd.WithNamespace(config.Statsd.Namespace), statsd.WithTags(config.Statsd.Tags)) + if err != nil { + log.Printf("[ERROR] : Can't configure %v client for %v - %v", outputType, config.Statsd.Forwarder, err) + return nil, err + } + + return statsdClient, nil +} + +// CountMetric sends metrics to StatsD/DogStatsD. +func (c *Client) CountMetric(metric string, value int64, tags []string) { + if c.StatsdClient != nil { + c.Stats.Statsd.Add("total", 1) + t := "" + if len(tags) != 0 { + for _, i := range tags { + s := strings.Split(i, ":") + t += "." + strings.Replace(s[1], " ", "", -1) + } + } + + if err := c.StatsdClient.Count(metric+t, value, []string{}, 1); err != nil { + c.Stats.Statsd.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "statsd", "status": Error}).Inc() + log.Printf("[ERROR] : StatsD - Unable to send metric (%v%v%v) : %v\n", c.Config.Statsd.Namespace, metric, t, err) + + return + } + + c.Stats.Statsd.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "statsd", "status": OK}).Inc() + log.Printf("[INFO] : StatsD - Send Metric OK (%v%v%v)\n", c.Config.Statsd.Namespace, metric, t) + } + + if c.DogstatsdClient != nil { + c.Stats.Dogstatsd.Add("total", 1) + if err := c.DogstatsdClient.Count(metric, value, tags, 1); err != nil { + c.Stats.Dogstatsd.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "dogstatsd", "status": Error}).Inc() + log.Printf("[ERROR] : DogStatsD - Send Metric Error (%v%v%v) : %v\n", c.Config.Statsd.Namespace, metric, tags, err) + + return + } + + c.Stats.Dogstatsd.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "dogstatsd", "status": OK}).Inc() + log.Printf("[INFO] : DogStatsD - Send Metric OK (%v%v %v)\n", c.Config.Statsd.Namespace, metric, tags) + } +} diff --git a/outputs/teams.go b/outputs/teams.go index e00b86274..32c80ed2f 100644 --- a/outputs/teams.go +++ b/outputs/teams.go @@ -1,112 +1,113 @@ -package outputs - -import ( - "strings" - - "github.com/falcosecurity/falcosidekick/types" -) - -type teamsFact struct { - Name string `json:"name"` - Value string `json:"value"` -} - -type teamsSection struct { - ActivityTitle string `json:"activityTitle"` - ActivitySubTitle string `json:"activitySubtitle"` - ActivityImage string `json:"activityImage,omitempty"` - Text string `json:"text"` - Facts []teamsFact `json:"facts,omitempty"` -} - -// Payload -type teamsPayload struct { - Type string `json:"@type"` - Summary string `json:"summary,omitempty"` - ThemeColor string `json:"themeColor,omitempty"` - Sections []teamsSection `json:"sections"` -} - -func newTeamsPayload(falcopayload types.FalcoPayload, config *types.Configuration) teamsPayload { - var sections []teamsSection - var section teamsSection - var facts []teamsFact - var 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 - } - - if config.Teams.ActivityImage != "" { - section.ActivityImage = config.Teams.ActivityImage - } - - if config.Teams.OutputFormat == "all" || config.Teams.OutputFormat == "facts" || config.Teams.OutputFormat == "" { - for i, j := range falcopayload.OutputFields { - switch j.(type) { - case string: - fact.Name = i - fact.Value = j.(string) - default: - continue - } - facts = append(facts, fact) - } - - fact.Name = "rule" - fact.Value = falcopayload.Rule - facts = append(facts, fact) - fact.Name = "priority" - fact.Value = falcopayload.Priority - facts = append(facts, fact) - } - - section.Facts = facts - - var color string - switch strings.ToLower(falcopayload.Priority) { - case "emergency": - color = "e20b0b" - case "alert": - color = "ff5400" - case "critical": - color = "ff9000" - case "error": - color = "ffc700" - case "warning": - color = "ffff00" - case "notice": - color = "5bffb5" - case "informational": - color = "68c2ff" - case "debug": - color = "ccfff2" - } - - sections = append(sections, section) - - t := teamsPayload{ - Type: "MessageCard", - Summary: falcopayload.Output, - ThemeColor: color, - Sections: sections, - } - - return t -} - -// TeamsPost posts event to Teams -func (c *Client) TeamsPost(falcopayload types.FalcoPayload) { - err := c.Post(newTeamsPayload(falcopayload, c.Config)) - if err != nil { - c.Stats.Teams.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "teams", "status": "error"}).Inc() - } else { - c.Stats.Teams.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "teams", "status": "ok"}).Inc() - } - c.Stats.Teams.Add("total", 1) -} +package outputs + +import ( + "strings" + + "github.com/falcosecurity/falcosidekick/types" +) + +type teamsFact struct { + Name string `json:"name"` + Value string `json:"value"` +} + +type teamsSection struct { + ActivityTitle string `json:"activityTitle"` + ActivitySubTitle string `json:"activitySubtitle"` + ActivityImage string `json:"activityImage,omitempty"` + Text string `json:"text"` + Facts []teamsFact `json:"facts,omitempty"` +} + +// Payload +type teamsPayload struct { + Type string `json:"@type"` + Summary string `json:"summary,omitempty"` + ThemeColor string `json:"themeColor,omitempty"` + Sections []teamsSection `json:"sections"` +} + +func newTeamsPayload(falcopayload types.FalcoPayload, config *types.Configuration) teamsPayload { + var sections []teamsSection + var section teamsSection + var facts []teamsFact + var 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 + } + + if config.Teams.ActivityImage != "" { + section.ActivityImage = config.Teams.ActivityImage + } + + if config.Teams.OutputFormat == All || config.Teams.OutputFormat == "facts" || config.Teams.OutputFormat == "" { + for i, j := range falcopayload.OutputFields { + switch j.(type) { + case string: + fact.Name = i + fact.Value = j.(string) + default: + continue + } + facts = append(facts, fact) + } + + fact.Name = Rule + fact.Value = falcopayload.Rule + facts = append(facts, fact) + fact.Name = Priority + fact.Value = falcopayload.Priority + facts = append(facts, fact) + } + + section.Facts = facts + + var color string + switch strings.ToLower(falcopayload.Priority) { + case Emergency: + color = "e20b0b" + case Alert: + color = "ff5400" + case Critical: + color = "ff9000" + case Error: + color = "ffc700" + case Warning: + color = "ffff00" + case Notice: + color = "5bffb5" + case Informational: + color = "68c2ff" + case Debug: + color = "ccfff2" + } + + sections = append(sections, section) + + t := teamsPayload{ + Type: "MessageCard", + Summary: falcopayload.Output, + ThemeColor: color, + Sections: sections, + } + + return t +} + +// TeamsPost posts event to Teams +func (c *Client) TeamsPost(falcopayload types.FalcoPayload) { + err := c.Post(newTeamsPayload(falcopayload, c.Config)) + if err != nil { + c.Stats.Teams.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "teams", "status": Error}).Inc() + } else { + c.Stats.Teams.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "teams", "status": OK}).Inc() + } + + c.Stats.Teams.Add(Total, 1) +} diff --git a/outputs/teams_test.go b/outputs/teams_test.go index 840b44761..6840e8d63 100644 --- a/outputs/teams_test.go +++ b/outputs/teams_test.go @@ -1,46 +1,47 @@ -package outputs - -import ( - "encoding/json" - "reflect" - "testing" - - "github.com/falcosecurity/falcosidekick/types" -) - -func TestNewTeamsPayload(t *testing.T) { - expectedOutput := teamsPayload{ - Type: "MessageCard", - Summary: "This is a test from falcosidekick", - ThemeColor: "ccfff2", - Sections: []teamsSection{ - teamsSection{ - ActivityTitle: "Falco Sidekick", - ActivitySubTitle: "2001-01-01 01:10:00 +0000 UTC", - ActivityImage: "", - Text: "This is a test from falcosidekick", - Facts: []teamsFact{ - teamsFact{ - Name: "proc.name", - Value: "falcosidekick", - }, - teamsFact{ - Name: "rule", - Value: "Test rule", - }, - teamsFact{ - Name: "priority", - Value: "Debug", - }, - }, - }, - }, - } - - var f types.FalcoPayload - json.Unmarshal([]byte(falcoTestInput), &f) - output := newTeamsPayload(f, &types.Configuration{}) - if !reflect.DeepEqual(output, expectedOutput) { - t.Fatalf("\nexpected payload: \n%#v\ngot: \n%#v\n", expectedOutput, output) - } -} +package outputs + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/falcosecurity/falcosidekick/types" +) + +func TestNewTeamsPayload(t *testing.T) { + expectedOutput := teamsPayload{ + Type: "MessageCard", + Summary: "This is a test from falcosidekick", + ThemeColor: "ccfff2", + Sections: []teamsSection{ + { + ActivityTitle: "Falco Sidekick", + ActivitySubTitle: "2001-01-01 01:10:00 +0000 UTC", + ActivityImage: "", + Text: "This is a test from falcosidekick", + Facts: []teamsFact{ + { + Name: "proc.name", + Value: "falcosidekick", + }, + { + Name: "rule", + Value: "Test rule", + }, + { + Name: "priority", + Value: "Debug", + }, + }, + }, + }, + } + + var f types.FalcoPayload + json.Unmarshal([]byte(falcoTestInput), &f) + output := newTeamsPayload(f, &types.Configuration{}) + + if !reflect.DeepEqual(output, expectedOutput) { + t.Fatalf("\nexpected payload: \n%#v\ngot: \n%#v\n", expectedOutput, output) + } +} diff --git a/outputs/webhook.go b/outputs/webhook.go index 59dc2cc87..af09a72bd 100644 --- a/outputs/webhook.go +++ b/outputs/webhook.go @@ -8,11 +8,12 @@ import ( func (c *Client) WebhookPost(falcopayload types.FalcoPayload) { err := c.Post(falcopayload) if err != nil { - c.Stats.Webhook.Add("error", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "webhook", "status": "error"}).Inc() + c.Stats.Webhook.Add(Error, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "webhook", "status": Error}).Inc() } else { - c.Stats.Webhook.Add("ok", 1) - c.PromStats.Outputs.With(map[string]string{"destination": "webhook", "status": "ok"}).Inc() + c.Stats.Webhook.Add(OK, 1) + c.PromStats.Outputs.With(map[string]string{"destination": "webhook", "status": OK}).Inc() } - c.Stats.Webhook.Add("total", 1) + + c.Stats.Webhook.Add(Total, 1) } diff --git a/types/types.go b/types/types.go index 4a5c8bd72..78b9cd5d6 100644 --- a/types/types.go +++ b/types/types.go @@ -19,8 +19,10 @@ type FalcoPayload struct { // Configuration is a struct to store configuration type Configuration struct { - ListenPort int + CheckCert bool Debug bool + ListenPort int + Customfields map[string]string Slack SlackOutputConfig Mattermost MattermostOutputConfig Rocketchat rocketchatOutputConfig @@ -40,8 +42,6 @@ type Configuration struct { Webhook WebhookOutputConfig Azure azureConfig GCP gcpOutputConfig - Customfields map[string]string - CheckCert bool } // SlackOutputConfig represents parameters for Slack From 9283d8c510727837b9b79708472603456052cffe Mon Sep 17 00:00:00 2001 From: Carlos Panato Date: Sun, 15 Nov 2020 18:14:58 +0100 Subject: [PATCH 3/3] Add check lint to circleci and remove the tag filter Signed-off-by: Carlos Panato --- .circleci/config.yml | 19 +++++++++++++------ outputs/smtp_templates.go | 14 +++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 636e5bff3..c3373fdc6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,16 +2,23 @@ version: 2.1 workflows: main: jobs: - - test: - filters: - tags: - only: /v[0-9]+(\.[0-9]+)*(-.*)*/ + - test + - lint + jobs: test: docker: - - image: docker.io/golang:1.14.0 + - image: docker.io/golang:1.14.12 steps: - checkout - run: name: Test - command: make test \ No newline at end of file + command: make test + lint: + docker: + - image: docker.io/golang:1.14.12 + steps: + - checkout + - run: + name: Lint + command: make lint diff --git a/outputs/smtp_templates.go b/outputs/smtp_templates.go index f77484626..03f964bf7 100644 --- a/outputs/smtp_templates.go +++ b/outputs/smtp_templates.go @@ -14,13 +14,13 @@ Time: {{ .Time }} var htmlTmpl = ` {{ $color := "#858585"}} {{ if or (eq .Priority "Emergency") (eq .Priority "emergency") }}{{ $color = "#e20b0b" }}{{ end }} -{{ if or (eq .Priority ALERT) (eq .Priority ALERT) }}{{ $color = "#ff5400" }}{{ end }} -{{ if or (eq .Priority CRITICAL) (eq .Priority CRITICAL) }}{{ $color = "#ff9000" }}{{ end }} -{{ if or (eq .Priority ERROR) (eq .Priority ERROR) }}{{ $color = "#ffc700" }}{{ end }} -{{ if or (eq .Priority WARNING) (eq .Priority WARNING) }}{{ $color = "#ffff00" }}{{ end }} -{{ if or (eq .Priority NOTICE) (eq .Priority NOTICE) }}{{ $color = "#5bffb5" }}{{ end }} -{{ if or (eq .Priority INFORMATIONAL) (eq .Priority INFORMATIONAL) }}{{ $color = "#68c2ff" }}{{ end }} -{{ if or (eq .Priority DEBUG) (eq .Priority DEBUG) }}{{ $color = "#ccfff2" }}{{ end }} +{{ if or (eq .Priority "Alert") (eq .Priority "Alert") }}{{ $color = "#ff5400" }}{{ end }} +{{ if or (eq .Priority "Critical") (eq .Priority "critical") }}{{ $color = "#ff9000" }}{{ end }} +{{ if or (eq .Priority "Error") (eq .Priority "error") }}{{ $color = "#ffc700" }}{{ end }} +{{ if or (eq .Priority "Warning") (eq .Priority "warning") }}{{ $color = "#ffff00" }}{{ end }} +{{ if or (eq .Priority "Notice") (eq .Priority "notice") }}{{ $color = "#5bffb5" }}{{ end }} +{{ if or (eq .Priority "Informational") (eq .Priority "informational") }}{{ $color = "#68c2ff" }}{{ end }} +{{ if or (eq .Priority "Debug") (eq .Priority "debug") }}{{ $color = "#ccfff2" }}{{ end }}