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

     1  package s3acquisition
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"compress/gzip"
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"net/url"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/aws/aws-lambda-go/events"
    19  	"github.com/aws/aws-sdk-go/aws"
    20  	"github.com/aws/aws-sdk-go/aws/session"
    21  	"github.com/aws/aws-sdk-go/service/s3"
    22  	"github.com/aws/aws-sdk-go/service/s3/s3iface"
    23  	"github.com/aws/aws-sdk-go/service/sqs"
    24  	"github.com/aws/aws-sdk-go/service/sqs/sqsiface"
    25  	"github.com/prometheus/client_golang/prometheus"
    26  	log "github.com/sirupsen/logrus"
    27  	"gopkg.in/tomb.v2"
    28  	"gopkg.in/yaml.v2"
    29  
    30  	"github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration"
    31  	"github.com/crowdsecurity/crowdsec/pkg/types"
    32  )
    33  
    34  type S3Configuration struct {
    35  	configuration.DataSourceCommonCfg `yaml:",inline"`
    36  	AwsProfile                        *string `yaml:"aws_profile"`
    37  	AwsRegion                         string  `yaml:"aws_region"`
    38  	AwsEndpoint                       string  `yaml:"aws_endpoint"`
    39  	BucketName                        string  `yaml:"bucket_name"`
    40  	Prefix                            string  `yaml:"prefix"`
    41  	Key                               string  `yaml:"-"` //Only for DSN acquisition
    42  	PollingMethod                     string  `yaml:"polling_method"`
    43  	PollingInterval                   int     `yaml:"polling_interval"`
    44  	SQSName                           string  `yaml:"sqs_name"`
    45  	SQSFormat                         string  `yaml:"sqs_format"`
    46  	MaxBufferSize                     int     `yaml:"max_buffer_size"`
    47  }
    48  
    49  type S3Source struct {
    50  	MetricsLevel int
    51  	Config       S3Configuration
    52  	logger       *log.Entry
    53  	s3Client     s3iface.S3API
    54  	sqsClient    sqsiface.SQSAPI
    55  	readerChan   chan S3Object
    56  	t            *tomb.Tomb
    57  	out          chan types.Event
    58  	ctx          aws.Context
    59  	cancel       context.CancelFunc
    60  }
    61  
    62  type S3Object struct {
    63  	Key    string
    64  	Bucket string
    65  }
    66  
    67  // For some reason, the aws sdk doesn't have a struct for this
    68  // The one aws-lamdbda-go/events is only intended when using S3 Notification without event bridge
    69  type S3Event struct {
    70  	Version    string   `json:"version"`
    71  	Id         string   `json:"id"`
    72  	DetailType string   `json:"detail-type"`
    73  	Source     string   `json:"source"`
    74  	Account    string   `json:"account"`
    75  	Time       string   `json:"time"`
    76  	Region     string   `json:"region"`
    77  	Resources  []string `json:"resources"`
    78  	Detail     struct {
    79  		Version         string `json:"version"`
    80  		RequestId       string `json:"request-id"`
    81  		Requester       string `json:"requester"`
    82  		Reason          string `json:"reason"`
    83  		SourceIpAddress string `json:"source-ip-address"`
    84  		Bucket          struct {
    85  			Name string `json:"name"`
    86  		} `json:"bucket"`
    87  		Object struct {
    88  			Key       string `json:"key"`
    89  			Size      int    `json:"size"`
    90  			Etag      string `json:"etag"`
    91  			Sequencer string `json:"sequencer"`
    92  		} `json:"object"`
    93  	} `json:"detail"`
    94  }
    95  
    96  const PollMethodList = "list"
    97  const PollMethodSQS = "sqs"
    98  const SQSFormatEventBridge = "eventbridge"
    99  const SQSFormatS3Notification = "s3notification"
   100  
   101  var linesRead = prometheus.NewCounterVec(
   102  	prometheus.CounterOpts{
   103  		Name: "cs_s3_hits_total",
   104  		Help: "Number of events read per bucket.",
   105  	},
   106  	[]string{"bucket"},
   107  )
   108  
   109  var objectsRead = prometheus.NewCounterVec(
   110  	prometheus.CounterOpts{
   111  		Name: "cs_s3_objects_total",
   112  		Help: "Number of objects read per bucket.",
   113  	},
   114  	[]string{"bucket"},
   115  )
   116  
   117  var sqsMessagesReceived = prometheus.NewCounterVec(
   118  	prometheus.CounterOpts{
   119  		Name: "cs_s3_sqs_messages_total",
   120  		Help: "Number of SQS messages received per queue.",
   121  	},
   122  	[]string{"queue"},
   123  )
   124  
   125  func (s *S3Source) newS3Client() error {
   126  	options := session.Options{
   127  		SharedConfigState: session.SharedConfigEnable,
   128  	}
   129  	if s.Config.AwsProfile != nil {
   130  		options.Profile = *s.Config.AwsProfile
   131  	}
   132  
   133  	sess, err := session.NewSessionWithOptions(options)
   134  
   135  	if err != nil {
   136  		return fmt.Errorf("failed to create aws session: %w", err)
   137  	}
   138  
   139  	config := aws.NewConfig()
   140  	if s.Config.AwsRegion != "" {
   141  		config = config.WithRegion(s.Config.AwsRegion)
   142  	}
   143  	if s.Config.AwsEndpoint != "" {
   144  		config = config.WithEndpoint(s.Config.AwsEndpoint)
   145  	}
   146  
   147  	s.s3Client = s3.New(sess, config)
   148  	if s.s3Client == nil {
   149  		return fmt.Errorf("failed to create S3 client")
   150  	}
   151  
   152  	return nil
   153  }
   154  
   155  func (s *S3Source) newSQSClient() error {
   156  	var sess *session.Session
   157  
   158  	if s.Config.AwsProfile != nil {
   159  		sess = session.Must(session.NewSessionWithOptions(session.Options{
   160  			SharedConfigState: session.SharedConfigEnable,
   161  			Profile:           *s.Config.AwsProfile,
   162  		}))
   163  	} else {
   164  		sess = session.Must(session.NewSessionWithOptions(session.Options{
   165  			SharedConfigState: session.SharedConfigEnable,
   166  		}))
   167  	}
   168  
   169  	if sess == nil {
   170  		return fmt.Errorf("failed to create aws session")
   171  	}
   172  	config := aws.NewConfig()
   173  	if s.Config.AwsRegion != "" {
   174  		config = config.WithRegion(s.Config.AwsRegion)
   175  	}
   176  	if s.Config.AwsEndpoint != "" {
   177  		config = config.WithEndpoint(s.Config.AwsEndpoint)
   178  	}
   179  	s.sqsClient = sqs.New(sess, config)
   180  	if s.sqsClient == nil {
   181  		return fmt.Errorf("failed to create SQS client")
   182  	}
   183  	return nil
   184  }
   185  
   186  func (s *S3Source) readManager() {
   187  	logger := s.logger.WithField("method", "readManager")
   188  	for {
   189  		select {
   190  		case <-s.t.Dying():
   191  			logger.Infof("Shutting down S3 read manager")
   192  			s.cancel()
   193  			return
   194  		case s3Object := <-s.readerChan:
   195  			logger.Debugf("Reading file %s/%s", s3Object.Bucket, s3Object.Key)
   196  			err := s.readFile(s3Object.Bucket, s3Object.Key)
   197  			if err != nil {
   198  				logger.Errorf("Error while reading file: %s", err)
   199  			}
   200  		}
   201  	}
   202  }
   203  
   204  func (s *S3Source) getBucketContent() ([]*s3.Object, error) {
   205  	logger := s.logger.WithField("method", "getBucketContent")
   206  	logger.Debugf("Getting bucket content for %s", s.Config.BucketName)
   207  	bucketObjects := make([]*s3.Object, 0)
   208  	var continuationToken *string = nil
   209  	for {
   210  		out, err := s.s3Client.ListObjectsV2WithContext(s.ctx, &s3.ListObjectsV2Input{
   211  			Bucket:            aws.String(s.Config.BucketName),
   212  			Prefix:            aws.String(s.Config.Prefix),
   213  			ContinuationToken: continuationToken,
   214  		})
   215  		if err != nil {
   216  			logger.Errorf("Error while listing bucket content: %s", err)
   217  			return nil, err
   218  		}
   219  		bucketObjects = append(bucketObjects, out.Contents...)
   220  		if out.NextContinuationToken == nil {
   221  			break
   222  		}
   223  		continuationToken = out.NextContinuationToken
   224  	}
   225  	sort.Slice(bucketObjects, func(i, j int) bool {
   226  		return bucketObjects[i].LastModified.Before(*bucketObjects[j].LastModified)
   227  	})
   228  	return bucketObjects, nil
   229  }
   230  
   231  func (s *S3Source) listPoll() error {
   232  	logger := s.logger.WithField("method", "listPoll")
   233  	ticker := time.NewTicker(time.Duration(s.Config.PollingInterval) * time.Second)
   234  	lastObjectDate := time.Now()
   235  	defer ticker.Stop()
   236  
   237  	for {
   238  		select {
   239  		case <-s.t.Dying():
   240  			logger.Infof("Shutting down list poller")
   241  			s.cancel()
   242  			return nil
   243  		case <-ticker.C:
   244  			newObject := false
   245  			bucketObjects, err := s.getBucketContent()
   246  			if err != nil {
   247  				logger.Errorf("Error while getting bucket content: %s", err)
   248  				continue
   249  			}
   250  			if bucketObjects == nil {
   251  				continue
   252  			}
   253  			for i := len(bucketObjects) - 1; i >= 0; i-- {
   254  				if bucketObjects[i].LastModified.After(lastObjectDate) {
   255  					newObject = true
   256  					logger.Debugf("Found new object %s", *bucketObjects[i].Key)
   257  					s.readerChan <- S3Object{
   258  						Bucket: s.Config.BucketName,
   259  						Key:    *bucketObjects[i].Key,
   260  					}
   261  				} else {
   262  					break
   263  				}
   264  			}
   265  			if newObject {
   266  				lastObjectDate = *bucketObjects[len(bucketObjects)-1].LastModified
   267  			}
   268  		}
   269  	}
   270  }
   271  
   272  func extractBucketAndPrefixFromEventBridge(message *string) (string, string, error) {
   273  	eventBody := S3Event{}
   274  	err := json.Unmarshal([]byte(*message), &eventBody)
   275  	if err != nil {
   276  		return "", "", err
   277  	}
   278  	if eventBody.Detail.Bucket.Name != "" {
   279  		return eventBody.Detail.Bucket.Name, eventBody.Detail.Object.Key, nil
   280  	}
   281  	return "", "", fmt.Errorf("invalid event body for event bridge format")
   282  }
   283  
   284  func extractBucketAndPrefixFromS3Notif(message *string) (string, string, error) {
   285  	s3notifBody := events.S3Event{}
   286  	err := json.Unmarshal([]byte(*message), &s3notifBody)
   287  	if err != nil {
   288  		return "", "", err
   289  	}
   290  	if len(s3notifBody.Records) == 0 {
   291  		return "", "", fmt.Errorf("no records found in S3 notification")
   292  	}
   293  	if !strings.HasPrefix(s3notifBody.Records[0].EventName, "ObjectCreated:") {
   294  		return "", "", fmt.Errorf("event %s is not supported", s3notifBody.Records[0].EventName)
   295  	}
   296  	return s3notifBody.Records[0].S3.Bucket.Name, s3notifBody.Records[0].S3.Object.Key, nil
   297  }
   298  
   299  func (s *S3Source) extractBucketAndPrefix(message *string) (string, string, error) {
   300  	if s.Config.SQSFormat == SQSFormatEventBridge {
   301  		bucket, key, err := extractBucketAndPrefixFromEventBridge(message)
   302  		if err != nil {
   303  			return "", "", err
   304  		}
   305  		return bucket, key, nil
   306  	} else if s.Config.SQSFormat == SQSFormatS3Notification {
   307  		bucket, key, err := extractBucketAndPrefixFromS3Notif(message)
   308  		if err != nil {
   309  			return "", "", err
   310  		}
   311  		return bucket, key, nil
   312  	} else {
   313  		bucket, key, err := extractBucketAndPrefixFromEventBridge(message)
   314  		if err == nil {
   315  			s.Config.SQSFormat = SQSFormatEventBridge
   316  			return bucket, key, nil
   317  		}
   318  		bucket, key, err = extractBucketAndPrefixFromS3Notif(message)
   319  		if err == nil {
   320  			s.Config.SQSFormat = SQSFormatS3Notification
   321  			return bucket, key, nil
   322  		}
   323  		return "", "", fmt.Errorf("SQS message format not supported")
   324  	}
   325  }
   326  
   327  func (s *S3Source) sqsPoll() error {
   328  	logger := s.logger.WithField("method", "sqsPoll")
   329  	for {
   330  		select {
   331  		case <-s.t.Dying():
   332  			logger.Infof("Shutting down SQS poller")
   333  			s.cancel()
   334  			return nil
   335  		default:
   336  			logger.Trace("Polling SQS queue")
   337  			out, err := s.sqsClient.ReceiveMessageWithContext(s.ctx, &sqs.ReceiveMessageInput{
   338  				QueueUrl:            aws.String(s.Config.SQSName),
   339  				MaxNumberOfMessages: aws.Int64(10),
   340  				WaitTimeSeconds:     aws.Int64(20), //Probably no need to make it configurable ?
   341  			})
   342  			if err != nil {
   343  				logger.Errorf("Error while polling SQS: %s", err)
   344  				continue
   345  			}
   346  			logger.Tracef("SQS output: %v", out)
   347  			logger.Debugf("Received %d messages from SQS", len(out.Messages))
   348  			for _, message := range out.Messages {
   349  				if s.MetricsLevel != configuration.METRICS_NONE {
   350  					sqsMessagesReceived.WithLabelValues(s.Config.SQSName).Inc()
   351  				}
   352  				bucket, key, err := s.extractBucketAndPrefix(message.Body)
   353  				if err != nil {
   354  					logger.Errorf("Error while parsing SQS message: %s", err)
   355  					//Always delete the message to avoid infinite loop
   356  					_, err = s.sqsClient.DeleteMessage(&sqs.DeleteMessageInput{
   357  						QueueUrl:      aws.String(s.Config.SQSName),
   358  						ReceiptHandle: message.ReceiptHandle,
   359  					})
   360  					if err != nil {
   361  						logger.Errorf("Error while deleting SQS message: %s", err)
   362  					}
   363  					continue
   364  				}
   365  				logger.Debugf("Received SQS message for object %s/%s", bucket, key)
   366  				s.readerChan <- S3Object{Key: key, Bucket: bucket}
   367  				_, err = s.sqsClient.DeleteMessage(&sqs.DeleteMessageInput{
   368  					QueueUrl:      aws.String(s.Config.SQSName),
   369  					ReceiptHandle: message.ReceiptHandle,
   370  				})
   371  				if err != nil {
   372  					logger.Errorf("Error while deleting SQS message: %s", err)
   373  				}
   374  				logger.Debugf("Deleted SQS message for object %s/%s", bucket, key)
   375  			}
   376  		}
   377  	}
   378  }
   379  
   380  func (s *S3Source) readFile(bucket string, key string) error {
   381  	//TODO: Handle SSE-C
   382  	var scanner *bufio.Scanner
   383  
   384  	logger := s.logger.WithFields(log.Fields{
   385  		"method": "readFile",
   386  		"bucket": bucket,
   387  		"key":    key,
   388  	})
   389  
   390  	output, err := s.s3Client.GetObjectWithContext(s.ctx, &s3.GetObjectInput{
   391  		Bucket: aws.String(bucket),
   392  		Key:    aws.String(key),
   393  	})
   394  
   395  	if err != nil {
   396  		return fmt.Errorf("failed to get object %s/%s: %w", bucket, key, err)
   397  	}
   398  	defer output.Body.Close()
   399  
   400  	if strings.HasSuffix(key, ".gz") {
   401  		//This *might* be a gzipped file, but sometimes the SDK will decompress the data for us (it's not clear when it happens, only had the issue with cloudtrail logs)
   402  		header := make([]byte, 2)
   403  		_, err := output.Body.Read(header)
   404  		if err != nil {
   405  			return fmt.Errorf("failed to read header of object %s/%s: %w", bucket, key, err)
   406  		}
   407  		if header[0] == 0x1f && header[1] == 0x8b {
   408  			gz, err := gzip.NewReader(io.MultiReader(bytes.NewReader(header), output.Body))
   409  			if err != nil {
   410  				return fmt.Errorf("failed to create gzip reader for object %s/%s: %w", bucket, key, err)
   411  			}
   412  			scanner = bufio.NewScanner(gz)
   413  		} else {
   414  			scanner = bufio.NewScanner(io.MultiReader(bytes.NewReader(header), output.Body))
   415  		}
   416  	} else {
   417  		scanner = bufio.NewScanner(output.Body)
   418  	}
   419  	if s.Config.MaxBufferSize > 0 {
   420  		s.logger.Infof("Setting max buffer size to %d", s.Config.MaxBufferSize)
   421  		buf := make([]byte, 0, bufio.MaxScanTokenSize)
   422  		scanner.Buffer(buf, s.Config.MaxBufferSize)
   423  	}
   424  	for scanner.Scan() {
   425  		select {
   426  		case <-s.t.Dying():
   427  			s.logger.Infof("Shutting down reader for %s/%s", bucket, key)
   428  			return nil
   429  		default:
   430  			text := scanner.Text()
   431  			logger.Tracef("Read line %s", text)
   432  			if s.MetricsLevel != configuration.METRICS_NONE {
   433  				linesRead.WithLabelValues(bucket).Inc()
   434  			}
   435  			l := types.Line{}
   436  			l.Raw = text
   437  			l.Labels = s.Config.Labels
   438  			l.Time = time.Now().UTC()
   439  			l.Process = true
   440  			l.Module = s.GetName()
   441  			if s.MetricsLevel == configuration.METRICS_FULL {
   442  				l.Src = bucket + "/" + key
   443  			} else if s.MetricsLevel == configuration.METRICS_AGGREGATE {
   444  				l.Src = bucket
   445  			}
   446  			var evt types.Event
   447  			if !s.Config.UseTimeMachine {
   448  				evt = types.Event{Line: l, Process: true, Type: types.LOG, ExpectMode: types.LIVE}
   449  			} else {
   450  				evt = types.Event{Line: l, Process: true, Type: types.LOG, ExpectMode: types.TIMEMACHINE}
   451  			}
   452  			s.out <- evt
   453  		}
   454  	}
   455  	if err := scanner.Err(); err != nil {
   456  		return fmt.Errorf("failed to read object %s/%s: %s", bucket, key, err)
   457  	}
   458  	if s.MetricsLevel != configuration.METRICS_NONE {
   459  		objectsRead.WithLabelValues(bucket).Inc()
   460  	}
   461  	return nil
   462  }
   463  
   464  func (s *S3Source) GetUuid() string {
   465  	return s.Config.UniqueId
   466  }
   467  
   468  func (s *S3Source) GetMetrics() []prometheus.Collector {
   469  	return []prometheus.Collector{linesRead, objectsRead, sqsMessagesReceived}
   470  }
   471  func (s *S3Source) GetAggregMetrics() []prometheus.Collector {
   472  	return []prometheus.Collector{linesRead, objectsRead, sqsMessagesReceived}
   473  }
   474  
   475  func (s *S3Source) UnmarshalConfig(yamlConfig []byte) error {
   476  	s.Config = S3Configuration{}
   477  	err := yaml.UnmarshalStrict(yamlConfig, &s.Config)
   478  	if err != nil {
   479  		return fmt.Errorf("cannot parse S3Acquisition configuration: %w", err)
   480  	}
   481  	if s.Config.Mode == "" {
   482  		s.Config.Mode = configuration.TAIL_MODE
   483  	}
   484  	if s.Config.PollingMethod == "" {
   485  		s.Config.PollingMethod = PollMethodList
   486  	}
   487  
   488  	if s.Config.PollingInterval == 0 {
   489  		s.Config.PollingInterval = 60
   490  	}
   491  
   492  	if s.Config.MaxBufferSize == 0 {
   493  		s.Config.MaxBufferSize = bufio.MaxScanTokenSize
   494  	}
   495  
   496  	if s.Config.PollingMethod != PollMethodList && s.Config.PollingMethod != PollMethodSQS {
   497  		return fmt.Errorf("invalid polling method %s", s.Config.PollingMethod)
   498  	}
   499  
   500  	if s.Config.BucketName != "" && s.Config.SQSName != "" {
   501  		return fmt.Errorf("bucket_name and sqs_name are mutually exclusive")
   502  	}
   503  
   504  	if s.Config.PollingMethod == PollMethodSQS && s.Config.SQSName == "" {
   505  		return fmt.Errorf("sqs_name is required when using sqs polling method")
   506  	}
   507  
   508  	if s.Config.BucketName == "" && s.Config.PollingMethod == PollMethodList {
   509  		return fmt.Errorf("bucket_name is required")
   510  	}
   511  
   512  	if s.Config.SQSFormat != "" && s.Config.SQSFormat != SQSFormatEventBridge && s.Config.SQSFormat != SQSFormatS3Notification {
   513  		return fmt.Errorf("invalid sqs_format %s, must be empty, %s or %s", s.Config.SQSFormat, SQSFormatEventBridge, SQSFormatS3Notification)
   514  	}
   515  
   516  	return nil
   517  }
   518  
   519  func (s *S3Source) Configure(yamlConfig []byte, logger *log.Entry, metricsLevel int) error {
   520  	err := s.UnmarshalConfig(yamlConfig)
   521  	if err != nil {
   522  		return err
   523  	}
   524  
   525  	if s.Config.SQSName != "" {
   526  		s.logger = logger.WithFields(log.Fields{
   527  			"queue": s.Config.SQSName,
   528  		})
   529  	} else {
   530  		s.logger = logger.WithFields(log.Fields{
   531  			"bucket": s.Config.BucketName,
   532  			"prefix": s.Config.Prefix,
   533  		})
   534  	}
   535  
   536  	if !s.Config.UseTimeMachine {
   537  		s.logger.Warning("use_time_machine is not set to true in the datasource configuration. This will likely lead to false positives as S3 logs are not processed in real time.")
   538  	}
   539  
   540  	if s.Config.PollingMethod == PollMethodList {
   541  		s.logger.Warning("Polling method is set to list. This is not recommended as it will not scale well. Consider using SQS instead.")
   542  	}
   543  
   544  	err = s.newS3Client()
   545  	if err != nil {
   546  		return err
   547  	}
   548  
   549  	if s.Config.PollingMethod == PollMethodSQS {
   550  		err = s.newSQSClient()
   551  		if err != nil {
   552  			return err
   553  		}
   554  	}
   555  
   556  	return nil
   557  }
   558  
   559  func (s *S3Source) ConfigureByDSN(dsn string, labels map[string]string, logger *log.Entry, uuid string) error {
   560  	if !strings.HasPrefix(dsn, "s3://") {
   561  		return fmt.Errorf("invalid DSN %s for S3 source, must start with s3://", dsn)
   562  	}
   563  
   564  	s.Config = S3Configuration{}
   565  	s.logger = logger.WithFields(log.Fields{
   566  		"bucket": s.Config.BucketName,
   567  		"prefix": s.Config.Prefix,
   568  	})
   569  	dsn = strings.TrimPrefix(dsn, "s3://")
   570  	args := strings.Split(dsn, "?")
   571  	if len(args[0]) == 0 {
   572  		return fmt.Errorf("empty s3:// DSN")
   573  	}
   574  
   575  	if len(args) == 2 && len(args[1]) != 0 {
   576  		params, err := url.ParseQuery(args[1])
   577  		if err != nil {
   578  			return fmt.Errorf("could not parse s3 args: %w", err)
   579  		}
   580  		for key, value := range params {
   581  			switch key {
   582  			case "log_level":
   583  				if len(value) != 1 {
   584  					return errors.New("expected zero or one value for 'log_level'")
   585  				}
   586  				lvl, err := log.ParseLevel(value[0])
   587  				if err != nil {
   588  					return fmt.Errorf("unknown level %s: %w", value[0], err)
   589  				}
   590  				s.logger.Logger.SetLevel(lvl)
   591  			case "max_buffer_size":
   592  				if len(value) != 1 {
   593  					return errors.New("expected zero or one value for 'max_buffer_size'")
   594  				}
   595  				maxBufferSize, err := strconv.Atoi(value[0])
   596  				if err != nil {
   597  					return fmt.Errorf("invalid value for 'max_buffer_size': %w", err)
   598  				}
   599  				s.logger.Debugf("Setting max buffer size to %d", maxBufferSize)
   600  				s.Config.MaxBufferSize = maxBufferSize
   601  			default:
   602  				return fmt.Errorf("unknown parameter %s", key)
   603  			}
   604  		}
   605  	}
   606  
   607  	s.Config.Labels = labels
   608  	s.Config.Mode = configuration.CAT_MODE
   609  	s.Config.UniqueId = uuid
   610  
   611  	pathParts := strings.Split(args[0], "/")
   612  	s.logger.Debugf("pathParts: %v", pathParts)
   613  
   614  	//FIXME: handle s3://bucket/
   615  	if len(pathParts) == 1 {
   616  		s.Config.BucketName = pathParts[0]
   617  		s.Config.Prefix = ""
   618  	} else if len(pathParts) > 1 {
   619  		s.Config.BucketName = pathParts[0]
   620  		if args[0][len(args[0])-1] == '/' {
   621  			s.Config.Prefix = strings.Join(pathParts[1:], "/")
   622  		} else {
   623  			s.Config.Key = strings.Join(pathParts[1:], "/")
   624  		}
   625  	} else {
   626  		return fmt.Errorf("invalid DSN %s for S3 source", dsn)
   627  	}
   628  
   629  	err := s.newS3Client()
   630  	if err != nil {
   631  		return err
   632  	}
   633  
   634  	return nil
   635  }
   636  
   637  func (s *S3Source) GetMode() string {
   638  	return s.Config.Mode
   639  }
   640  
   641  func (s *S3Source) GetName() string {
   642  	return "s3"
   643  }
   644  
   645  func (s *S3Source) OneShotAcquisition(out chan types.Event, t *tomb.Tomb) error {
   646  	s.logger.Infof("starting acquisition of %s/%s/%s", s.Config.BucketName, s.Config.Prefix, s.Config.Key)
   647  	s.out = out
   648  	s.ctx, s.cancel = context.WithCancel(context.Background())
   649  	s.Config.UseTimeMachine = true
   650  	s.t = t
   651  	if s.Config.Key != "" {
   652  		err := s.readFile(s.Config.BucketName, s.Config.Key)
   653  		if err != nil {
   654  			return err
   655  		}
   656  	} else {
   657  		//No key, get everything in the bucket based on the prefix
   658  		objects, err := s.getBucketContent()
   659  		if err != nil {
   660  			return err
   661  		}
   662  		for _, object := range objects {
   663  			err := s.readFile(s.Config.BucketName, *object.Key)
   664  			if err != nil {
   665  				return err
   666  			}
   667  		}
   668  	}
   669  	t.Kill(nil)
   670  	return nil
   671  }
   672  
   673  func (s *S3Source) StreamingAcquisition(out chan types.Event, t *tomb.Tomb) error {
   674  	s.t = t
   675  	s.out = out
   676  	s.readerChan = make(chan S3Object, 100) //FIXME: does this needs to be buffered?
   677  	s.ctx, s.cancel = context.WithCancel(context.Background())
   678  	s.logger.Infof("starting acquisition of %s/%s", s.Config.BucketName, s.Config.Prefix)
   679  	t.Go(func() error {
   680  		s.readManager()
   681  		return nil
   682  	})
   683  	if s.Config.PollingMethod == PollMethodSQS {
   684  		t.Go(func() error {
   685  			err := s.sqsPoll()
   686  			if err != nil {
   687  				return err
   688  			}
   689  			return nil
   690  		})
   691  	} else {
   692  		t.Go(func() error {
   693  			err := s.listPoll()
   694  			if err != nil {
   695  				return err
   696  			}
   697  			return nil
   698  		})
   699  	}
   700  	return nil
   701  }
   702  
   703  func (s *S3Source) CanRun() error {
   704  	return nil
   705  }
   706  
   707  func (s *S3Source) Dump() interface{} {
   708  	return s
   709  }