Return targeted scan errors (#2995)

Targeted scans should return their errors so that consumers can process them. By creating a type that combines an error with a targeted secret ID, we can return these errors without having to modify the Source interface.
This commit is contained in:
Cody Rose
2024-06-21 13:50:56 -04:00
committed by GitHub
parent a150103e2b
commit de19a39f2c
3 changed files with 51 additions and 3 deletions
+17
View File
@@ -57,3 +57,20 @@ func (s *ScanErrors) Errors() error {
defer s.mu.RUnlock()
return errors.Join(s.errors...)
}
// TargetedScanError is an error with a secret ID attached. Collections of them can be returned by targeted scans that
// scan multiple targets in order to associate individual errors with individual scan targets.
type TargetedScanError struct {
Err error
SecretID int64
}
var _ error = (*TargetedScanError)(nil)
func (t TargetedScanError) Error() string {
return t.Err.Error()
}
func (t TargetedScanError) Unwrap() error {
return t.Err
}
+6 -3
View File
@@ -361,7 +361,8 @@ func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, tar
// Otherwise, we're scanning all data.
// This allows us to only scan the commit where a vulnerability was found.
if len(targets) > 0 {
return s.scanTargets(ctx, targets, chunksChan)
errs := s.scanTargets(ctx, targets, chunksChan)
return errors.Join(errs...)
}
// Reset consumption and rate limit metrics on each run.
@@ -1514,14 +1515,16 @@ func (s *Source) chunkPullRequestComments(ctx context.Context, repoInfo repoInfo
return nil
}
func (s *Source) scanTargets(ctx context.Context, targets []sources.ChunkingTarget, chunksChan chan *sources.Chunk) error {
func (s *Source) scanTargets(ctx context.Context, targets []sources.ChunkingTarget, chunksChan chan *sources.Chunk) []error {
var errs []error
for _, tgt := range targets {
if err := s.scanTarget(ctx, tgt, chunksChan); err != nil {
ctx.Logger().Error(err, "error scanning target")
errs = append(errs, &sources.TargetedScanError{Err: err, SecretID: tgt.SecretID})
}
}
return nil
return errs
}
func (s *Source) scanTarget(ctx context.Context, target sources.ChunkingTarget, chunksChan chan *sources.Chunk) error {
+28
View File
@@ -6,6 +6,7 @@ import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"net/http"
"net/url"
@@ -18,6 +19,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-github/v62/github"
"github.com/stretchr/testify/assert"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
"golang.org/x/sync/errgroup"
"google.golang.org/protobuf/types/known/anypb"
"gopkg.in/h2non/gock.v1"
@@ -844,3 +846,29 @@ func TestGetGistID(t *testing.T) {
assert.Equal(t, tt.expected, got)
}
}
// This isn't really a GitHub test, but GitHub is the only source that supports scan targeting right now, so this is
// where I've put this targeted scan test.
func Test_ScanMultipleTargets_MultipleErrors(t *testing.T) {
s := &Source{conn: &sourcespb.GitHub{}} // This test doesn't require initialization
ctx := context.Background()
chunksChan := make(chan *sources.Chunk)
targets := []sources.ChunkingTarget{
{SecretID: 1},
{SecretID: 2},
}
// The specific error text doesn't matter for the test, but it has to match what the source generates
want := []*sources.TargetedScanError{
{SecretID: 1, Err: errors.New("unable to cast metadata type for targeted scan")},
{SecretID: 2, Err: errors.New("unable to cast metadata type for targeted scan")},
}
err := s.Chunks(ctx, chunksChan, targets...)
unwrappable, ok := err.(interface{ Unwrap() []error })
if assert.True(t, ok, "returned error was not unwrappable") {
got := unwrappable.Unwrap()
assert.ElementsMatch(t, got, want)
}
}