github.com/crowdsecurity/crowdsec@v1.6.1/pkg/acquisition/modules/kafka/kafka.go (about)

     1  package kafkaacquisition
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"crypto/x509"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"strconv"
    12  	"time"
    13  
    14  	"github.com/prometheus/client_golang/prometheus"
    15  	"github.com/segmentio/kafka-go"
    16  	log "github.com/sirupsen/logrus"
    17  	"gopkg.in/tomb.v2"
    18  	"gopkg.in/yaml.v2"
    19  
    20  	"github.com/crowdsecurity/go-cs-lib/trace"
    21  
    22  	"github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration"
    23  	"github.com/crowdsecurity/crowdsec/pkg/types"
    24  )
    25  
    26  var (
    27  	dataSourceName = "kafka"
    28  )
    29  
    30  var linesRead = prometheus.NewCounterVec(
    31  	prometheus.CounterOpts{
    32  		Name: "cs_kafkasource_hits_total",
    33  		Help: "Total lines that were read from topic",
    34  	},
    35  	[]string{"topic"})
    36  
    37  type KafkaConfiguration struct {
    38  	Brokers                           []string   `yaml:"brokers"`
    39  	Topic                             string     `yaml:"topic"`
    40  	GroupID                           string     `yaml:"group_id"`
    41  	Partition                         int        `yaml:"partition"`
    42  	Timeout                           string     `yaml:"timeout"`
    43  	TLS                               *TLSConfig `yaml:"tls"`
    44  	configuration.DataSourceCommonCfg `yaml:",inline"`
    45  }
    46  
    47  type TLSConfig struct {
    48  	InsecureSkipVerify bool   `yaml:"insecure_skip_verify"`
    49  	ClientCert         string `yaml:"client_cert"`
    50  	ClientKey          string `yaml:"client_key"`
    51  	CaCert             string `yaml:"ca_cert"`
    52  }
    53  
    54  type KafkaSource struct {
    55  	metricsLevel int
    56  	Config       KafkaConfiguration
    57  	logger       *log.Entry
    58  	Reader       *kafka.Reader
    59  }
    60  
    61  func (k *KafkaSource) GetUuid() string {
    62  	return k.Config.UniqueId
    63  }
    64  
    65  func (k *KafkaSource) UnmarshalConfig(yamlConfig []byte) error {
    66  	k.Config = KafkaConfiguration{}
    67  
    68  	err := yaml.UnmarshalStrict(yamlConfig, &k.Config)
    69  	if err != nil {
    70  		return fmt.Errorf("cannot parse %s datasource configuration: %w", dataSourceName, err)
    71  	}
    72  
    73  	if len(k.Config.Brokers) == 0 {
    74  		return fmt.Errorf("cannot create a %s reader with an empty list of broker addresses", dataSourceName)
    75  	}
    76  
    77  	if k.Config.Topic == "" {
    78  		return fmt.Errorf("cannot create a %s reader with am empty topic", dataSourceName)
    79  	}
    80  
    81  	if k.Config.Mode == "" {
    82  		k.Config.Mode = configuration.TAIL_MODE
    83  	}
    84  
    85  	k.logger.Debugf("successfully unmarshaled kafka configuration : %+v", k.Config)
    86  
    87  	return err
    88  }
    89  
    90  func (k *KafkaSource) Configure(yamlConfig []byte, logger *log.Entry, MetricsLevel int) error {
    91  	k.logger = logger
    92  	k.metricsLevel = MetricsLevel
    93  
    94  	k.logger.Debugf("start configuring %s source", dataSourceName)
    95  
    96  	err := k.UnmarshalConfig(yamlConfig)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	dialer, err := k.Config.NewDialer()
   102  	if err != nil {
   103  		return fmt.Errorf("cannot create %s dialer: %w", dataSourceName, err)
   104  	}
   105  
   106  	k.Reader, err = k.Config.NewReader(dialer, k.logger)
   107  	if err != nil {
   108  		return fmt.Errorf("cannote create %s reader: %w", dataSourceName, err)
   109  	}
   110  
   111  	if k.Reader == nil {
   112  		return fmt.Errorf("cannot create %s reader", dataSourceName)
   113  	}
   114  
   115  	k.logger.Debugf("successfully configured %s source", dataSourceName)
   116  
   117  	return nil
   118  }
   119  
   120  func (k *KafkaSource) ConfigureByDSN(string, map[string]string, *log.Entry, string) error {
   121  	return fmt.Errorf("%s datasource does not support command-line acquisition", dataSourceName)
   122  }
   123  
   124  func (k *KafkaSource) GetMode() string {
   125  	return k.Config.Mode
   126  }
   127  
   128  func (k *KafkaSource) GetName() string {
   129  	return dataSourceName
   130  }
   131  
   132  func (k *KafkaSource) OneShotAcquisition(out chan types.Event, t *tomb.Tomb) error {
   133  	return fmt.Errorf("%s datasource does not support one-shot acquisition", dataSourceName)
   134  }
   135  
   136  func (k *KafkaSource) CanRun() error {
   137  	return nil
   138  }
   139  
   140  func (k *KafkaSource) GetMetrics() []prometheus.Collector {
   141  	return []prometheus.Collector{linesRead}
   142  }
   143  
   144  func (k *KafkaSource) GetAggregMetrics() []prometheus.Collector {
   145  	return []prometheus.Collector{linesRead}
   146  }
   147  
   148  func (k *KafkaSource) Dump() interface{} {
   149  	return k
   150  }
   151  
   152  func (k *KafkaSource) ReadMessage(out chan types.Event) error {
   153  	// Start processing from latest Offset
   154  	k.Reader.SetOffsetAt(context.Background(), time.Now())
   155  	for {
   156  		k.logger.Tracef("reading message from topic '%s'", k.Config.Topic)
   157  		m, err := k.Reader.ReadMessage(context.Background())
   158  		if err != nil {
   159  			if errors.Is(err, io.EOF) {
   160  				return nil
   161  			}
   162  			k.logger.Errorln(fmt.Errorf("while reading %s message: %w", dataSourceName, err))
   163  			continue
   164  		}
   165  		k.logger.Tracef("got message: %s", string(m.Value))
   166  		l := types.Line{
   167  			Raw:     string(m.Value),
   168  			Labels:  k.Config.Labels,
   169  			Time:    m.Time.UTC(),
   170  			Src:     k.Config.Topic,
   171  			Process: true,
   172  			Module:  k.GetName(),
   173  		}
   174  		k.logger.Tracef("line with message read from topic '%s': %+v", k.Config.Topic, l)
   175  		if k.metricsLevel != configuration.METRICS_NONE {
   176  			linesRead.With(prometheus.Labels{"topic": k.Config.Topic}).Inc()
   177  		}
   178  		var evt types.Event
   179  
   180  		if !k.Config.UseTimeMachine {
   181  			evt = types.Event{Line: l, Process: true, Type: types.LOG, ExpectMode: types.LIVE}
   182  		} else {
   183  			evt = types.Event{Line: l, Process: true, Type: types.LOG, ExpectMode: types.TIMEMACHINE}
   184  		}
   185  		out <- evt
   186  	}
   187  }
   188  
   189  func (k *KafkaSource) RunReader(out chan types.Event, t *tomb.Tomb) error {
   190  	k.logger.Debugf("starting %s datasource reader goroutine with configuration %+v", dataSourceName, k.Config)
   191  	t.Go(func() error {
   192  		return k.ReadMessage(out)
   193  	})
   194  	//nolint //fp
   195  	for {
   196  		select {
   197  		case <-t.Dying():
   198  			k.logger.Infof("%s datasource topic %s stopping", dataSourceName, k.Config.Topic)
   199  			if err := k.Reader.Close(); err != nil {
   200  				return fmt.Errorf("while closing  %s reader on topic '%s': %w", dataSourceName, k.Config.Topic, err)
   201  			}
   202  			return nil
   203  		}
   204  	}
   205  }
   206  
   207  func (k *KafkaSource) StreamingAcquisition(out chan types.Event, t *tomb.Tomb) error {
   208  	k.logger.Infof("start reader on brokers '%+v' with topic '%s'", k.Config.Brokers, k.Config.Topic)
   209  
   210  	t.Go(func() error {
   211  		defer trace.CatchPanic("crowdsec/acquis/kafka/live")
   212  		return k.RunReader(out, t)
   213  	})
   214  
   215  	return nil
   216  }
   217  
   218  func (kc *KafkaConfiguration) NewTLSConfig() (*tls.Config, error) {
   219  	tlsConfig := tls.Config{
   220  		InsecureSkipVerify: kc.TLS.InsecureSkipVerify,
   221  	}
   222  
   223  	cert, err := tls.LoadX509KeyPair(kc.TLS.ClientCert, kc.TLS.ClientKey)
   224  	if err != nil {
   225  		return &tlsConfig, err
   226  	}
   227  	tlsConfig.Certificates = []tls.Certificate{cert}
   228  
   229  	caCert, err := os.ReadFile(kc.TLS.CaCert)
   230  	if err != nil {
   231  		return &tlsConfig, err
   232  	}
   233  	caCertPool, err := x509.SystemCertPool()
   234  	if err != nil {
   235  		return &tlsConfig, fmt.Errorf("unable to load system CA certificates: %w", err)
   236  	}
   237  	if caCertPool == nil {
   238  		caCertPool = x509.NewCertPool()
   239  	}
   240  	caCertPool.AppendCertsFromPEM(caCert)
   241  	tlsConfig.RootCAs = caCertPool
   242  
   243  	return &tlsConfig, err
   244  }
   245  
   246  func (kc *KafkaConfiguration) NewDialer() (*kafka.Dialer, error) {
   247  	dialer := &kafka.Dialer{}
   248  	var timeoutDuration time.Duration
   249  	timeoutDuration = time.Duration(10) * time.Second
   250  	if kc.Timeout != "" {
   251  		intTimeout, err := strconv.Atoi(kc.Timeout)
   252  		if err != nil {
   253  			return dialer, err
   254  		}
   255  		timeoutDuration = time.Duration(intTimeout) * time.Second
   256  	}
   257  	dialer = &kafka.Dialer{
   258  		Timeout:   timeoutDuration,
   259  		DualStack: true,
   260  	}
   261  
   262  	if kc.TLS != nil {
   263  		tlsConfig, err := kc.NewTLSConfig()
   264  		if err != nil {
   265  			return dialer, err
   266  		}
   267  		dialer.TLS = tlsConfig
   268  	}
   269  	return dialer, nil
   270  }
   271  
   272  func (kc *KafkaConfiguration) NewReader(dialer *kafka.Dialer, logger *log.Entry) (*kafka.Reader, error) {
   273  	rConf := kafka.ReaderConfig{
   274  		Brokers:     kc.Brokers,
   275  		Topic:       kc.Topic,
   276  		Dialer:      dialer,
   277  		Logger:      kafka.LoggerFunc(logger.Debugf),
   278  		ErrorLogger: kafka.LoggerFunc(logger.Errorf),
   279  	}
   280  	if kc.GroupID != "" && kc.Partition != 0 {
   281  		return &kafka.Reader{}, fmt.Errorf("cannot specify both group_id and partition")
   282  	}
   283  	if kc.GroupID != "" {
   284  		rConf.GroupID = kc.GroupID
   285  	} else if kc.Partition != 0 {
   286  		rConf.Partition = kc.Partition
   287  	} else {
   288  		logger.Warnf("no group_id specified, crowdsec will only read from the 1st partition of the topic")
   289  	}
   290  	if err := rConf.Validate(); err != nil {
   291  		return &kafka.Reader{}, fmt.Errorf("while validating reader configuration: %w", err)
   292  	}
   293  	return kafka.NewReader(rConf), nil
   294  }