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 }