crowdsec/cmd/notification-sentinel/main.go

134 lines
3.8 KiB
Go
Raw Permalink Normal View History

package main
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/crowdsecurity/crowdsec/pkg/protobufs"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"gopkg.in/yaml.v3"
)
type PluginConfig struct {
Name string `yaml:"name"`
CustomerID string `yaml:"customer_id"`
SharedKey string `yaml:"shared_key"`
LogType string `yaml:"log_type"`
LogLevel *string `yaml:"log_level"`
}
type SentinelPlugin struct {
PluginConfigByName map[string]PluginConfig
}
var logger hclog.Logger = hclog.New(&hclog.LoggerOptions{
Name: "sentinel-plugin",
Level: hclog.LevelFromString("INFO"),
Output: os.Stderr,
JSONFormat: true,
})
func (s *SentinelPlugin) getAuthorizationHeader(now string, length int, pluginName string) (string, error) {
xHeaders := "x-ms-date:" + now
stringToHash := fmt.Sprintf("POST\n%d\napplication/json\n%s\n/api/logs", length, xHeaders)
decodedKey, _ := base64.StdEncoding.DecodeString(s.PluginConfigByName[pluginName].SharedKey)
h := hmac.New(sha256.New, decodedKey)
h.Write([]byte(stringToHash))
encodedHash := base64.StdEncoding.EncodeToString(h.Sum(nil))
authorization := "SharedKey " + s.PluginConfigByName[pluginName].CustomerID + ":" + encodedHash
logger.Trace("authorization header", "header", authorization)
return authorization, nil
}
func (s *SentinelPlugin) Notify(ctx context.Context, notification *protobufs.Notification) (*protobufs.Empty, error) {
if _, ok := s.PluginConfigByName[notification.Name]; !ok {
return nil, fmt.Errorf("invalid plugin config name %s", notification.Name)
}
cfg := s.PluginConfigByName[notification.Name]
if cfg.LogLevel != nil && *cfg.LogLevel != "" {
logger.SetLevel(hclog.LevelFromString(*cfg.LogLevel))
}
logger.Info("received notification for sentinel config", "name", notification.Name)
url := fmt.Sprintf("https://%s.ods.opinsights.azure.com/api/logs?api-version=2016-04-01", s.PluginConfigByName[notification.Name].CustomerID)
body := strings.NewReader(notification.Text)
//Cannot use time.RFC1123 as azure wants GMT, not UTC
now := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT")
authorization, err := s.getAuthorizationHeader(now, len(notification.Text), notification.Name)
if err != nil {
return &protobufs.Empty{}, err
}
req, err := http.NewRequest(http.MethodPost, url, body)
if err != nil {
logger.Error("failed to create request", "error", err)
return &protobufs.Empty{}, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Log-Type", s.PluginConfigByName[notification.Name].LogType)
req.Header.Set("Authorization", authorization)
req.Header.Set("x-ms-date", now)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
logger.Error("failed to send request", "error", err)
return &protobufs.Empty{}, err
}
defer resp.Body.Close()
logger.Debug("sent notification to sentinel", "status", resp.Status)
if resp.StatusCode != http.StatusOK {
return &protobufs.Empty{}, fmt.Errorf("failed to send notification to sentinel: %s", resp.Status)
}
return &protobufs.Empty{}, nil
}
func (s *SentinelPlugin) Configure(ctx context.Context, config *protobufs.Config) (*protobufs.Empty, error) {
d := PluginConfig{}
err := yaml.Unmarshal(config.Config, &d)
s.PluginConfigByName[d.Name] = d
return &protobufs.Empty{}, err
}
func main() {
var handshake = plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "CROWDSEC_PLUGIN_KEY",
MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"),
}
sp := &SentinelPlugin{PluginConfigByName: make(map[string]PluginConfig)}
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: handshake,
Plugins: map[string]plugin.Plugin{
"sentinel": &protobufs.NotifierPlugin{
Impl: sp,
},
},
GRPCServer: plugin.DefaultGRPCServer,
Logger: logger,
})
}