parent
7d0f89df29
commit
ea40ffd655
9 changed files with 647 additions and 2 deletions
50
.github/workflows/go-tests.yml
vendored
50
.github/workflows/go-tests.yml
vendored
|
@ -55,6 +55,56 @@ jobs:
|
|||
--health-interval=10s
|
||||
--health-timeout=5s
|
||||
--health-retries=3
|
||||
zoo1:
|
||||
image: confluentinc/cp-zookeeper:7.1.1
|
||||
ports:
|
||||
- "2181:2181"
|
||||
env:
|
||||
ZOOKEEPER_CLIENT_PORT: 2181
|
||||
ZOOKEEPER_SERVER_ID: 1
|
||||
ZOOKEEPER_SERVERS: zoo1:2888:3888
|
||||
options: >-
|
||||
--name=zoo1
|
||||
--health-cmd "jps -l | grep zookeeper"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
kafka1:
|
||||
image: crowdsecurity/kafka-ssl
|
||||
ports:
|
||||
- "9093:9093"
|
||||
- "9092:9092"
|
||||
- "9999:9999"
|
||||
env:
|
||||
KAFKA_ADVERTISED_LISTENERS: LISTENER_DOCKER_INTERNAL://127.0.0.1:19092,LISTENER_DOCKER_EXTERNAL://127.0.0.1:9092,LISTENER_DOCKER_EXTERNAL_SSL://127.0.0.1:9093
|
||||
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_DOCKER_INTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL:PLAINTEXT,LISTENER_DOCKER_EXTERNAL_SSL:SSL
|
||||
KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_DOCKER_INTERNAL
|
||||
KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181"
|
||||
KAFKA_BROKER_ID: 1
|
||||
KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO"
|
||||
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
||||
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
|
||||
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
|
||||
KAFKA_JMX_PORT: 9999
|
||||
KAFKA_JMX_HOSTNAME: "127.0.0.1"
|
||||
KAFKA_AUTHORIZER_CLASS_NAME: kafka.security.authorizer.AclAuthorizer
|
||||
KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true"
|
||||
KAFKA_SSL_KEYSTORE_FILENAME: kafka.kafka1.keystore.jks
|
||||
KAFKA_SSL_KEYSTORE_CREDENTIALS: kafka1_keystore_creds
|
||||
KAFKA_SSL_KEY_CREDENTIALS: kafka1_sslkey_creds
|
||||
KAFKA_SSL_TRUSTSTORE_FILENAME: kafka.kafka1.truststore.jks
|
||||
KAFKA_SSL_TRUSTSTORE_CREDENTIALS: kafka1_truststore_creds
|
||||
KAFKA_SSL_ENABLED_PROTOCOLS: TLSv1.2
|
||||
KAFKA_SSL_PROTOCOL: TLSv1.2
|
||||
KAFKA_SSL_CLIENT_AUTH: none
|
||||
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
|
||||
options: >-
|
||||
--name=kafka1
|
||||
--health-cmd "kafka-broker-api-versions --version"
|
||||
--health-interval 10s
|
||||
--health-timeout 10s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
|
||||
|
|
7
go.mod
7
go.mod
|
@ -57,7 +57,7 @@ require (
|
|||
github.com/r3labs/diff/v2 v2.14.1
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942
|
||||
github.com/stretchr/testify v1.7.1
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3
|
||||
google.golang.org/grpc v1.45.0
|
||||
|
@ -126,6 +126,7 @@ require (
|
|||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.7 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
|
@ -146,12 +147,14 @@ require (
|
|||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5 // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/common v0.30.0 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/segmentio/kafka-go v0.4.32 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c // indirect
|
||||
github.com/tidwall/gjson v1.13.0 // indirect
|
||||
|
@ -167,7 +170,7 @@ require (
|
|||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99 // indirect
|
||||
)
|
||||
|
||||
replace golang.org/x/time/rate => github.com/crowdsecurity/crowdsec/pkg/time/rate v0.0.0
|
||||
|
|
14
go.sum
14
go.sum
|
@ -513,6 +513,9 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
|
|||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.7 h1:7cgTQxJCU/vy+oP/E3B9RGbQTgbiVzIJWIKOLoAsPok=
|
||||
github.com/klauspost/compress v1.15.7/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
|
@ -632,6 +635,9 @@ github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhM
|
|||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
|
||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
|
@ -687,6 +693,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
|
|||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/segmentio/kafka-go v0.4.32 h1:Ohr+9E+kDv/Ld2UPJN9hnKZRd2qgiqCmI8v2e1qlfLM=
|
||||
github.com/segmentio/kafka-go v0.4.32/go.mod h1:JAPPIiY3MQIwVHj64CWOP0LsFFfQ7H0w69kuoxnMIS0=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
|
@ -721,6 +729,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942 h1:t0lM6y/M5IiUZyvbBTcngso8SZEZICH7is9B6g/obVU=
|
||||
github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c h1:HelZ2kAFadG0La9d+4htN4HzQ68Bm2iM9qKMSMES6xg=
|
||||
github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c/go.mod h1:JlzghshsemAMDGZLytTFY8C1JQxQPhnatWqNwUXjggo=
|
||||
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
|
@ -749,6 +759,7 @@ github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+
|
|||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
|
||||
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
||||
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -791,6 +802,7 @@ golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACk
|
|||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
@ -1165,6 +1177,8 @@ gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99 h1:dbuHpmKjkDzSOMKAWl10QNlgaZUd3V1q99xc81tt2Kc=
|
||||
gopkg.in/yaml.v3 v3.0.0-20220512140231-539c8e751b99/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
dockeracquisition "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/docker"
|
||||
fileacquisition "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/file"
|
||||
journalctlacquisition "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/journalctl"
|
||||
kafkaacquisition "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/kafka"
|
||||
kinesisacquisition "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/kinesis"
|
||||
syslogacquisition "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/syslog"
|
||||
wineventlogacquisition "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/wineventlog"
|
||||
|
@ -70,6 +71,10 @@ var AcquisitionSources = []struct {
|
|||
name: "wineventlog",
|
||||
iface: func() DataSource { return &wineventlogacquisition.WinEventLogSource{} },
|
||||
},
|
||||
{
|
||||
name: "kafka",
|
||||
iface: func() DataSource { return &kafkaacquisition.KafkaSource{} },
|
||||
},
|
||||
}
|
||||
|
||||
func GetDataSourceIface(dataSourceType string) DataSource {
|
||||
|
|
242
pkg/acquisition/modules/kafka/kafka.go
Normal file
242
pkg/acquisition/modules/kafka/kafka.go
Normal file
|
@ -0,0 +1,242 @@
|
|||
package kafkaacquisition
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/leakybucket"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/segmentio/kafka-go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/tomb.v2"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
dataSourceName = "kafka"
|
||||
)
|
||||
|
||||
var linesRead = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "cs_kafkasource_hits_total",
|
||||
Help: "Total lines that were read from topic",
|
||||
},
|
||||
[]string{"topic"})
|
||||
|
||||
type KafkaConfiguration struct {
|
||||
Brokers []string `yaml:"brokers"`
|
||||
Topic string `yaml:"topic"`
|
||||
GroupID string `yaml:"group_id"`
|
||||
Timeout string `yaml:"timeout"`
|
||||
TLS *TLSConfig `yaml:"tls"`
|
||||
configuration.DataSourceCommonCfg `yaml:",inline"`
|
||||
}
|
||||
|
||||
type TLSConfig struct {
|
||||
InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
|
||||
ClientCert string `yaml:"client_cert"`
|
||||
ClientKey string `yaml:"client_key"`
|
||||
CaCert string `yaml:"ca_cert"`
|
||||
}
|
||||
|
||||
type KafkaSource struct {
|
||||
Config KafkaConfiguration
|
||||
logger *log.Entry
|
||||
Reader *kafka.Reader
|
||||
}
|
||||
|
||||
func (k *KafkaSource) Configure(Config []byte, logger *log.Entry) error {
|
||||
var err error
|
||||
|
||||
k.Config = KafkaConfiguration{}
|
||||
k.logger = logger
|
||||
err = yaml.UnmarshalStrict(Config, &k.Config)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot parse %s datasource configuration", dataSourceName)
|
||||
}
|
||||
if len(k.Config.Brokers) == 0 {
|
||||
return fmt.Errorf("cannot create a %s reader with an empty list of broker addresses", dataSourceName)
|
||||
}
|
||||
if k.Config.Topic == "" {
|
||||
return fmt.Errorf("cannot create a %s reader with am empty topic", dataSourceName)
|
||||
}
|
||||
if k.Config.Mode == "" {
|
||||
k.Config.Mode = configuration.TAIL_MODE
|
||||
}
|
||||
dialer, err := k.Config.NewDialer()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot create %s dialer", dataSourceName)
|
||||
}
|
||||
k.Reader, err = k.Config.NewReader(dialer)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannote create %s reader", dataSourceName)
|
||||
}
|
||||
if k.Reader == nil {
|
||||
return fmt.Errorf("cannot create %s reader", dataSourceName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *KafkaSource) ConfigureByDSN(string, map[string]string, *log.Entry) error {
|
||||
return fmt.Errorf("%s datasource does not support command-line acquisition", dataSourceName)
|
||||
}
|
||||
|
||||
func (k *KafkaSource) GetMode() string {
|
||||
return k.Config.Mode
|
||||
}
|
||||
|
||||
func (k *KafkaSource) GetName() string {
|
||||
return dataSourceName
|
||||
}
|
||||
|
||||
func (k *KafkaSource) OneShotAcquisition(out chan types.Event, t *tomb.Tomb) error {
|
||||
return fmt.Errorf("%s datasource does not support one-shot acquisition", dataSourceName)
|
||||
}
|
||||
|
||||
func (k *KafkaSource) CanRun() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *KafkaSource) GetMetrics() []prometheus.Collector {
|
||||
return []prometheus.Collector{linesRead}
|
||||
}
|
||||
|
||||
func (k *KafkaSource) GetAggregMetrics() []prometheus.Collector {
|
||||
return []prometheus.Collector{linesRead}
|
||||
}
|
||||
|
||||
func (k *KafkaSource) Dump() interface{} {
|
||||
return k
|
||||
}
|
||||
|
||||
func (k *KafkaSource) ReadMessage(out chan types.Event) error {
|
||||
// Start processing from latest Offset
|
||||
k.Reader.SetOffsetAt(context.Background(), time.Now())
|
||||
for {
|
||||
m, err := k.Reader.ReadMessage(context.Background())
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
k.logger.Errorln(errors.Wrapf(err, "while reading %s message", dataSourceName))
|
||||
}
|
||||
l := types.Line{
|
||||
Raw: string(m.Value),
|
||||
Labels: k.Config.Labels,
|
||||
Time: m.Time.UTC(),
|
||||
Src: k.Config.Topic,
|
||||
Process: true,
|
||||
Module: k.GetName(),
|
||||
}
|
||||
linesRead.With(prometheus.Labels{"topic": k.Config.Topic}).Inc()
|
||||
var evt types.Event
|
||||
|
||||
if !k.Config.UseTimeMachine {
|
||||
evt = types.Event{Line: l, Process: true, Type: types.LOG, ExpectMode: leakybucket.LIVE}
|
||||
} else {
|
||||
evt = types.Event{Line: l, Process: true, Type: types.LOG, ExpectMode: leakybucket.TIMEMACHINE}
|
||||
}
|
||||
out <- evt
|
||||
}
|
||||
}
|
||||
|
||||
func (k *KafkaSource) RunReader(out chan types.Event, t *tomb.Tomb) error {
|
||||
t.Go(func() error {
|
||||
return k.ReadMessage(out)
|
||||
})
|
||||
//nolint //fp
|
||||
for {
|
||||
select {
|
||||
case <-t.Dying():
|
||||
k.logger.Infof("%s datasource topic %s stopping", dataSourceName, k.Config.Topic)
|
||||
if err := k.Reader.Close(); err != nil {
|
||||
return errors.Wrapf(err, "while closing %s reader on topic '%s'", dataSourceName, k.Config.Topic)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (k *KafkaSource) StreamingAcquisition(out chan types.Event, t *tomb.Tomb) error {
|
||||
k.logger.Infof("start reader on topic '%s'", k.Config.Topic)
|
||||
|
||||
t.Go(func() error {
|
||||
defer types.CatchPanic("crowdsec/acquis/kafka/live")
|
||||
return k.RunReader(out, t)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kc *KafkaConfiguration) NewTLSConfig() (*tls.Config, error) {
|
||||
tlsConfig := tls.Config{
|
||||
InsecureSkipVerify: kc.TLS.InsecureSkipVerify,
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(kc.TLS.ClientCert, kc.TLS.ClientKey)
|
||||
if err != nil {
|
||||
return &tlsConfig, err
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||
|
||||
caCert, err := ioutil.ReadFile(kc.TLS.CaCert)
|
||||
if err != nil {
|
||||
return &tlsConfig, err
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(caCert)
|
||||
tlsConfig.RootCAs = caCertPool
|
||||
|
||||
tlsConfig.BuildNameToCertificate()
|
||||
return &tlsConfig, err
|
||||
}
|
||||
|
||||
func (kc *KafkaConfiguration) NewDialer() (*kafka.Dialer, error) {
|
||||
dialer := &kafka.Dialer{}
|
||||
var timeoutDuration time.Duration
|
||||
timeoutDuration = time.Duration(10) * time.Second
|
||||
if kc.Timeout != "" {
|
||||
intTimeout, err := strconv.Atoi(kc.Timeout)
|
||||
if err != nil {
|
||||
return dialer, err
|
||||
}
|
||||
timeoutDuration = time.Duration(intTimeout) * time.Second
|
||||
}
|
||||
dialer = &kafka.Dialer{
|
||||
Timeout: timeoutDuration,
|
||||
DualStack: true,
|
||||
}
|
||||
|
||||
if kc.TLS != nil {
|
||||
tlsConfig, err := kc.NewTLSConfig()
|
||||
if err != nil {
|
||||
return dialer, err
|
||||
}
|
||||
dialer.TLS = tlsConfig
|
||||
}
|
||||
return dialer, nil
|
||||
}
|
||||
|
||||
func (kc *KafkaConfiguration) NewReader(dialer *kafka.Dialer) (*kafka.Reader, error) {
|
||||
rConf := kafka.ReaderConfig{
|
||||
Brokers: kc.Brokers,
|
||||
Topic: kc.Topic,
|
||||
Dialer: dialer,
|
||||
}
|
||||
if kc.GroupID != "" {
|
||||
rConf.GroupID = kc.GroupID
|
||||
}
|
||||
if err := rConf.Validate(); err != nil {
|
||||
return &kafka.Reader{}, errors.Wrapf(err, "while validating reader configuration")
|
||||
}
|
||||
return kafka.NewReader(rConf), nil
|
||||
}
|
253
pkg/acquisition/modules/kafka/kafka_test.go
Normal file
253
pkg/acquisition/modules/kafka/kafka_test.go
Normal file
|
@ -0,0 +1,253 @@
|
|||
package kafkaacquisition
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/crowdsecurity/crowdsec/pkg/cstest"
|
||||
"github.com/crowdsecurity/crowdsec/pkg/types"
|
||||
"github.com/segmentio/kafka-go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/tomb.v2"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestConfigure(t *testing.T) {
|
||||
tests := []struct {
|
||||
config string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
config: `
|
||||
foobar: bla
|
||||
source: kafka`,
|
||||
expectedErr: "line 2: field foobar not found in type kafkaacquisition.KafkaConfiguration",
|
||||
},
|
||||
{
|
||||
config: `source: kafka`,
|
||||
expectedErr: "cannot create a kafka reader with an empty list of broker addresses",
|
||||
},
|
||||
{
|
||||
config: `
|
||||
source: kafka
|
||||
brokers:
|
||||
- bla
|
||||
timeout: 5`,
|
||||
expectedErr: "cannot create a kafka reader with am empty topic",
|
||||
},
|
||||
{
|
||||
config: `
|
||||
source: kafka
|
||||
brokers:
|
||||
- bla
|
||||
topic: toto
|
||||
timeout: aa`,
|
||||
expectedErr: "cannot create kafka dialer: strconv.Atoi: parsing \"aa\": invalid syntax",
|
||||
},
|
||||
{
|
||||
config: `
|
||||
source: kafka
|
||||
brokers:
|
||||
- localhost:9092
|
||||
topic: crowdsec`,
|
||||
expectedErr: "",
|
||||
},
|
||||
}
|
||||
|
||||
subLogger := log.WithFields(log.Fields{
|
||||
"type": "kafka",
|
||||
})
|
||||
for _, test := range tests {
|
||||
k := KafkaSource{}
|
||||
err := k.Configure([]byte(test.config), subLogger)
|
||||
cstest.AssertErrorContains(t, err, test.expectedErr)
|
||||
}
|
||||
}
|
||||
|
||||
func writeToKafka(w *kafka.Writer, logs []string) {
|
||||
|
||||
for idx, log := range logs {
|
||||
err := w.WriteMessages(context.Background(), kafka.Message{
|
||||
Key: []byte(strconv.Itoa(idx)),
|
||||
// create an arbitrary message payload for the value
|
||||
Value: []byte(log),
|
||||
})
|
||||
if err != nil {
|
||||
panic("could not write message " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createTopic(topic string, broker string) {
|
||||
conn, err := kafka.Dial("tcp", broker)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
controller, err := conn.Controller()
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
var controllerConn *kafka.Conn
|
||||
controllerConn, err = kafka.Dial("tcp", net.JoinHostPort(controller.Host, strconv.Itoa(controller.Port)))
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
defer controllerConn.Close()
|
||||
|
||||
topicConfigs := []kafka.TopicConfig{
|
||||
{
|
||||
Topic: topic,
|
||||
NumPartitions: 1,
|
||||
ReplicationFactor: 1,
|
||||
},
|
||||
}
|
||||
|
||||
err = controllerConn.CreateTopics(topicConfigs...)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamingAcquisition(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
logs []string
|
||||
expectedLines int
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "valid msgs",
|
||||
logs: []string{
|
||||
"message 1",
|
||||
"message 2",
|
||||
"message 3",
|
||||
},
|
||||
expectedLines: 3,
|
||||
},
|
||||
}
|
||||
|
||||
subLogger := log.WithFields(log.Fields{
|
||||
"type": "kafka",
|
||||
})
|
||||
|
||||
createTopic("crowdsecplaintext", "localhost:9092")
|
||||
|
||||
w := kafka.NewWriter(kafka.WriterConfig{
|
||||
Brokers: []string{"localhost:9092"},
|
||||
Topic: "crowdsecplaintext",
|
||||
})
|
||||
if w == nil {
|
||||
log.Fatalf("Unable to setup a kafka producer")
|
||||
}
|
||||
|
||||
for _, ts := range tests {
|
||||
t.Run(ts.name, func(t *testing.T) {
|
||||
k := KafkaSource{}
|
||||
err := k.Configure([]byte(`
|
||||
source: kafka
|
||||
brokers:
|
||||
- localhost:9092
|
||||
topic: crowdsecplaintext`), subLogger)
|
||||
if err != nil {
|
||||
t.Fatalf("could not configure kafka source : %s", err)
|
||||
}
|
||||
tomb := tomb.Tomb{}
|
||||
out := make(chan types.Event)
|
||||
err = k.StreamingAcquisition(out, &tomb)
|
||||
cstest.AssertErrorContains(t, err, ts.expectedErr)
|
||||
|
||||
actualLines := 0
|
||||
go writeToKafka(w, ts.logs)
|
||||
READLOOP:
|
||||
for {
|
||||
select {
|
||||
case <-out:
|
||||
actualLines++
|
||||
case <-time.After(2 * time.Second):
|
||||
break READLOOP
|
||||
}
|
||||
}
|
||||
assert.Equal(t, ts.expectedLines, actualLines)
|
||||
tomb.Kill(nil)
|
||||
tomb.Wait()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestStreamingAcquisitionWithSSL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
logs []string
|
||||
expectedLines int
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "valid msgs",
|
||||
logs: []string{
|
||||
"message 1",
|
||||
"message 2",
|
||||
},
|
||||
expectedLines: 2,
|
||||
},
|
||||
}
|
||||
|
||||
subLogger := log.WithFields(log.Fields{
|
||||
"type": "kafka",
|
||||
})
|
||||
|
||||
createTopic("crowdsecssl", "localhost:9092")
|
||||
|
||||
w2 := kafka.NewWriter(kafka.WriterConfig{
|
||||
Brokers: []string{"localhost:9092"},
|
||||
Topic: "crowdsecssl",
|
||||
})
|
||||
if w2 == nil {
|
||||
log.Fatalf("Unable to setup a kafka producer")
|
||||
}
|
||||
|
||||
for _, ts := range tests {
|
||||
t.Run(ts.name, func(t *testing.T) {
|
||||
k := KafkaSource{}
|
||||
err := k.Configure([]byte(`
|
||||
source: kafka
|
||||
brokers:
|
||||
- localhost:9093
|
||||
topic: crowdsecssl
|
||||
tls:
|
||||
insecure_skip_verify: true
|
||||
client_cert: ./testdata/kafkaClient.certificate.pem
|
||||
client_key: ./testdata/kafkaClient.key
|
||||
ca_cert: ./testdata/snakeoil-ca-1.crt
|
||||
`), subLogger)
|
||||
if err != nil {
|
||||
t.Fatalf("could not configure kafka source : %s", err)
|
||||
}
|
||||
tomb := tomb.Tomb{}
|
||||
out := make(chan types.Event)
|
||||
err = k.StreamingAcquisition(out, &tomb)
|
||||
cstest.AssertErrorContains(t, err, ts.expectedErr)
|
||||
|
||||
actualLines := 0
|
||||
go writeToKafka(w2, ts.logs)
|
||||
READLOOP:
|
||||
for {
|
||||
select {
|
||||
case <-out:
|
||||
actualLines++
|
||||
case <-time.After(2 * time.Second):
|
||||
break READLOOP
|
||||
}
|
||||
}
|
||||
assert.Equal(t, ts.expectedLines, actualLines)
|
||||
tomb.Kill(nil)
|
||||
tomb.Wait()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
23
pkg/acquisition/modules/kafka/testdata/kafkaClient.certificate.pem
vendored
Normal file
23
pkg/acquisition/modules/kafka/testdata/kafkaClient.certificate.pem
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDyDCCArCgAwIBAgIUZ3H0cvKHzTXDByikP2PLvhqCTSwwDQYJKoZIhvcNAQEL
|
||||
BQAwbzEZMBcGA1UEAwwQQ3Jvd2RTZWMgVGVzdCBDQTERMA8GA1UECwwIQ3Jvd2Rz
|
||||
ZWMxETAPBgNVBAoMCENyb3dkc2VjMQ4wDAYDVQQHDAVQYXJpczEPMA0GA1UECAwG
|
||||
RnJhbmNlMQswCQYDVQQGEwJGUjAeFw0yMjA4MDExNjA5NDJaFw00OTEyMTYxNjA5
|
||||
NDJaMGYxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBh
|
||||
cmlzMREwDwYDVQQKEwhDcm93ZHNlYzENMAsGA1UECxMEVEVTVDEUMBIGA1UEAxML
|
||||
a2Fma2FDbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCG5R6r
|
||||
xi9FcL6p8bD5+bpV3bTDNwRTF4b9psrhVY8MhvjvaoYODHvENJaDb3Z/ipDUdG6e
|
||||
zjgigfjLRRwxvj2+E0nTn/TsfRQIlH2BYPEzCCrG33WKkcmG1K3LEbkXGyBcPljd
|
||||
DPHb2nbZERDFBcIlqNM5N9+cbLFQnJNw3u7Nsv/e4jjUpAeYg30YVKwrr9P4mj8L
|
||||
NR+ZALe0+2NUJpTYX0ZP4vPeYqTGWPshMrGHLrChhYnaYWzEvYITjVtzkHk9xtFg
|
||||
uRGjgtwlpf0m2EM8GHhteHaLb2efU1C860QaFkTBK1JeGU1A2O6O8lOo8CMVG6h+
|
||||
IBA1kspGRO0wmix5AgMBAAGjZTBjMCEGA1UdEQQaMBiCC2thZmthQ2xpZW50ggls
|
||||
b2NhbGhvc3QwHQYDVR0OBBYEFD/QYQ2ppLhrC8qnfSjEsppvI/NPMB8GA1UdIwQY
|
||||
MBaAFCCtzZtp2uUwxDCvIf8ETMpCtLxzMA0GCSqGSIb3DQEBCwUAA4IBAQA7Bly4
|
||||
t1ob95om3h+d9tYOuqzERUhO9BZXjqGFmOxb4pmpg5ANa9j82VOy0PWvBPR4M6N5
|
||||
uHwUKj6S4HWDLpabNNsBWYUzILBBQDqkiKgy0NmakZjv2fbFSIEpZF8sfyL3Z/ci
|
||||
JRo6SqZWILh7B2BqysLmgTJeRFode3zqIKhLPIqYqEDBCwgSL1quX0afut2q86lx
|
||||
x2RJB/N8QsNfXSjTOojXY3cJzLdW4XKGZKk75YhlpYt+v5235paVbocz32diQczk
|
||||
9yCqfJfG8BBNA6WdPgtLhQHiDLO7UY+Y+jGIe2G41w7adT/b2Omeb2h3RbGeqKx9
|
||||
WteVlQb955ItDXKI
|
||||
-----END CERTIFICATE-----
|
32
pkg/acquisition/modules/kafka/testdata/kafkaClient.key
vendored
Normal file
32
pkg/acquisition/modules/kafka/testdata/kafkaClient.key
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
Bag Attributes
|
||||
friendlyName: kafkaclient
|
||||
localKeyID: 54 69 6D 65 20 31 36 35 39 33 37 30 31 38 36 30 33 36
|
||||
Key Attributes: <No Attributes>
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCG5R6rxi9FcL6p
|
||||
8bD5+bpV3bTDNwRTF4b9psrhVY8MhvjvaoYODHvENJaDb3Z/ipDUdG6ezjgigfjL
|
||||
RRwxvj2+E0nTn/TsfRQIlH2BYPEzCCrG33WKkcmG1K3LEbkXGyBcPljdDPHb2nbZ
|
||||
ERDFBcIlqNM5N9+cbLFQnJNw3u7Nsv/e4jjUpAeYg30YVKwrr9P4mj8LNR+ZALe0
|
||||
+2NUJpTYX0ZP4vPeYqTGWPshMrGHLrChhYnaYWzEvYITjVtzkHk9xtFguRGjgtwl
|
||||
pf0m2EM8GHhteHaLb2efU1C860QaFkTBK1JeGU1A2O6O8lOo8CMVG6h+IBA1kspG
|
||||
RO0wmix5AgMBAAECggEAYLrdwomMDjxpzI2OvcJQ1w/zdmT2Ses+FpfLNchXmsES
|
||||
swPs+xgCjFC1eaytCYpAjsirJl90K3KOCJ0XOahUt/822nUCea67deedE/CDJXf+
|
||||
zLsim2otW+0YbtzXn/UIwHzI1kJZELFYthEhuFaHwN+OD6K8S3w5rjeJFtAV6BQc
|
||||
AzMAwQ+6j8DG/V+5mgp4YCrcGXmJzvdXqJdiBmFwoEOAdp/ahTjMscmyI0ZQdMpF
|
||||
t8e8x5WDVT65ScUA6nvKkSttYhJ5qpEWiMerR8rBJbkbNi529fUW7/sX1xTMYI5G
|
||||
psOxGdXGSyH2i365DtFxHhtovSc+TgWpNJgZDcJLLQKBgQDdWi4sJO985m1hps7m
|
||||
bKiXLJE/gbSYQekIlt4/KGbY01BrKSSvgsVTIZbC/m8GhAG48uLG1y51R30cGx6g
|
||||
Wg8616duqnq+P7pvw+hWsGrtzYh4URx6T91SJi313Xi5ouqLsNiMznPoLXEnDgJv
|
||||
xO17TkCnThU/Ms6ml1PFeccAYwKBgQCcAoFVqibXRyFoD2UVMpgekOQ+QxBZXItm
|
||||
RoiBhimEahhx6DjJ9GPmAWUTJfAMom0TNoYa/ziM/+VdNruUMRUvAqBHV2vqllYE
|
||||
Szhfxlh0RyCiZzrgEqZLVMdr0vxbeA4e5D2+26NH0YHGqCVacdX5659bSM5hcP/s
|
||||
WO/fGIcAcwKBgQCIKv3UcjRRZX9MX01QOu/Lb8M6ihQKxiERA55cxAHgyL3j7j9/
|
||||
KLcy2Krh8ZtjKrnUiLYxFBakVwID1DsW8dAEpr19Oqqfdpy+PIolKgndmF6nhV47
|
||||
b/36lzoW0dN+f1ZB8NyGYkqzPaEqIVgmYcKl5BGp2kL/ycWOffEuvidJeQKBgHls
|
||||
eb1y8Ugc1KNpWANnnX4sx3iuOctTfCveOeCVyzqEWQJO++Qzko0yCLkejfdHdB3A
|
||||
EiBxBFK52Ir0TorIqPQt1xGvuQ6cc9ZjtTzV44Kc2YmNTwWXflajZZNGY6PNjS/9
|
||||
9RDXYf5D0f4MYQZEE4axHRavU/IDQS1zCz9Yl7qBAoGAezrd5EziH9VNHMzPnfkH
|
||||
+Lg2DCrRbyG1pHpuKJg2i98Ulwuu/9A5m5Vj1iYrANt9v4ycWimLikyd5vJXW60V
|
||||
9PBb8FB/vjpXNJ1PZBGjlxgpWpzF13JGcJpFBK+z5yCPevzJlc/H+IAQbd3mS3WW
|
||||
DDwAGG2L41aLYKmsjAtr76I=
|
||||
-----END PRIVATE KEY-----
|
23
pkg/acquisition/modules/kafka/testdata/snakeoil-ca-1.crt
vendored
Normal file
23
pkg/acquisition/modules/kafka/testdata/snakeoil-ca-1.crt
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDvzCCAqegAwIBAgIUSc+OZ8EjaDgzm0sjqlHVXjZ0od0wDQYJKoZIhvcNAQEL
|
||||
BQAwbzEZMBcGA1UEAwwQQ3Jvd2RTZWMgVGVzdCBDQTERMA8GA1UECwwIQ3Jvd2Rz
|
||||
ZWMxETAPBgNVBAoMCENyb3dkc2VjMQ4wDAYDVQQHDAVQYXJpczEPMA0GA1UECAwG
|
||||
RnJhbmNlMQswCQYDVQQGEwJGUjAeFw0yMjA4MDExNjA5MzZaFw0yMzA4MDExNjA5
|
||||
MzZaMG8xGTAXBgNVBAMMEENyb3dkU2VjIFRlc3QgQ0ExETAPBgNVBAsMCENyb3dk
|
||||
c2VjMREwDwYDVQQKDAhDcm93ZHNlYzEOMAwGA1UEBwwFUGFyaXMxDzANBgNVBAgM
|
||||
BkZyYW5jZTELMAkGA1UEBhMCRlIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
|
||||
AoIBAQDAzj1gJzEhzcymL9dZX6+dTLdi9RDoII7PWtYCIoY5tqvewOzVBZMEDEhG
|
||||
az8Btwo6Edr7u804Zule9ZVSaTkmse+thNthukXrtmTEuOuienym5KkVddNckTtr
|
||||
w/5MLMkKK0Ux45BYW4H3wT1HpD56ezCUhxL5O3ACPjufw7yvMheHRnQxe3txmlNq
|
||||
rd9swZH0sdZovWmW2Fj+C5qYbP/6hLzii9SNWOzOnKxlbw8CMBzK7KZgWp4qi4sz
|
||||
tFCkGh2+Ya2QV3+q9Z6fD3hTZJfELEbDgP7ULYrvlGzLrrfLFcqAwmQ360PlsWiL
|
||||
bg0+/rWkBRz/3wpma2RP+dGFfaj7AgMBAAGjUzBRMB0GA1UdDgQWBBQgrc2badrl
|
||||
MMQwryH/BEzKQrS8czAfBgNVHSMEGDAWgBQgrc2badrlMMQwryH/BEzKQrS8czAP
|
||||
BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAE6ct4k+X4hAw+TUpN
|
||||
E/rVrEybHFv6qvgyE7Ay/LhpevU+r8UBChv3XZ/u3h4WKdqFrxPN4JDOvIXN0Jq2
|
||||
Xs7Bs//hj+hULvJ3DWfQEQ6LivcxVxQsU47Sbxf6sUeCV3kXSxjFEcsvSx9kPNv6
|
||||
3Bi1EwPrMiwNdpB1BDUG7Z2mFxhoHv1SUppE7Lhu/x/1b7LgYqNy2VWWOFg/TZI2
|
||||
tdg45fMtNYp8kdQW+r18YxToQHUjXkkQqW9HSyxIqeabVqxuuptyY+OSIIFxBWaB
|
||||
A4BbiHPKhJ0umCQa9mPeVKWUjUeXzRnHMXw2nPyqfK+1wQXt/7DZrBQLVe5Z9IHG
|
||||
DZj/
|
||||
-----END CERTIFICATE-----
|
Loading…
Reference in a new issue