From 55e045023b795891becbe1d0feb8c785611add13 Mon Sep 17 00:00:00 2001 From: Yacine SAIBI Date: Tue, 16 Jul 2024 16:21:30 +0200 Subject: [PATCH] Add plugin to send notification to alertmanager --- cmd/notification-alertmanager/Makefile | 17 +++++ .../alertmanager.yaml | 25 +++++++ cmd/notification-alertmanager/main.go | 50 +++++++++++++ cmd/notification-alertmanager/plugin.go | 74 +++++++++++++++++++ 4 files changed, 166 insertions(+) create mode 100644 cmd/notification-alertmanager/Makefile create mode 100644 cmd/notification-alertmanager/alertmanager.yaml create mode 100644 cmd/notification-alertmanager/main.go create mode 100644 cmd/notification-alertmanager/plugin.go diff --git a/cmd/notification-alertmanager/Makefile b/cmd/notification-alertmanager/Makefile new file mode 100644 index 00000000000..2c0ca483eb8 --- /dev/null +++ b/cmd/notification-alertmanager/Makefile @@ -0,0 +1,17 @@ +ifeq ($(OS), Windows_NT) + SHELL := pwsh.exe + .SHELLFLAGS := -NoProfile -Command + EXT = .exe +endif + +GO = go +GOBUILD = $(GO) build + +BINARY_NAME = notification-alertmanager$(EXT) + +build: clean + $(GOBUILD) $(LD_OPTS) -o $(BINARY_NAME) + +.PHONY: clean +clean: + @$(RM) $(BINARY_NAME) $(WIN_IGNORE_ERR) diff --git a/cmd/notification-alertmanager/alertmanager.yaml b/cmd/notification-alertmanager/alertmanager.yaml new file mode 100644 index 00000000000..98b550a9e4f --- /dev/null +++ b/cmd/notification-alertmanager/alertmanager.yaml @@ -0,0 +1,25 @@ +type: alertmanager # Don't change +name: alertmanager_default # Must match the registered plugin in the profile + +# One of "trace", "debug", "info", "warn", "error", "off" +log_level: info + +# group_wait: # Time to wait collecting alerts before relaying a message to this plugin, eg "30s" +# group_threshold: # Amount of alerts that triggers a message before has expired, eg "10" +# max_retry: # Number of attempts to relay messages to plugins in case of error +timeout: 20s # Time to wait for response from the plugin before considering the attempt a failure, eg "10s" + +#------------------------- +# plugin-specific options + +format: | + {{.|toJson}} + +loglevel: "info" +host: "alertmanager_host_url" +user: "alertmanager username" +password: "alertmanager password" +basepath: "/api/v1" +schemes: ["https"] +source: "cluster_1" # source of the alert (cluster_name, server_name,...) +team: "infra_team" # the team in charge for this source or scope diff --git a/cmd/notification-alertmanager/main.go b/cmd/notification-alertmanager/main.go new file mode 100644 index 00000000000..5afacfc4ebf --- /dev/null +++ b/cmd/notification-alertmanager/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "os" + + protobufs "github.com/crowdsecurity/crowdsec/pkg/protobufs" + hclog "github.com/hashicorp/go-hclog" + plugin "github.com/hashicorp/go-plugin" +) + +var logger hclog.Logger = hclog.New(&hclog.LoggerOptions{ + Name: "alertmanager-plugin", + Level: hclog.LevelFromString("DEBUG"), + Output: os.Stderr, + JSONFormat: true, +}) + +type PluginConfig struct { + Name string + LogLevel *string + + Host string + BasePath string + Schemes []string + User string + Password string + Source string + Team string +} + +func main() { + var handshake = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "CROWDSEC_PLUGIN_KEY", + MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"), + } + + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: handshake, + Plugins: map[string]plugin.Plugin{ + "alertmanager": &protobufs.NotifierPlugin{ + Impl: &AlertmanagerPlugin{ + ConfigByName: make(map[string]PluginConfig), + }, + }, + }, + GRPCServer: plugin.DefaultGRPCServer, + Logger: logger, + }) +} diff --git a/cmd/notification-alertmanager/plugin.go b/cmd/notification-alertmanager/plugin.go new file mode 100644 index 00000000000..622ded91c69 --- /dev/null +++ b/cmd/notification-alertmanager/plugin.go @@ -0,0 +1,74 @@ +package main + +import ( + "context" + "fmt" + "time" + + protobufs "github.com/crowdsecurity/crowdsec/pkg/protobufs" + runtime "github.com/go-openapi/runtime/client" + strfmt "github.com/go-openapi/strfmt" + hclog "github.com/hashicorp/go-hclog" + alert "github.com/prometheus/alertmanager/api/v2/client/alert" + "github.com/prometheus/alertmanager/api/v2/models" + yaml "gopkg.in/yaml.v2" +) + +type AlertmanagerPlugin struct { + ConfigByName map[string]PluginConfig +} + +func (n *AlertmanagerPlugin) Configure(ctx context.Context, config *protobufs.Config) (*protobufs.Empty, error) { + d := PluginConfig{} + if err := yaml.Unmarshal(config.Config, &d); err != nil { + return nil, err + } + n.ConfigByName[d.Name] = d + return &protobufs.Empty{}, nil +} + +func (n *AlertmanagerPlugin) Notify(ctx context.Context, notification *protobufs.Notification) (*protobufs.Empty, error) { + if _, ok := n.ConfigByName[notification.Name]; !ok { + return nil, fmt.Errorf("invalid plugin config name %s", notification.Name) + } + cfg := n.ConfigByName[notification.Name] + if cfg.LogLevel != nil && *cfg.LogLevel != "" { + logger.SetLevel(hclog.LevelFromString(*cfg.LogLevel)) + } else { + logger.SetLevel(hclog.Info) + } + + format := strfmt.NewFormats() + transport := runtime.New(cfg.Host, cfg.BasePath, cfg.Schemes) + transport.DefaultAuthentication = runtime.BasicAuth(cfg.User, cfg.Password) + client := alert.New(transport, format) + + alertParams := n.createPostAlertParams(ctx, cfg, notification) + _, err := client.PostAlerts(alertParams) + if err != nil { + logger.Error("ErreurPostAlerts:", err.Error()) + return nil, err + } else { + logger.Info(fmt.Sprintf(" %s ", notification.Name)) + } + return &protobufs.Empty{}, nil +} + +func (n *AlertmanagerPlugin) createPostAlertParams(ctx context.Context, cfg PluginConfig, notification *protobufs.Notification) *alert.PostAlertsParams { + alertParams := alert.NewPostAlertsParams() + now := time.Now() + params := &models.PostableAlert{ + StartsAt: strfmt.DateTime(now), + EndsAt: strfmt.DateTime(now.Add(5 * time.Minute)), + Alert: models.Alert{ + Labels: models.LabelSet{ + "alertname": "crowdsec_alert", + "source": cfg.Source, + "team": cfg.Team, + "text": notification.Text, + }, + }, + } + alertParams.Alerts = models.PostableAlerts{params} + return alertParams +}