main.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. package main
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/tls"
  6. "crypto/x509"
  7. "fmt"
  8. "io"
  9. "net"
  10. "net/http"
  11. "os"
  12. "strings"
  13. "github.com/hashicorp/go-hclog"
  14. plugin "github.com/hashicorp/go-plugin"
  15. "gopkg.in/yaml.v3"
  16. "github.com/crowdsecurity/crowdsec/pkg/protobufs"
  17. )
  18. type PluginConfig struct {
  19. Name string `yaml:"name"`
  20. URL string `yaml:"url"`
  21. UnixSocket string `yaml:"unix_socket"`
  22. Headers map[string]string `yaml:"headers"`
  23. SkipTLSVerification bool `yaml:"skip_tls_verification"`
  24. Method string `yaml:"method"`
  25. LogLevel *string `yaml:"log_level"`
  26. Client *http.Client `yaml:"-"`
  27. CertPath string `yaml:"cert_path"`
  28. KeyPath string `yaml:"key_path"`
  29. CAPath string `yaml:"ca_cert_path"`
  30. }
  31. type HTTPPlugin struct {
  32. PluginConfigByName map[string]PluginConfig
  33. }
  34. var logger hclog.Logger = hclog.New(&hclog.LoggerOptions{
  35. Name: "http-plugin",
  36. Level: hclog.LevelFromString("INFO"),
  37. Output: os.Stderr,
  38. JSONFormat: true,
  39. })
  40. func getCertPool(caPath string) (*x509.CertPool, error) {
  41. cp, err := x509.SystemCertPool()
  42. if err != nil {
  43. return nil, fmt.Errorf("unable to load system CA certificates: %w", err)
  44. }
  45. if cp == nil {
  46. cp = x509.NewCertPool()
  47. }
  48. if caPath == "" {
  49. return cp, nil
  50. }
  51. logger.Info(fmt.Sprintf("Using CA cert '%s'", caPath))
  52. caCert, err := os.ReadFile(caPath)
  53. if err != nil {
  54. return nil, fmt.Errorf("unable to load CA certificate '%s': %w", caPath, err)
  55. }
  56. cp.AppendCertsFromPEM(caCert)
  57. return cp, nil
  58. }
  59. func getTLSClient(c *PluginConfig) error {
  60. caCertPool, err := getCertPool(c.CAPath)
  61. if err != nil {
  62. return err
  63. }
  64. tlsConfig := &tls.Config{
  65. RootCAs: caCertPool,
  66. InsecureSkipVerify: c.SkipTLSVerification,
  67. }
  68. if c.CertPath != "" && c.KeyPath != "" {
  69. logger.Info(fmt.Sprintf("Using client certificate '%s' and key '%s'", c.CertPath, c.KeyPath))
  70. cert, err := tls.LoadX509KeyPair(c.CertPath, c.KeyPath)
  71. if err != nil {
  72. return fmt.Errorf("unable to load client certificate '%s' and key '%s': %w", c.CertPath, c.KeyPath, err)
  73. }
  74. tlsConfig.Certificates = []tls.Certificate{cert}
  75. }
  76. transport := &http.Transport{
  77. TLSClientConfig: tlsConfig,
  78. }
  79. if c.UnixSocket != "" {
  80. logger.Info(fmt.Sprintf("Using socket '%s'", c.UnixSocket))
  81. transport.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
  82. return net.Dial("unix", strings.TrimSuffix(c.UnixSocket, "/"))
  83. }
  84. }
  85. c.Client = &http.Client{
  86. Transport: transport,
  87. }
  88. return nil
  89. }
  90. func (s *HTTPPlugin) Notify(ctx context.Context, notification *protobufs.Notification) (*protobufs.Empty, error) {
  91. if _, ok := s.PluginConfigByName[notification.Name]; !ok {
  92. return nil, fmt.Errorf("invalid plugin config name %s", notification.Name)
  93. }
  94. cfg := s.PluginConfigByName[notification.Name]
  95. if cfg.LogLevel != nil && *cfg.LogLevel != "" {
  96. logger.SetLevel(hclog.LevelFromString(*cfg.LogLevel))
  97. }
  98. logger.Info(fmt.Sprintf("received signal for %s config", notification.Name))
  99. request, err := http.NewRequest(cfg.Method, cfg.URL, bytes.NewReader([]byte(notification.Text)))
  100. if err != nil {
  101. return nil, err
  102. }
  103. for headerName, headerValue := range cfg.Headers {
  104. logger.Debug(fmt.Sprintf("adding header %s: %s", headerName, headerValue))
  105. request.Header.Add(headerName, headerValue)
  106. }
  107. logger.Debug(fmt.Sprintf("making HTTP %s call to %s with body %s", cfg.Method, cfg.URL, notification.Text))
  108. resp, err := cfg.Client.Do(request.WithContext(ctx))
  109. if err != nil {
  110. logger.Error(fmt.Sprintf("Failed to make HTTP request : %s", err))
  111. return nil, err
  112. }
  113. defer resp.Body.Close()
  114. respData, err := io.ReadAll(resp.Body)
  115. if err != nil {
  116. return nil, fmt.Errorf("failed to read response body got error %w", err)
  117. }
  118. logger.Debug(fmt.Sprintf("got response %s", string(respData)))
  119. if resp.StatusCode < 200 || resp.StatusCode >= 300 {
  120. logger.Warn(fmt.Sprintf("HTTP server returned non 200 status code: %d", resp.StatusCode))
  121. logger.Debug(fmt.Sprintf("HTTP server returned body: %s", string(respData)))
  122. return &protobufs.Empty{}, nil
  123. }
  124. return &protobufs.Empty{}, nil
  125. }
  126. func (s *HTTPPlugin) Configure(ctx context.Context, config *protobufs.Config) (*protobufs.Empty, error) {
  127. d := PluginConfig{}
  128. err := yaml.Unmarshal(config.Config, &d)
  129. if err != nil {
  130. return nil, err
  131. }
  132. err = getTLSClient(&d)
  133. if err != nil {
  134. return nil, err
  135. }
  136. s.PluginConfigByName[d.Name] = d
  137. logger.Debug(fmt.Sprintf("HTTP plugin '%s' use URL '%s'", d.Name, d.URL))
  138. return &protobufs.Empty{}, err
  139. }
  140. func main() {
  141. handshake := plugin.HandshakeConfig{
  142. ProtocolVersion: 1,
  143. MagicCookieKey: "CROWDSEC_PLUGIN_KEY",
  144. MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"),
  145. }
  146. sp := &HTTPPlugin{PluginConfigByName: make(map[string]PluginConfig)}
  147. plugin.Serve(&plugin.ServeConfig{
  148. HandshakeConfig: handshake,
  149. Plugins: map[string]plugin.Plugin{
  150. "http": &protobufs.NotifierPlugin{
  151. Impl: sp,
  152. },
  153. },
  154. GRPCServer: plugin.DefaultGRPCServer,
  155. Logger: logger,
  156. })
  157. }