Support multiple custom detectors (#2064)

#1711 accidentally removed the ability to support multiple custom detectors. This PR partially adds back this capability: Multiple custom detectors are now supported overall, but only one custom detector can be returned for a given keyword match.
This commit is contained in:
Cody Rose
2023-10-30 18:17:17 -04:00
committed by GitHub
parent de4a14b3f9
commit e58a2913ea
4 changed files with 72 additions and 16 deletions
+10 -10
View File
@@ -20,20 +20,20 @@ import (
// for poorly defined regexps. // for poorly defined regexps.
const maxTotalMatches = 100 const maxTotalMatches = 100
// customRegexWebhook is a CustomRegex with webhook validation that is // CustomRegexWebhook is a CustomRegex with webhook validation that is
// guaranteed to be valid (assuming the data is not changed after // guaranteed to be valid (assuming the data is not changed after
// initialization). // initialization).
type customRegexWebhook struct { type CustomRegexWebhook struct {
*custom_detectorspb.CustomRegex *custom_detectorspb.CustomRegex
} }
// Ensure the Scanner satisfies the interface at compile time. // Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*customRegexWebhook)(nil) var _ detectors.Detector = (*CustomRegexWebhook)(nil)
// NewWebhookCustomRegex initializes and validates a customRegexWebhook. An // NewWebhookCustomRegex initializes and validates a CustomRegexWebhook. An
// unexported type is intentionally returned here to ensure the values have // unexported type is intentionally returned here to ensure the values have
// been validated. // been validated.
func NewWebhookCustomRegex(pb *custom_detectorspb.CustomRegex) (*customRegexWebhook, error) { func NewWebhookCustomRegex(pb *custom_detectorspb.CustomRegex) (*CustomRegexWebhook, error) {
// TODO: Return all validation errors. // TODO: Return all validation errors.
if err := ValidateKeywords(pb.Keywords); err != nil { if err := ValidateKeywords(pb.Keywords); err != nil {
return nil, err return nil, err
@@ -52,12 +52,12 @@ func NewWebhookCustomRegex(pb *custom_detectorspb.CustomRegex) (*customRegexWebh
} }
// TODO: Copy only necessary data out of pb. // TODO: Copy only necessary data out of pb.
return &customRegexWebhook{pb}, nil return &CustomRegexWebhook{pb}, nil
} }
var httpClient = common.SaneHttpClient() var httpClient = common.SaneHttpClient()
func (c *customRegexWebhook) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { func (c *CustomRegexWebhook) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data) dataStr := string(data)
regexMatches := make(map[string][][]string, len(c.GetRegex())) regexMatches := make(map[string][][]string, len(c.GetRegex()))
@@ -109,7 +109,7 @@ func (c *customRegexWebhook) FromData(ctx context.Context, verify bool, data []b
return results, nil return results, nil
} }
func (c *customRegexWebhook) createResults(ctx context.Context, match map[string][]string, verify bool, results chan<- detectors.Result) error { func (c *CustomRegexWebhook) createResults(ctx context.Context, match map[string][]string, verify bool, results chan<- detectors.Result) error {
if common.IsDone(ctx) { if common.IsDone(ctx) {
// TODO: Log we're possibly leaving out results. // TODO: Log we're possibly leaving out results.
return ctx.Err() return ctx.Err()
@@ -180,7 +180,7 @@ func (c *customRegexWebhook) createResults(ctx context.Context, match map[string
} }
} }
func (c *customRegexWebhook) Keywords() []string { func (c *CustomRegexWebhook) Keywords() []string {
return c.GetKeywords() return c.GetKeywords()
} }
@@ -245,6 +245,6 @@ func permutateMatches(regexMatches map[string][][]string) []map[string][]string
return matches return matches
} }
func (c *customRegexWebhook) Type() detectorspb.DetectorType { func (c *CustomRegexWebhook) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_CustomRegex return detectorspb.DetectorType_CustomRegex
} }
@@ -105,7 +105,7 @@ func TestCustomDetectorsParsing(t *testing.T) {
} }
func TestFromData_InvalidRegEx(t *testing.T) { func TestFromData_InvalidRegEx(t *testing.T) {
c := &customRegexWebhook{ c := &CustomRegexWebhook{
&custom_detectorspb.CustomRegex{ &custom_detectorspb.CustomRegex{
Name: "Internal bi tool", Name: "Internal bi tool",
Keywords: []string{"secret_v1_", "pat_v2_"}, Keywords: []string{"secret_v1_", "pat_v2_"},
+12 -5
View File
@@ -4,17 +4,20 @@ import (
"strings" "strings"
ahocorasick "github.com/BobuSumisu/aho-corasick" ahocorasick "github.com/BobuSumisu/aho-corasick"
"github.com/trufflesecurity/trufflehog/v3/pkg/custom_detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
) )
// detectorKey is used to identify a detector in the keywordsToDetectors map. // detectorKey is used to identify a detector in the keywordsToDetectors map.
// Multiple detectors can have the same detector type but different versions. // Multiple detectors can have the same detector type but different versions.
// This allows us to identify a detector by its type and version. // This allows us to identify a detector by its type and version. An
// additional (optional) field is provided to disambiguate multiple custom
// detectors.
type detectorKey struct { type detectorKey struct {
detectorType detectorspb.DetectorType detectorType detectorspb.DetectorType
version int version int
customDetectorName string
} }
// AhoCorasickCore encapsulates the operations and data structures used for keyword matching via the // AhoCorasickCore encapsulates the operations and data structures used for keyword matching via the
@@ -85,5 +88,9 @@ func createDetectorKey(d detectors.Detector) detectorKey {
if v, ok := d.(detectors.Versioner); ok { if v, ok := d.(detectors.Versioner); ok {
version = v.Version() version = v.Version()
} }
return detectorKey{detectorType: detectorType, version: version} var customDetectorName string
if r, ok := d.(*custom_detectors.CustomRegexWebhook); ok {
customDetectorName = r.GetName()
}
return detectorKey{detectorType: detectorType, version: version, customDetectorName: customDetectorName}
} }
+49
View File
@@ -5,7 +5,9 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/trufflesecurity/trufflehog/v3/pkg/custom_detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/custom_detectorspb"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
) )
@@ -54,6 +56,53 @@ var _ detectors.Detector = (*testDetectorV2)(nil)
var _ detectors.Versioner = (*testDetectorV1)(nil) var _ detectors.Versioner = (*testDetectorV1)(nil)
var _ detectors.Versioner = (*testDetectorV2)(nil) var _ detectors.Versioner = (*testDetectorV2)(nil)
func TestAhoCorasickCore_MultipleCustomDetectorsMatchable(t *testing.T) {
customDetector1, err := custom_detectors.NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{
Name: "custom detector 1",
Keywords: []string{"a"},
Regex: map[string]string{"": ""},
})
assert.Nil(t, err)
customDetector2, err := custom_detectors.NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{
Name: "custom detector 2",
Keywords: []string{"b"},
Regex: map[string]string{"": ""},
})
assert.Nil(t, err)
testCases := []struct {
matchString string
detector detectors.Detector
}{
{
matchString: "a",
detector: customDetector1,
},
{
matchString: "b",
detector: customDetector2,
},
}
var allDetectors []detectors.Detector
for _, tt := range testCases {
allDetectors = append(allDetectors, tt.detector)
}
ac := NewAhoCorasickCore(allDetectors)
for _, tt := range testCases {
matches := ac.MatchString(tt.matchString)
assert.Equal(t, 1, len(matches))
matchingDetectors := make(map[detectorspb.DetectorType]detectors.Detector)
ac.PopulateDetectorsByMatch(matches[0], matchingDetectors)
assert.Equal(t, 1, len(matchingDetectors))
assert.Equal(t, tt.detector, matchingDetectors[detectorspb.DetectorType_CustomRegex])
}
}
func TestAhoCorasickCore_MultipleDetectorVersionsMatchable(t *testing.T) { func TestAhoCorasickCore_MultipleDetectorVersionsMatchable(t *testing.T) {
testCases := []struct { testCases := []struct {
matchString string matchString string