2023-07-25 13:07:10 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-08-24 07:46:25 +00:00
|
|
|
req, err := http.NewRequest(http.MethodPost, url, body)
|
2023-07-25 13:07:10 +00:00
|
|
|
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)
|
|
|
|
|
2023-08-24 07:46:25 +00:00
|
|
|
if resp.StatusCode != http.StatusOK {
|
2023-07-25 13:07:10 +00:00
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|