mirror of
https://github.com/trufflesecurity/trufflehog.git
synced 2024-11-14 17:07:31 +00:00
add metrics to s3 scan
This commit is contained in:
parent
c5c48e04c1
commit
9722ad7a41
2 changed files with 116 additions and 1 deletions
97
pkg/sources/s3/metrics.go
Normal file
97
pkg/sources/s3/metrics.go
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
package s3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
|
||||||
|
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// metricsCollector defines the interface for recording S3 scan metrics.
|
||||||
|
type metricsCollector interface {
|
||||||
|
// Object metrics.
|
||||||
|
|
||||||
|
RecordObjectScanned(bucket string)
|
||||||
|
RecordObjectSkipped(bucket, reason string)
|
||||||
|
RecordObjectError(bucket string)
|
||||||
|
|
||||||
|
// Role metrics.
|
||||||
|
|
||||||
|
RecordRoleScanned(roleArn string)
|
||||||
|
RecordBucketForRole(roleArn string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type collector struct {
|
||||||
|
objectsScanned *prometheus.CounterVec
|
||||||
|
objectsSkipped *prometheus.CounterVec
|
||||||
|
objectsErrors *prometheus.CounterVec
|
||||||
|
rolesScanned *prometheus.GaugeVec
|
||||||
|
bucketsPerRole *prometheus.GaugeVec
|
||||||
|
}
|
||||||
|
|
||||||
|
func newS3MetricsCollector() metricsCollector {
|
||||||
|
return &collector{
|
||||||
|
objectsScanned: promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Namespace: common.MetricsNamespace,
|
||||||
|
Subsystem: common.MetricsSubsystem,
|
||||||
|
Name: "objects_scanned_total",
|
||||||
|
Help: "Total number of S3 objects successfully scanned",
|
||||||
|
}, []string{"bucket"}),
|
||||||
|
|
||||||
|
objectsSkipped: promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Namespace: common.MetricsNamespace,
|
||||||
|
Subsystem: common.MetricsSubsystem,
|
||||||
|
Name: "objects_skipped_total",
|
||||||
|
Help: "Total number of S3 objects skipped during scan",
|
||||||
|
}, []string{"bucket", "reason"}),
|
||||||
|
|
||||||
|
objectsErrors: promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Namespace: common.MetricsNamespace,
|
||||||
|
Subsystem: common.MetricsSubsystem,
|
||||||
|
Name: "objects_errors_total",
|
||||||
|
Help: "Total number of errors encountered during S3 scan",
|
||||||
|
}, []string{"bucket"}),
|
||||||
|
|
||||||
|
rolesScanned: promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Namespace: common.MetricsNamespace,
|
||||||
|
Subsystem: common.MetricsSubsystem,
|
||||||
|
Name: "roles_scanned",
|
||||||
|
Help: "Number of AWS roles being scanned",
|
||||||
|
}, []string{"role_arn"}),
|
||||||
|
|
||||||
|
bucketsPerRole: promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Namespace: common.MetricsNamespace,
|
||||||
|
Subsystem: common.MetricsSubsystem,
|
||||||
|
Name: "buckets_per_role",
|
||||||
|
Help: "Number of buckets accessible per AWS role",
|
||||||
|
}, []string{"role_arn"}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *collector) RecordObjectScanned(bucket string) {
|
||||||
|
c.objectsScanned.WithLabelValues(bucket).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *collector) RecordObjectSkipped(bucket, reason string) {
|
||||||
|
c.objectsSkipped.WithLabelValues(bucket, reason).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *collector) RecordObjectError(bucket string) {
|
||||||
|
c.objectsErrors.WithLabelValues(bucket).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultRoleARN = "default"
|
||||||
|
|
||||||
|
func (c *collector) RecordRoleScanned(roleArn string) {
|
||||||
|
if roleArn == "" {
|
||||||
|
roleArn = defaultRoleARN
|
||||||
|
}
|
||||||
|
c.rolesScanned.WithLabelValues(roleArn).Set(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *collector) RecordBucketForRole(roleArn string) {
|
||||||
|
if roleArn == "" {
|
||||||
|
roleArn = defaultRoleARN
|
||||||
|
}
|
||||||
|
c.bucketsPerRole.WithLabelValues(roleArn).Inc()
|
||||||
|
}
|
|
@ -48,6 +48,7 @@ type Source struct {
|
||||||
|
|
||||||
progressTracker *ProgressTracker
|
progressTracker *ProgressTracker
|
||||||
sources.Progress
|
sources.Progress
|
||||||
|
metricsCollector metricsCollector
|
||||||
|
|
||||||
errorCount *sync.Map
|
errorCount *sync.Map
|
||||||
jobPool *errgroup.Group
|
jobPool *errgroup.Group
|
||||||
|
@ -98,6 +99,7 @@ func (s *Source) Init(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
s.metricsCollector = newS3MetricsCollector()
|
||||||
|
|
||||||
s.setMaxObjectSize(conn.GetMaxObjectSize())
|
s.setMaxObjectSize(conn.GetMaxObjectSize())
|
||||||
|
|
||||||
|
@ -282,6 +284,8 @@ func (s *Source) scanBuckets(
|
||||||
|
|
||||||
bucketsToScanCount := len(bucketsToScan)
|
bucketsToScanCount := len(bucketsToScan)
|
||||||
for i := startIdx; i < bucketsToScanCount; i++ {
|
for i := startIdx; i < bucketsToScanCount; i++ {
|
||||||
|
s.metricsCollector.RecordBucketForRole(role)
|
||||||
|
|
||||||
bucket := bucketsToScan[i]
|
bucket := bucketsToScan[i]
|
||||||
ctx := context.WithValue(ctx, "bucket", bucket)
|
ctx := context.WithValue(ctx, "bucket", bucket)
|
||||||
|
|
||||||
|
@ -413,6 +417,7 @@ func (s *Source) pageChunker(
|
||||||
|
|
||||||
for objIdx, obj := range metadata.page.Contents {
|
for objIdx, obj := range metadata.page.Contents {
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
|
s.metricsCollector.RecordObjectSkipped(metadata.bucket, "nil_object")
|
||||||
if err := s.progressTracker.UpdateObjectProgress(ctx, objIdx, metadata.bucket, metadata.page.Contents); err != nil {
|
if err := s.progressTracker.UpdateObjectProgress(ctx, objIdx, metadata.bucket, metadata.page.Contents); err != nil {
|
||||||
ctx.Logger().Error(err, "could not update progress for nil object")
|
ctx.Logger().Error(err, "could not update progress for nil object")
|
||||||
}
|
}
|
||||||
|
@ -429,6 +434,7 @@ func (s *Source) pageChunker(
|
||||||
// Skip GLACIER and GLACIER_IR objects.
|
// Skip GLACIER and GLACIER_IR objects.
|
||||||
if obj.StorageClass == nil || strings.Contains(*obj.StorageClass, "GLACIER") {
|
if obj.StorageClass == nil || strings.Contains(*obj.StorageClass, "GLACIER") {
|
||||||
ctx.Logger().V(5).Info("Skipping object in storage class", "storage_class", *obj.StorageClass)
|
ctx.Logger().V(5).Info("Skipping object in storage class", "storage_class", *obj.StorageClass)
|
||||||
|
s.metricsCollector.RecordObjectSkipped(metadata.bucket, "storage_class")
|
||||||
if err := s.progressTracker.UpdateObjectProgress(ctx, objIdx, metadata.bucket, metadata.page.Contents); err != nil {
|
if err := s.progressTracker.UpdateObjectProgress(ctx, objIdx, metadata.bucket, metadata.page.Contents); err != nil {
|
||||||
ctx.Logger().Error(err, "could not update progress for glacier object")
|
ctx.Logger().Error(err, "could not update progress for glacier object")
|
||||||
}
|
}
|
||||||
|
@ -438,6 +444,7 @@ func (s *Source) pageChunker(
|
||||||
// Ignore large files.
|
// Ignore large files.
|
||||||
if *obj.Size > s.maxObjectSize {
|
if *obj.Size > s.maxObjectSize {
|
||||||
ctx.Logger().V(5).Info("Skipping %d byte file (over maxObjectSize limit)")
|
ctx.Logger().V(5).Info("Skipping %d byte file (over maxObjectSize limit)")
|
||||||
|
s.metricsCollector.RecordObjectSkipped(metadata.bucket, "size_limit")
|
||||||
if err := s.progressTracker.UpdateObjectProgress(ctx, objIdx, metadata.bucket, metadata.page.Contents); err != nil {
|
if err := s.progressTracker.UpdateObjectProgress(ctx, objIdx, metadata.bucket, metadata.page.Contents); err != nil {
|
||||||
ctx.Logger().Error(err, "could not update progress for large file")
|
ctx.Logger().Error(err, "could not update progress for large file")
|
||||||
}
|
}
|
||||||
|
@ -447,6 +454,7 @@ func (s *Source) pageChunker(
|
||||||
// File empty file.
|
// File empty file.
|
||||||
if *obj.Size == 0 {
|
if *obj.Size == 0 {
|
||||||
ctx.Logger().V(5).Info("Skipping empty file")
|
ctx.Logger().V(5).Info("Skipping empty file")
|
||||||
|
s.metricsCollector.RecordObjectSkipped(metadata.bucket, "empty_file")
|
||||||
if err := s.progressTracker.UpdateObjectProgress(ctx, objIdx, metadata.bucket, metadata.page.Contents); err != nil {
|
if err := s.progressTracker.UpdateObjectProgress(ctx, objIdx, metadata.bucket, metadata.page.Contents); err != nil {
|
||||||
ctx.Logger().Error(err, "could not update progress for empty file")
|
ctx.Logger().Error(err, "could not update progress for empty file")
|
||||||
}
|
}
|
||||||
|
@ -456,6 +464,7 @@ func (s *Source) pageChunker(
|
||||||
// Skip incompatible extensions.
|
// Skip incompatible extensions.
|
||||||
if common.SkipFile(*obj.Key) {
|
if common.SkipFile(*obj.Key) {
|
||||||
ctx.Logger().V(5).Info("Skipping file with incompatible extension")
|
ctx.Logger().V(5).Info("Skipping file with incompatible extension")
|
||||||
|
s.metricsCollector.RecordObjectSkipped(metadata.bucket, "incompatible_extension")
|
||||||
if err := s.progressTracker.UpdateObjectProgress(ctx, objIdx, metadata.bucket, metadata.page.Contents); err != nil {
|
if err := s.progressTracker.UpdateObjectProgress(ctx, objIdx, metadata.bucket, metadata.page.Contents); err != nil {
|
||||||
ctx.Logger().Error(err, "could not update progress for incompatible file")
|
ctx.Logger().Error(err, "could not update progress for incompatible file")
|
||||||
}
|
}
|
||||||
|
@ -471,6 +480,7 @@ func (s *Source) pageChunker(
|
||||||
|
|
||||||
if strings.HasSuffix(*obj.Key, "/") {
|
if strings.HasSuffix(*obj.Key, "/") {
|
||||||
ctx.Logger().V(5).Info("Skipping directory")
|
ctx.Logger().V(5).Info("Skipping directory")
|
||||||
|
s.metricsCollector.RecordObjectSkipped(metadata.bucket, "directory")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -496,8 +506,12 @@ func (s *Source) pageChunker(
|
||||||
Key: obj.Key,
|
Key: obj.Key,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !strings.Contains(err.Error(), "AccessDenied") {
|
if strings.Contains(err.Error(), "AccessDenied") {
|
||||||
|
ctx.Logger().Error(err, "could not get S3 object; access denied")
|
||||||
|
s.metricsCollector.RecordObjectSkipped(metadata.bucket, "access_denied")
|
||||||
|
} else {
|
||||||
ctx.Logger().Error(err, "could not get S3 object")
|
ctx.Logger().Error(err, "could not get S3 object")
|
||||||
|
s.metricsCollector.RecordObjectError(metadata.bucket)
|
||||||
}
|
}
|
||||||
// According to the documentation for GetObjectWithContext,
|
// According to the documentation for GetObjectWithContext,
|
||||||
// the response can be non-nil even if there was an error.
|
// the response can be non-nil even if there was an error.
|
||||||
|
@ -551,6 +565,7 @@ func (s *Source) pageChunker(
|
||||||
|
|
||||||
if err := handlers.HandleFile(ctx, res.Body, chunkSkel, sources.ChanReporter{Ch: chunksChan}); err != nil {
|
if err := handlers.HandleFile(ctx, res.Body, chunkSkel, sources.ChanReporter{Ch: chunksChan}); err != nil {
|
||||||
ctx.Logger().Error(err, "error handling file")
|
ctx.Logger().Error(err, "error handling file")
|
||||||
|
s.metricsCollector.RecordObjectError(metadata.bucket)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,6 +583,7 @@ func (s *Source) pageChunker(
|
||||||
if err := s.progressTracker.UpdateObjectProgress(ctx, objIdx, metadata.bucket, metadata.page.Contents); err != nil {
|
if err := s.progressTracker.UpdateObjectProgress(ctx, objIdx, metadata.bucket, metadata.page.Contents); err != nil {
|
||||||
ctx.Logger().Error(err, "could not update progress for scanned object")
|
ctx.Logger().Error(err, "could not update progress for scanned object")
|
||||||
}
|
}
|
||||||
|
s.metricsCollector.RecordObjectScanned(metadata.bucket)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
@ -629,6 +645,8 @@ func (s *Source) visitRoles(
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
|
s.metricsCollector.RecordRoleScanned(role)
|
||||||
|
|
||||||
client, err := s.newClient(defaultAWSRegion, role)
|
client, err := s.newClient(defaultAWSRegion, role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not create s3 client: %w", err)
|
return fmt.Errorf("could not create s3 client: %w", err)
|
||||||
|
|
Loading…
Reference in a new issue