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 }