123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- package main
- import (
- "bytes"
- "context"
- "crypto/tls"
- "crypto/x509"
- "fmt"
- "io"
- "net"
- "net/http"
- "os"
- "strings"
- "github.com/hashicorp/go-hclog"
- plugin "github.com/hashicorp/go-plugin"
- "gopkg.in/yaml.v3"
- "github.com/crowdsecurity/crowdsec/pkg/protobufs"
- )
- type PluginConfig struct {
- Name string `yaml:"name"`
- URL string `yaml:"url"`
- UnixSocket string `yaml:"unix_socket"`
- Headers map[string]string `yaml:"headers"`
- SkipTLSVerification bool `yaml:"skip_tls_verification"`
- Method string `yaml:"method"`
- LogLevel *string `yaml:"log_level"`
- Client *http.Client `yaml:"-"`
- CertPath string `yaml:"cert_path"`
- KeyPath string `yaml:"key_path"`
- CAPath string `yaml:"ca_cert_path"`
- }
- type HTTPPlugin struct {
- PluginConfigByName map[string]PluginConfig
- }
- var logger hclog.Logger = hclog.New(&hclog.LoggerOptions{
- Name: "http-plugin",
- Level: hclog.LevelFromString("INFO"),
- Output: os.Stderr,
- JSONFormat: true,
- })
- func getCertPool(caPath string) (*x509.CertPool, error) {
- cp, err := x509.SystemCertPool()
- if err != nil {
- return nil, fmt.Errorf("unable to load system CA certificates: %w", err)
- }
- if cp == nil {
- cp = x509.NewCertPool()
- }
- if caPath == "" {
- return cp, nil
- }
- logger.Info(fmt.Sprintf("Using CA cert '%s'", caPath))
- caCert, err := os.ReadFile(caPath)
- if err != nil {
- return nil, fmt.Errorf("unable to load CA certificate '%s': %w", caPath, err)
- }
- cp.AppendCertsFromPEM(caCert)
- return cp, nil
- }
- func getTLSClient(c *PluginConfig) error {
- caCertPool, err := getCertPool(c.CAPath)
- if err != nil {
- return err
- }
- tlsConfig := &tls.Config{
- RootCAs: caCertPool,
- InsecureSkipVerify: c.SkipTLSVerification,
- }
- if c.CertPath != "" && c.KeyPath != "" {
- logger.Info(fmt.Sprintf("Using client certificate '%s' and key '%s'", c.CertPath, c.KeyPath))
- cert, err := tls.LoadX509KeyPair(c.CertPath, c.KeyPath)
- if err != nil {
- return fmt.Errorf("unable to load client certificate '%s' and key '%s': %w", c.CertPath, c.KeyPath, err)
- }
- tlsConfig.Certificates = []tls.Certificate{cert}
- }
- transport := &http.Transport{
- TLSClientConfig: tlsConfig,
- }
- if c.UnixSocket != "" {
- logger.Info(fmt.Sprintf("Using socket '%s'", c.UnixSocket))
- transport.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
- return net.Dial("unix", strings.TrimSuffix(c.UnixSocket, "/"))
- }
- }
- c.Client = &http.Client{
- Transport: transport,
- }
- return nil
- }
- func (s *HTTPPlugin) 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(fmt.Sprintf("received signal for %s config", notification.Name))
- request, err := http.NewRequest(cfg.Method, cfg.URL, bytes.NewReader([]byte(notification.Text)))
- if err != nil {
- return nil, err
- }
- for headerName, headerValue := range cfg.Headers {
- logger.Debug(fmt.Sprintf("adding header %s: %s", headerName, headerValue))
- request.Header.Add(headerName, headerValue)
- }
- logger.Debug(fmt.Sprintf("making HTTP %s call to %s with body %s", cfg.Method, cfg.URL, notification.Text))
- resp, err := cfg.Client.Do(request.WithContext(ctx))
- if err != nil {
- logger.Error(fmt.Sprintf("Failed to make HTTP request : %s", err))
- return nil, err
- }
- defer resp.Body.Close()
- respData, err := io.ReadAll(resp.Body)
- if err != nil {
- return nil, fmt.Errorf("failed to read response body got error %w", err)
- }
- logger.Debug(fmt.Sprintf("got response %s", string(respData)))
- if resp.StatusCode < 200 || resp.StatusCode >= 300 {
- logger.Warn(fmt.Sprintf("HTTP server returned non 200 status code: %d", resp.StatusCode))
- logger.Debug(fmt.Sprintf("HTTP server returned body: %s", string(respData)))
- return &protobufs.Empty{}, nil
- }
- return &protobufs.Empty{}, nil
- }
- func (s *HTTPPlugin) Configure(ctx context.Context, config *protobufs.Config) (*protobufs.Empty, error) {
- d := PluginConfig{}
- err := yaml.Unmarshal(config.Config, &d)
- if err != nil {
- return nil, err
- }
- err = getTLSClient(&d)
- if err != nil {
- return nil, err
- }
- s.PluginConfigByName[d.Name] = d
- logger.Debug(fmt.Sprintf("HTTP plugin '%s' use URL '%s'", d.Name, d.URL))
- return &protobufs.Empty{}, err
- }
- func main() {
- handshake := plugin.HandshakeConfig{
- ProtocolVersion: 1,
- MagicCookieKey: "CROWDSEC_PLUGIN_KEY",
- MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"),
- }
- sp := &HTTPPlugin{PluginConfigByName: make(map[string]PluginConfig)}
- plugin.Serve(&plugin.ServeConfig{
- HandshakeConfig: handshake,
- Plugins: map[string]plugin.Plugin{
- "http": &protobufs.NotifierPlugin{
- Impl: sp,
- },
- },
- GRPCServer: plugin.DefaultGRPCServer,
- Logger: logger,
- })
- }
|