Ignore canary IDs in notifications (#2526)

* Update aws.go

* Update aws.go

* Update tests

---------

Co-authored-by: Dustin Decker <dustin@trufflesec.com>
This commit is contained in:
Dylan Ayrey 2024-02-28 16:52:50 -08:00 committed by GitHub
parent f0397fed8f
commit 7620906b07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 42 additions and 18 deletions

View file

@ -21,6 +21,7 @@ import (
type scanner struct { type scanner struct {
verificationClient *http.Client verificationClient *http.Client
skipIDs map[string]struct{} skipIDs map[string]struct{}
verifyCanaries bool
} }
// resourceTypes derived from: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids // resourceTypes derived from: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids
@ -95,6 +96,12 @@ func WithSkipIDs(skipIDs []string) func(*scanner) {
} }
} }
func WithVerifyCanaries() func(*scanner) {
return func(s *scanner) {
s.verifyCanaries = true
}
}
// Ensure the scanner satisfies the interface at compile time. // Ensure the scanner satisfies the interface at compile time.
var _ detectors.Detector = (*scanner)(nil) var _ detectors.Detector = (*scanner)(nil)
@ -173,15 +180,17 @@ func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (result
if err == nil { if err == nil {
s1.ExtraData["account"] = account s1.ExtraData["account"] = account
} }
if _, ok := thinkstCanaryList[account]; ok { if !s.verifyCanaries && !strings.Contains(dataStr, "Redacted: "+resIDMatch) {
s1.ExtraData["is_canary"] = "true" if _, ok := thinkstCanaryList[account]; ok {
s1.ExtraData["message"] = thinkstMessage s1.ExtraData["is_canary"] = "true"
s1.Verified = true s1.ExtraData["message"] = thinkstMessage
} s1.Verified = true
if _, ok := thinkstKnockoffsCanaryList[account]; ok { }
s1.ExtraData["is_canary"] = "true" if _, ok := thinkstKnockoffsCanaryList[account]; ok {
s1.ExtraData["message"] = thinkstKnockoffsMessage s1.ExtraData["is_canary"] = "true"
s1.Verified = true s1.ExtraData["message"] = thinkstKnockoffsMessage
s1.Verified = true
}
} }
if verify && !s1.Verified { if verify && !s1.Verified {

View file

@ -49,7 +49,7 @@ func TestAWS_FromChunk(t *testing.T) {
}{ }{
{ {
name: "found, verified", name: "found, verified",
s: scanner{}, s: scanner{verifyCanaries: true},
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s", secret, id)), data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s", secret, id)),
@ -73,7 +73,7 @@ func TestAWS_FromChunk(t *testing.T) {
}, },
{ {
name: "found, unverified", name: "found, unverified",
s: scanner{verificationClient: unverifiedSecretClient}, s: scanner{verificationClient: unverifiedSecretClient, verifyCanaries: true},
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation
@ -105,7 +105,7 @@ func TestAWS_FromChunk(t *testing.T) {
}, },
{ {
name: "found two, one included for every ID found", name: "found two, one included for every ID found",
s: scanner{}, s: scanner{verifyCanaries: true},
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
data: []byte(fmt.Sprintf("The verified ID is %s with a secret of %s, but the unverified ID is %s and this is the secret %s", id, secret, inactiveID, inactiveSecret)), data: []byte(fmt.Sprintf("The verified ID is %s with a secret of %s, but the unverified ID is %s and this is the secret %s", id, secret, inactiveID, inactiveSecret)),
@ -149,7 +149,9 @@ func TestAWS_FromChunk(t *testing.T) {
}, },
{ {
name: "found two, returned both because the active secret for one paired with the inactive ID, despite the hash", name: "found two, returned both because the active secret for one paired with the inactive ID, despite the hash",
s: scanner{}, s: scanner{
verifyCanaries: true,
},
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
data: []byte(fmt.Sprintf("The verified ID is %s with a secret of %s, but the unverified ID is %s and the secret is this hash %s", id, secret, inactiveID, hash)), data: []byte(fmt.Sprintf("The verified ID is %s with a secret of %s, but the unverified ID is %s and the secret is this hash %s", id, secret, inactiveID, hash)),
@ -172,13 +174,17 @@ func TestAWS_FromChunk(t *testing.T) {
DetectorType: detectorspb.DetectorType_AWS, DetectorType: detectorspb.DetectorType_AWS,
Verified: false, Verified: false,
Redacted: inactiveID, Redacted: inactiveID,
ExtraData: map[string]string{"account": "171436882533", "resource_type": "Access key"},
}, },
}, },
wantErr: false, wantErr: false,
}, },
{ {
name: "found, unverified, with leading +", name: "found, unverified, with leading +",
s: scanner{verificationClient: unverifiedSecretClient}, s: scanner{
verificationClient: unverifiedSecretClient,
verifyCanaries: true,
},
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s but not valid", "+HaNv9cTwheDKGJaws/+BMF2GgybQgBWdhcOOdfF", id)), // the secret would satisfy the regex but not pass validation data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s but not valid", "+HaNv9cTwheDKGJaws/+BMF2GgybQgBWdhcOOdfF", id)), // the secret would satisfy the regex but not pass validation
@ -213,7 +219,10 @@ func TestAWS_FromChunk(t *testing.T) {
}, },
{ {
name: "found, would be verified if not for http timeout", name: "found, would be verified if not for http timeout",
s: scanner{verificationClient: common.SaneHttpClientTimeOut(1 * time.Microsecond)}, s: scanner{
verificationClient: common.SaneHttpClientTimeOut(1 * time.Microsecond),
verifyCanaries: true,
},
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s", secret, id)), data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s", secret, id)),
@ -235,7 +244,10 @@ func TestAWS_FromChunk(t *testing.T) {
}, },
{ {
name: "found, unverified due to unexpected http response status", name: "found, unverified due to unexpected http response status",
s: scanner{verificationClient: common.ConstantResponseHttpClient(500, "internal server error")}, s: scanner{
verificationClient: common.ConstantResponseHttpClient(500, "internal server error"),
verifyCanaries: true,
},
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s", secret, id)), data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s", secret, id)),
@ -257,7 +269,10 @@ func TestAWS_FromChunk(t *testing.T) {
}, },
{ {
name: "found, unverified due to unexpected 403 response reason", name: "found, unverified due to unexpected 403 response reason",
s: scanner{verificationClient: common.ConstantResponseHttpClient(403, `{"Error": {"Code": "SignatureDoesNotMatch"} }`)}, s: scanner{
verificationClient: common.ConstantResponseHttpClient(403, `{"Error": {"Code": "SignatureDoesNotMatch"} }`),
verifyCanaries: true,
},
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s", secret, id)), data: []byte(fmt.Sprintf("You can find a aws secret %s within aws %s", secret, id)),
@ -279,7 +294,7 @@ func TestAWS_FromChunk(t *testing.T) {
}, },
{ {
name: "verified secret checked directly after unverified secret with same key id", name: "verified secret checked directly after unverified secret with same key id",
s: scanner{}, s: scanner{verifyCanaries: true},
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
data: []byte(fmt.Sprintf("%s\n%s\n%s", inactiveSecret, id, secret)), data: []byte(fmt.Sprintf("%s\n%s\n%s", inactiveSecret, id, secret)),