Skip to content

Commit

Permalink
Prepare handlers for new inputs (pipe, gRPC)
Browse files Browse the repository at this point in the history
Fix issue #49 with incorrect StatsD events (tags are not allowed by classic protocol, only by DogStatsD)
Add new output DogStatsD (issue #49)
Add nex expvar metrics for number of running goroutines, number of used CPU, StatsD/DogStatsD Outputs
Standardization of metric names (be consistent between expar and statsd)
Add actgardner as helm chart maintainer
  • Loading branch information
Issif committed Nov 10, 2019
1 parent bb1281d commit afc7a0c
Show file tree
Hide file tree
Showing 25 changed files with 258 additions and 145 deletions.
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Currently available outputs are :
* **SMTP** (email)
* [**Opsgenie**](https://www.opsgenie.com/)
* [**StatsD**](https://github.com/statsd/statsd) (for monitoring of `falcosidekick`)
* [**DogStatsD**](https://docs.datadoghq.com/developers/dogstatsd/?tab=go) (for monitoring of `falcosidekick`)
* [**Webhook**]

## Usage
Expand Down Expand Up @@ -155,9 +156,14 @@ opsgenie:
# minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default)
statsd:
# forwarder: "" # The address for the StatsD forwarder, in the form "host:port", if not empty StatsD is enabled
# namespace: "falcosidekick" # A prefix for all metrics
# tags: "" # A comma-separated list of tags to add to all metrics
forwarder: "" # The address for the StatsD forwarder, in the form "host:port", if not empty StatsD is enabled
namespace: "falcosidekick." # A prefix for all metrics (default: "falcosidekick.")
dogstatsd:
forwarder: "" # The address for the DogStatsD forwarder, in the form "host:port", if not empty DogStatsD is enabled
namespace: "falcosidekick." # A prefix for all metrics (default: "falcosidekick.")
# tag :
# key: "value"
webhook:
# address: "" # Webhook address, if not empty, Webhook output is enabled
Expand Down Expand Up @@ -229,8 +235,10 @@ The *env vars* "match" field names in *yaml file with this structure (**take car
* **OPSGENIE_REGION** : "" # (us|eu) region of your domain (default is 'us')
* **OPSGENIE_MINIMUMPRIORITY** : minimum priority of event for using this output, order is `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)`
* **STATSD_FORWARDER**: The address for the StatsD forwarder, in the form http://host:port, if not empty StatsD is enabled
* **STATSD_NAMESPACE**: A prefix for all metrics
* **STATSD_TAGS**: A comma-separated list of tags to add to all metrics
* **STATSD_NAMESPACE**: A prefix for all metrics (default: "falcosidekick.")
* **DOGSTATSD_FORWARDER**: The address for the DogStatsD forwarder, in the form http://host:port, if not empty DogStatsD is enabled
* **DOGSTATSD_NAMESPACE**: A prefix for all metrics (default: falcosidekick."")
* **DOGSTATSD_TAGS**: A comma-separated list of tags to add to all metrics
* **WEBHOOK_ADDRESS** : "" # Webhook address, if not empty, Webhook output is enabled
* **WEBHOOK_MINIMUMPRIORITY** : minimum priority of event for using this output, order is `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)`
Expand Down
4 changes: 3 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ func getConfig() *types.Configuration {
v.SetDefault("Opsgenie.MinimumPriority", "")
v.SetDefault("Statsd.Forwarder", "")
v.SetDefault("Statsd.Namespace", "falcosidekick.")
v.SetDefault("Statsd.Tags", []string{})
v.SetDefault("Dogstatsd.Forwarder", "")
v.SetDefault("Dogstatsd.Namespace", "falcosidekick.")
v.SetDefault("Dogstatsd.Tags", []string{})
v.SetDefault("Customfields", map[string]string{})
v.SetDefault("Webhook.Address", "")
v.SetDefault("Webhook.MinimumPriority", "")
Expand Down
16 changes: 13 additions & 3 deletions config_example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,19 @@ opsgenie:
# minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default)

statsd:
# forwarder: "" # The address for the StatsD forwarder, in the form "host:port", if not empty StatsD is enabled
# namespace: "falcosidekick." # A prefix for all metrics
# tags: "" # A comma-separated list of tags to add to all metrics
forwarder: "" # The address for the StatsD forwarder, in the form "host:port", if not empty StatsD is enabled
namespace: "falcosidekick." # A prefix for all metrics (default: "falcosidekick.")

dogstatsd:
forwarder: "" # The address for the DogStatsD forwarder, in the form "host:port", if not empty DogStatsD is enabled
namespace: "falcosidekick." # A prefix for all metrics (default: "falcosidekick.")
# tag :
# key: "value"

opsgenie:
# apikey: "2c771471-e2af-4dc6-bd35-e7f6ff479b64" # Opsgenie API Key, if not empty, Opsgenie output is enabled
region: "eu" # (us|eu) region of your domain
# minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default)

webhook:
# address: "" # Webhook address, if not empty, Webhook output is enabled
Expand Down
5 changes: 3 additions & 2 deletions deploy/helm/falcosidekick/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
apiVersion: v1
appVersion: "2.11.0"
appVersion: "2.12.0"
description: A simple daemon to help you with falco's outputs
icon: https://raw.githubusercontent.com/falcosecurity/falcosidekick/master/imgs/falcosidekick.png
name: falcosidekick
version: 0.1.10
version: 0.1.11
maintainers:
- name: SweetOps
- name: Issif
- name: actgardner
10 changes: 8 additions & 2 deletions deploy/helm/falcosidekick/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,14 @@ spec:
value: {{ .Values.config.statsd.forwarder | quote }}
- name: STATSD_NAMESPACE
value: {{ .Values.config.statsd.namespace | quote }}
- name: STATSD_TAGS
value: {{ .Values.config.statsd.tags | quote }}
{{- end }}
{{- if .Values.config.statsd.forwarder }}
- name: DOGSTATSD_FORWARDER
value: {{ .Values.config.dogstatsd.forwarder | quote }}
- name: DOGSTATSD_NAMESPACE
value: {{ .Values.config.dogstatsd.namespace | quote }}
- name: DOGSTATSD_TAGS
value: {{ .Values.config.dogstatsd.tags | quote }}
{{- end }}
{{- if .Values.config.webhook.address }}
- name: WEBHOOK_ADDRESS
Expand Down
4 changes: 4 additions & 0 deletions deploy/helm/falcosidekick/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ config:
statsd:
forwarder: ""
namespace: "falcosidekick."

dogstatsd:
forwarder: ""
namespace: "falcosidekick."
tags: ""

webhook:
Expand Down
86 changes: 44 additions & 42 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bytes"
"encoding/json"
"io"
"log"
"net/http"
"strconv"
Expand All @@ -28,27 +29,56 @@ func getPriorityMap() map[string]int {

// mainHandler is Falco Sidekick main handler (default).
func mainHandler(w http.ResponseWriter, r *http.Request) {

var falcopayload types.FalcoPayload

stats.Requests.Add("total", 1)
countMetric("total", 1, []string{})
nullClient.CountMetric("total", 1, []string{})

if r.Body == nil {
http.Error(w, "Please send a valid request body", 400)
stats.Requests.Add("rejected", 1)
countMetric("rejected", 1, []string{"error:nobody"})
nullClient.CountMetric("rejected", 1, []string{"error:nobody"})
return
}

err := json.NewDecoder(r.Body).Decode(&falcopayload)
falcopayload, err := newFalcoPayload(r.Body)
if err != nil && err.Error() != "EOF" || len(falcopayload.Output) == 0 {
http.Error(w, "Please send a valid request body : "+err.Error(), 400)
stats.Requests.Add("rejected", 1)
countMetric("rejected", 1, []string{"error:invalidjson"})
nullClient.CountMetric("rejected", 1, []string{"error:invalidjson"})
return
}

forwardEvent(falcopayload)
}

// pingHandler is a simple handler to test if daemon is UP.
func pingHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong\n"))
}

// testHandler sends a test event to all enabled outputs.
func testHandler(w http.ResponseWriter, r *http.Request) {
testEvent := `{"output":"This is a test from falcosidekick","priority":"Debug","rule":"Test rule", "time":"` + time.Now().UTC().Format(time.RFC3339) + `","outputfields": {"proc.name":"falcosidekick","user.name":"falcosidekick"}}`

resp, err := http.Post("http://localhost:"+strconv.Itoa(config.ListenPort), "application/json", bytes.NewBuffer([]byte(testEvent)))
if err != nil {
log.Printf("[DEBUG] : Test Failed. Falcosidekick can't call itself\n")
}
defer resp.Body.Close()

log.Printf("[DEBUG] : Test sent\n")
if resp.StatusCode != http.StatusOK {
log.Printf("[DEBUG] : Test KO (%v)\n", resp.Status)
}
}

func newFalcoPayload(payload io.Reader) (types.FalcoPayload, error) {
var falcopayload types.FalcoPayload

err := json.NewDecoder(payload).Decode(&falcopayload)
if err != nil {
return types.FalcoPayload{}, err
}

// falcopayload.OutputFields = make(map[string]interface{})
if len(config.Customfields) > 0 {
if falcopayload.OutputFields == nil {
Expand All @@ -59,15 +89,15 @@ func mainHandler(w http.ResponseWriter, r *http.Request) {
}
}

nullClient.CountMetric("accepted", 1, []string{})
stats.Requests.Add("accepted", 1)
priority := strings.ToLower(falcopayload.Priority)
countMetric("accepted", 1, []string{"priority:" + priority})
switch priority {
case "emergency", "alert", "critical", "error", "warning", "notice", "informational", "debug":
countMetric("accepted", 1, []string{"priority:" + priority})
stats.Falco.Add(strings.ToLower(falcopayload.Priority), 1)
nullClient.CountMetric("accepted", 1, []string{"priority:" + priority})
stats.Falco.Add(priority, 1)
default:
countMetric("accepted", 1, []string{"priority:unknownpriority"})
nullClient.CountMetric("accepted", 1, []string{"priority:unknownpriority"})
stats.Falco.Add("unknownpriority", 1)
}

Expand All @@ -76,8 +106,10 @@ func mainHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("[DEBUG] : Falco's payload : %v", string(body))
}

priorityMap := getPriorityMap()
return falcopayload, nil
}

func forwardEvent(falcopayload types.FalcoPayload) {
if config.Slack.WebhookURL != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Slack.MinimumPriority)] || falcopayload.Rule == "Test rule") {
go slackClient.SlackPost(falcopayload)
}
Expand Down Expand Up @@ -118,33 +150,3 @@ func mainHandler(w http.ResponseWriter, r *http.Request) {
go webhookClient.WebhookPost(falcopayload)
}
}

// pingHandler is a simple handler to test if daemon is UP.
func pingHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong\n"))
}

// testHandler sends a test event to all enabled outputs.
func testHandler(w http.ResponseWriter, r *http.Request) {
testEvent := `{"output":"This is a test from falcosidekick","priority":"Debug","rule":"Test rule", "time":"` + time.Now().UTC().Format(time.RFC3339) + `","outputfields": {"proc.name":"falcosidekick","user.name":"falcosidekick"}}`

resp, err := http.Post("http://localhost:"+strconv.Itoa(config.ListenPort), "application/json", bytes.NewBuffer([]byte(testEvent)))
if err != nil {
log.Printf("[DEBUG] : Test Failed. Falcosidekick can't call itself\n")
}
defer resp.Body.Close()

log.Printf("[DEBUG] : Test sent\n")
if resp.StatusCode != http.StatusOK {
log.Printf("[DEBUG] : Test KO (%v)\n", resp.Status)
}
}

func countMetric(metric string, value int64, tags []string) {
if statsdClient == nil {
return
}
if err := statsdClient.Count(metric, value, tags, 1); err != nil {
log.Printf("[ERROR] : Unable to send metric to StatsD - %v", err)
}
}
Loading

0 comments on commit afc7a0c

Please sign in to comment.