|
@@ -0,0 +1,133 @@
|
|
|
|
+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("POST", 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 != 200 {
|
|
|
|
+ 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,
|
|
|
|
+ })
|
|
|
|
+}
|