Skip to content

Commit

Permalink
feat(discord): Add discord output
Browse files Browse the repository at this point in the history
Signed-off-by: Spencer Krum <nibz@spencerkrum.com>
Co-authored-by: Thomas Labarussias <issif+github@gadz.org>
  • Loading branch information
nibalizer and Issif committed Aug 2, 2020
1 parent 16deccb commit 922b267
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ config.yaml
.env
.vscode
.idea
*.swp
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Currently available outputs are :
* [**Mattermost**](https://mattermost.com/)
* [**Teams**](https://products.office.com/en-us/microsoft-teams/group-chat-software)
* [**Datadog**](https://www.datadoghq.com/)
* [**Discord**](https://www.discord.com/)
* [**AlertManager**](https://prometheus.io/docs/alerting/alertmanager/)
* [**Elasticsearch**](https://www.elastic.co/)
* [**Loki**](https://grafana.com/oss/loki)
Expand Down Expand Up @@ -194,6 +195,11 @@ azure:
# namespace: "" # The name of the space the Hub is part of
# minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default)
discord:
webhookurl: "" # discord WebhookURL (ex: https://discord.com/api/webhooks/xxxxxxxxxx...), if not empty, Discord output is enabled
#icon: "" # Discord icon (avatar)
minimumpriority: "debug" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default)
```
Usage :
Expand Down Expand Up @@ -239,6 +245,9 @@ The *env vars* "match" field names in *yaml file with this structure (**take car
* **DATADOG_APIKEY** : Datadog API Key, if not `empty`, Datadog output is *enabled*
* **DATADOG_HOST** : Datadog host. Override if you are on the Datadog EU site. Defaults to american site with "https://api.datadoghq.com"
* **DATADOG_MINIMUMPRIORITY** : minimum priority of event for using this output, order is `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)`
* **DISCORD_WEBHOOKURL** : Discord WebhookURL (ex: https://discord.com/api/webhooks/xxxxxxxxxx...), if not empty, Discord output is enabled
* **DISCORD_ICON** : Discord icon (avatar)
* **DISCORD_MINIMUMPRIORITY** : minimum priority of event for using use this output, order is `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)`
* **ALERTMANAGER_HOSTPORT** : AlertManager http://host:port, if not `empty`, AlertManager is *enabled*
* **ALERTMANAGER_MINIMUMPRIORITY** : minimum priority of event for using this output, order is `emergency|alert|critical|error|warning|notice|informational|debug or "" (default)`
* **ELASTICSEARCH_HOSTPORT** : Elasticsearch http://host:port, if not `empty`, Elasticsearch is *enabled*
Expand Down Expand Up @@ -407,6 +416,10 @@ time akey bkey ckey priority rule value
![opsgenie example](https://github.com/falcosecurity/falcosidekick/raw/master/imgs/opsgenie.png)
### Discord
![discord example](https://github.com/falcosecurity/falcosidekick/raw/master/imgs/discord_example.png)
## Development
### Build
Expand All @@ -415,6 +428,14 @@ time akey bkey ckey priority rule value
go build
```
### Quicktest
Create a debug event
```bash
curl -H "Content-Type: application/json" -H "Accept: application/json" localhost:2801/test
```
### Test & Coverage
```bash
Expand Down
3 changes: 3 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ func getConfig() *types.Configuration {
v.SetDefault("Datadog.APIKey", "")
v.SetDefault("Datadog.Host", "https://api.datadoghq.com")
v.SetDefault("Datadog.MinimumPriority", "")
v.SetDefault("Discord.WebhookURL", "")
v.SetDefault("Discord.MinimumPriority", "")
v.SetDefault("Discord.Icon", "https://raw.githubusercontent.com/falcosecurity/falcosidekick/master/imgs/falcosidekick_color.png")
v.SetDefault("Alertmanager.HostPort", "")
v.SetDefault("Alertmanager.MinimumPriority", "")
v.SetDefault("Elasticsearch.HostPort", "")
Expand Down
5 changes: 5 additions & 0 deletions config_example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,8 @@ azure:
name: "" # Name of the Hub, if not empty, EventHub is enabled
namespace: "" # Name of the space the Hub is in
# minimumpriority: "" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default)
#
discord:
webhookurl: "" # discord WebhookURL (ex: https://discord.com/api/webhooks/xxxxxxxxxx...), if not empty, Discord output is enabled
#icon: "" # Discord icon (avatar)
minimumpriority: "debug" # minimum priority of event for using this output, order is emergency|alert|critical|error|warning|notice|informational|debug or "" (default)
3 changes: 3 additions & 0 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ func forwardEvent(falcopayload types.FalcoPayload) {
if config.Datadog.APIKey != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Datadog.MinimumPriority)] || falcopayload.Rule == "Test rule") {
go datadogClient.DatadogPost(falcopayload)
}
if config.Discord.WebhookURL != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Discord.MinimumPriority)] || falcopayload.Rule == "Test rule") {
go discordClient.DiscordPost(falcopayload)
}
if config.Alertmanager.HostPort != "" && (priorityMap[strings.ToLower(falcopayload.Priority)] >= priorityMap[strings.ToLower(config.Alertmanager.MinimumPriority)] || falcopayload.Rule == "Test rule") {
go alertmanagerClient.AlertmanagerPost(falcopayload)
}
Expand Down
Binary file added imgs/discord.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var (
mattermostClient *outputs.Client
teamsClient *outputs.Client
datadogClient *outputs.Client
discordClient *outputs.Client
alertmanagerClient *outputs.Client
elasticsearchClient *outputs.Client
influxdbClient *outputs.Client
Expand All @@ -30,7 +31,6 @@ var (
webhookClient *outputs.Client
azureClient *outputs.Client
)

var statsdClient, dogstatsdClient *statsd.Client
var config *types.Configuration
var stats *types.Statistics
Expand Down Expand Up @@ -116,6 +116,15 @@ func init() {
enabledOutputsText += "Datadog "
}
}
if config.Discord.WebhookURL != "" {
var err error
discordClient, err = outputs.NewClient("Discord", config.Discord.WebhookURL, config, stats, statsdClient, dogstatsdClient)
if err != nil {
config.Discord.WebhookURL = ""
} else {
enabledOutputsText += "Discord "
}
}
if config.Alertmanager.HostPort != "" {
var err error
alertmanagerClient, err = outputs.NewClient("AlertManager", config.Alertmanager.HostPort+outputs.AlertmanagerURI, config, stats, statsdClient, dogstatsdClient)
Expand Down
110 changes: 110 additions & 0 deletions outputs/discord.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package outputs

import (
"github.com/falcosecurity/falcosidekick/types"
"strings"
)

type discordPayload struct {
Content string `json:"content"`
Avatar_url string `json:"avatar_url,omitempty"`
Embeds []discordEmbedPayload `json:"embeds"`
}

type discordEmbedPayload struct {
Title string `json:"title"`
Url string `json:"url"`
Description string `json:"description"`
Color string `json:"color"`
Fields []discordEmbedFieldPayload `json:"fields"`
}

type discordEmbedFieldPayload struct {
Name string `json:"name"`
Value string `json:"value"`
Inline bool `json:"inline"`
}

func newDiscordPayload(falcopayload types.FalcoPayload, config *types.Configuration) discordPayload {
var iconURL string
if config.Discord.Icon != "" {
iconURL = config.Discord.Icon
} else {
iconURL = "https://raw.githubusercontent.com/falcosecurity/falcosidekick/master/imgs/falcosidekick.png"
}

var color string
switch strings.ToLower(falcopayload.Priority) {
case "emergency":
color = "15158332" // red
case "alert":
color = "11027200" // dark orange
case "critical":
color = "15105570" // orange
case "error":
color = "15844367" // gold
case "warning":
color = "12745742" // dark gold
case "notice":
color = "3066993" // teal
case "informational":
color = "3447003" // blue
case "debug":
color = "12370112" // light grey
}

embeds := make([]discordEmbedPayload, 0)

embedFields := make([]discordEmbedFieldPayload, 0)
var embedField discordEmbedFieldPayload

for i, j := range falcopayload.OutputFields {
switch j.(type) {
case string:
embedField.Name = i
embedField.Inline = true
embedField.Value = "```" + j.(string) + "```"
default:
continue
}
embedFields = append(embedFields, embedField)
}
embedField.Name = "rule"
embedField.Value = falcopayload.Rule
embedField.Inline = true
embedFields = append(embedFields, embedField)
embedField.Name = "priority"
embedField.Value = falcopayload.Priority
embedField.Inline = true
embedFields = append(embedFields, embedField)
embedField.Name = "time"
embedField.Value = falcopayload.Time.String()
embedField.Inline = true
embedFields = append(embedFields, embedField)

embed := discordEmbedPayload{
Title: "",
Description: falcopayload.Output,
Color: color,
Fields: embedFields,
}
embeds = append(embeds, embed)

ds := discordPayload{
Content: "",
Avatar_url: iconURL,
Embeds: embeds,
}
return ds
}

// DiscordPost posts events to discord
func (c *Client) DiscordPost(falcopayload types.FalcoPayload) {
err := c.Post(newDiscordPayload(falcopayload, c.Config))
if err != nil {
c.Stats.Discord.Add("error", 1)
} else {
c.Stats.Discord.Add("ok", 1)
}
c.Stats.Discord.Add("total", 1)
}
4 changes: 4 additions & 0 deletions stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func getInitStats() *types.Statistics {
Mattermost: expvar.NewMap("outputs.mattermost"),
Teams: expvar.NewMap("outputs.teams"),
Datadog: expvar.NewMap("outputs.datadog"),
Discord: expvar.NewMap("outputs.discord"),
Alertmanager: expvar.NewMap("outputs.alertmanager"),
Elasticsearch: expvar.NewMap("outputs.elasticsearch"),
Loki: expvar.NewMap("outputs.loki"),
Expand Down Expand Up @@ -72,6 +73,9 @@ func getInitStats() *types.Statistics {
stats.Datadog.Add("total", 0)
stats.Datadog.Add("error", 0)
stats.Datadog.Add("ok", 0)
stats.Discord.Add("total", 0)
stats.Discord.Add("error", 0)
stats.Discord.Add("ok", 0)
stats.Alertmanager.Add("total", 0)
stats.Alertmanager.Add("error", 0)
stats.Alertmanager.Add("ok", 0)
Expand Down
8 changes: 8 additions & 0 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Configuration struct {
Rocketchat rocketchatOutputConfig
Teams teamsOutputConfig
Datadog datadogOutputConfig
Discord discordOutputConfig
Alertmanager alertmanagerOutputConfig
Elasticsearch elasticsearchOutputConfig
Influxdb influxdbOutputConfig
Expand Down Expand Up @@ -82,6 +83,12 @@ type datadogOutputConfig struct {
MinimumPriority string
}

type discordOutputConfig struct {
WebhookURL string
MinimumPriority string
Icon string
}

type alertmanagerOutputConfig struct {
HostPort string
MinimumPriority string
Expand Down Expand Up @@ -181,6 +188,7 @@ type Statistics struct {
Rocketchat *expvar.Map
Teams *expvar.Map
Datadog *expvar.Map
Discord *expvar.Map
Alertmanager *expvar.Map
Elasticsearch *expvar.Map
Loki *expvar.Map
Expand Down

0 comments on commit 922b267

Please sign in to comment.