main.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. package main
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/tls"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "os"
  10. "github.com/crowdsecurity/crowdsec/pkg/protobufs"
  11. "github.com/hashicorp/go-hclog"
  12. plugin "github.com/hashicorp/go-plugin"
  13. "gopkg.in/yaml.v2"
  14. )
  15. type PluginConfig struct {
  16. Name string `yaml:"name"`
  17. URL string `yaml:"url"`
  18. Headers map[string]string `yaml:"headers"`
  19. SkipTLSVerification bool `yaml:"skip_tls_verification"`
  20. Method string `yaml:"method"`
  21. LogLevel *string `yaml:"log_level"`
  22. }
  23. type HTTPPlugin struct {
  24. PluginConfigByName map[string]PluginConfig
  25. }
  26. var logger hclog.Logger = hclog.New(&hclog.LoggerOptions{
  27. Name: "http-plugin",
  28. Level: hclog.LevelFromString("INFO"),
  29. Output: os.Stderr,
  30. JSONFormat: true,
  31. })
  32. func (s *HTTPPlugin) Notify(ctx context.Context, notification *protobufs.Notification) (*protobufs.Empty, error) {
  33. if _, ok := s.PluginConfigByName[notification.Name]; !ok {
  34. return nil, fmt.Errorf("invalid plugin config name %s", notification.Name)
  35. }
  36. cfg := s.PluginConfigByName[notification.Name]
  37. if cfg.LogLevel != nil && *cfg.LogLevel != "" {
  38. logger.SetLevel(hclog.LevelFromString(*cfg.LogLevel))
  39. }
  40. logger.Info(fmt.Sprintf("received signal for %s config", notification.Name))
  41. client := http.Client{}
  42. if cfg.SkipTLSVerification {
  43. client.Transport = &http.Transport{
  44. TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
  45. }
  46. }
  47. request, err := http.NewRequest(cfg.Method, cfg.URL, bytes.NewReader([]byte(notification.Text)))
  48. if err != nil {
  49. return nil, err
  50. }
  51. for headerName, headerValue := range cfg.Headers {
  52. logger.Debug(fmt.Sprintf("adding header %s: %s", headerName, headerValue))
  53. request.Header.Add(headerName, headerValue)
  54. }
  55. logger.Debug(fmt.Sprintf("making HTTP %s call to %s with body %s", cfg.Method, cfg.URL, notification.Text))
  56. resp, err := client.Do(request)
  57. if err != nil {
  58. logger.Error(fmt.Sprintf("Failed to make HTTP request : %s", err))
  59. return nil, err
  60. }
  61. defer resp.Body.Close()
  62. respData, err := io.ReadAll(resp.Body)
  63. if err != nil {
  64. return nil, fmt.Errorf("failed to read response body got error %s", err)
  65. }
  66. logger.Debug(fmt.Sprintf("got response %s", string(respData)))
  67. if resp.StatusCode < 200 || resp.StatusCode >= 300 {
  68. logger.Warn(fmt.Sprintf("HTTP server returned non 200 status code: %d", resp.StatusCode))
  69. return &protobufs.Empty{}, nil
  70. }
  71. return &protobufs.Empty{}, nil
  72. }
  73. func (s *HTTPPlugin) Configure(ctx context.Context, config *protobufs.Config) (*protobufs.Empty, error) {
  74. d := PluginConfig{}
  75. err := yaml.Unmarshal(config.Config, &d)
  76. s.PluginConfigByName[d.Name] = d
  77. logger.Debug(fmt.Sprintf("HTTP plugin '%s' use URL '%s'", d.Name, d.URL))
  78. return &protobufs.Empty{}, err
  79. }
  80. func main() {
  81. var handshake = plugin.HandshakeConfig{
  82. ProtocolVersion: 1,
  83. MagicCookieKey: "CROWDSEC_PLUGIN_KEY",
  84. MagicCookieValue: os.Getenv("CROWDSEC_PLUGIN_KEY"),
  85. }
  86. sp := &HTTPPlugin{PluginConfigByName: make(map[string]PluginConfig)}
  87. plugin.Serve(&plugin.ServeConfig{
  88. HandshakeConfig: handshake,
  89. Plugins: map[string]plugin.Plugin{
  90. "http": &protobufs.NotifierPlugin{
  91. Impl: sp,
  92. },
  93. },
  94. GRPCServer: plugin.DefaultGRPCServer,
  95. Logger: logger,
  96. })
  97. }