Skip to content

Commit

Permalink
Return targeted scan errors (#2995)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rosecodym committed Jun 21, 2024
1 parent a150103 commit de19a39
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 3 deletions.
17 changes: 17 additions & 0 deletions pkg/sources/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
9 changes: 6 additions & 3 deletions pkg/sources/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
28 changes: 28 additions & 0 deletions pkg/sources/github/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"net/http"
"net/url"
Expand All @@ -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"
Expand Down Expand Up @@ -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)
}
}

0 comments on commit de19a39

Please sign in to comment.