diff --git a/pkg/detectors/artifactory/artifactory.go b/pkg/detectors/artifactory/artifactory.go index 856d90fa4..6b29b1d46 100644 --- a/pkg/detectors/artifactory/artifactory.go +++ b/pkg/detectors/artifactory/artifactory.go @@ -29,7 +29,7 @@ var ( defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. - keyPat = regexp.MustCompile(`\b([a-zA-Z0-9]{64,73})\b`) + keyPat = regexp.MustCompile(`\b(AKCp[a-zA-Z0-9]{69})\b`) URLPat = regexp.MustCompile(`\b([A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]\.jfrog\.io)`) invalidHosts = simple.NewCache[struct{}]() @@ -42,7 +42,7 @@ func (Scanner) CloudEndpoint() string { return "" } // Keywords are used for efficiently pre-filtering chunks. // Use identifiers in the secret preferably, or the provider name. func (s Scanner) Keywords() []string { - return []string{"artifactory", "jfrog.io"} + return []string{"artifactory", "jfrog.io", "AKCp"} } func (s Scanner) getClient() *http.Client { diff --git a/pkg/detectors/artifactory/artifactory_integration_test.go b/pkg/detectors/artifactory/artifactory_integration_test.go index 8dcccda9c..a06a77828 100644 --- a/pkg/detectors/artifactory/artifactory_integration_test.go +++ b/pkg/detectors/artifactory/artifactory_integration_test.go @@ -7,26 +7,21 @@ import ( "context" "fmt" "testing" - "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" - "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) func TestArtifactory_FromChunk(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() - testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") - if err != nil { - t.Fatalf("could not get test secrets from GCP: %s", err) - } - secret := testSecrets.MustGetField("ARTIFACTORY_TOKEN") - inactiveSecret := testSecrets.MustGetField("ARTIFACTORY_INACTIVE") - appURL := testSecrets.MustGetField("ARTIFACTORY_URL") + // NOTE: Using mock secrets because JFrog deprecated AKCp API keys (disabled creation end of Q3 2024). + // Real AKCp keys can no longer be generated, so we cannot test actual verification scenarios. + // These mock keys follow the correct format: AKCp + 69 alphanumeric characters = 73 total + // Reference: https://jfrog.com/help/r/jfrog-release-information/artifactory-7.47.10-cloud-self-hosted + mockSecret := "AKCp5bueTFpfypEqQbGJPp7eHFi28fBivfWczrjbPb9erDff9LbXZbj6UsRExVXA8asWGc9fM" + appURL := "trufflehog.jfrog.io" type args struct { ctx context.Context @@ -41,28 +36,12 @@ func TestArtifactory_FromChunk(t *testing.T) { wantErr bool }{ { - name: "found, verified", + name: "found, unverified - mock key (cannot verify deprecated AKCp format)", s: Scanner{}, args: args{ ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a artifactory secret %s and domain %s", secret, appURL)), - verify: true, - }, - want: []detectors.Result{ - { - DetectorType: detectorspb.DetectorType_ArtifactoryAccessToken, - Verified: true, - }, - }, - wantErr: false, - }, - { - name: "found, unverified", - s: Scanner{}, - args: args{ - ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a artifactory secret %s but not valid on endpoint %s", inactiveSecret, appURL)), // the secret would satisfy the regex but not pass validation - verify: true, + data: []byte(fmt.Sprintf("You can find a artifactory secret %s and domain %s", mockSecret, appURL)), + verify: false, // Cannot verify - AKCp API keys are deprecated and no valid keys available }, want: []detectors.Result{ { diff --git a/pkg/detectors/artifactory/artifactory_test.go b/pkg/detectors/artifactory/artifactory_test.go index 071d89ffd..6c9eed790 100644 --- a/pkg/detectors/artifactory/artifactory_test.go +++ b/pkg/detectors/artifactory/artifactory_test.go @@ -27,13 +27,16 @@ func TestArtifactory_Pattern(t *testing.T) { name: "valid pattern", input: ` [INFO] Sending request to the artifactory API - [DEBUG] Using Key=cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZg + [DEBUG] Using Key=AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE [INFO] rwxtOp.jfrog.io [INFO] Response received: 200 OK `, useCloudEndpoint: false, useFoundEndpoint: true, - want: []string{"cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZgrwxtOp.jfrog.io"}, + want: []string{ + "AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE" + + "rwxtOp.jfrog.io", + }, }, { name: "valid pattern - xml", @@ -41,7 +44,7 @@ func TestArtifactory_Pattern(t *testing.T) { GLOBAL {artifactory} - {AQAAABAAA KUd8GOVfcXnIv1nJ5qmnNzrqkLvseoPRMuwsdDVr9QthonFogtMaoJ3pgtO4eHXC} + AKCp8budTFpbypBqQbGJPp7eHFi28fBivfWczrjbPb9erDff9LbXZbj6UsRExVXA8asWGc9fM {HTTPnGQZ79vjWXze.jfrog.io} configuration for production 2023-05-18T14:32:10Z @@ -50,25 +53,31 @@ func TestArtifactory_Pattern(t *testing.T) { `, useCloudEndpoint: false, useFoundEndpoint: true, - want: []string{"KUd8GOVfcXnIv1nJ5qmnNzrqkLvseoPRMuwsdDVr9QthonFogtMaoJ3pgtO4eHXCHTTPnGQZ79vjWXze.jfrog.io"}, + want: []string{ + "AKCp8budTFpbypBqQbGJPp7eHFi28fBivfWczrjbPb9erDff9LbXZbj6UsRExVXA8asWGc9fM" + + "HTTPnGQZ79vjWXze.jfrog.io", + }, }, { name: "valid pattern - with cloud endpoints", input: ` [INFO] Sending request to the artifactory API - [DEBUG] Using Key=cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZg + [DEBUG] Using Key=AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE [INFO] Response received: 200 OK `, cloudEndpoint: "cloudendpoint.jfrog.io", useCloudEndpoint: true, useFoundEndpoint: false, - want: []string{"cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZgcloudendpoint.jfrog.io"}, + want: []string{ + "AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE" + + "cloudendpoint.jfrog.io", + }, }, { name: "valid pattern - with cloud and found endpoints", input: ` [INFO] Sending request to the artifactory API - [DEBUG] Using Key=cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZg + [DEBUG] Using Key=AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE [INFO] rwxtOp.jfrog.io [INFO] Response received: 200 OK `, @@ -76,15 +85,17 @@ func TestArtifactory_Pattern(t *testing.T) { useCloudEndpoint: true, useFoundEndpoint: true, want: []string{ - "cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZgcloudendpoint.jfrog.io", - "cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZgrwxtOp.jfrog.io", + "AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE" + + "cloudendpoint.jfrog.io", + "AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE" + + "rwxtOp.jfrog.io", }, }, { name: "valid pattern - with disabled found endpoints", input: ` [INFO] Sending request to the artifactory API - [DEBUG] Using Key=cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZg + [DEBUG] Using Key=AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE [INFO] rwxtOp.jfrog.io [INFO] Response received: 200 OK `, @@ -92,28 +103,41 @@ func TestArtifactory_Pattern(t *testing.T) { useCloudEndpoint: true, useFoundEndpoint: false, want: []string{ - "cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZgcloudendpoint.jfrog.io", + "AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE" + + "cloudendpoint.jfrog.io", }, }, { name: "valid pattern - with https in configured endpoint", input: ` [INFO] Sending request to the artifactory API - [DEBUG] Using Key=cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZg + [DEBUG] Using Key=AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE [INFO] Response received: 200 OK `, cloudEndpoint: "https://cloudendpoint.jfrog.io", useCloudEndpoint: true, useFoundEndpoint: false, want: []string{ - "cmVmdGtuOjAxOjE3ODA1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZgcloudendpoint.jfrog.io", + "AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE" + + "cloudendpoint.jfrog.io", }, }, { - name: "invalid pattern", + name: "invalid pattern - wrong prefix", input: ` [INFO] Sending request to the artifactory API - [DEBUG] Using Key=cmVmdGtuOjAxOjEODA_1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZg + [DEBUG] Using Key=XYZp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE + [INFO] rwxtOp.jfrog.io + [INFO] Response received: 200 OK + `, + useFoundEndpoint: true, + want: nil, + }, + { + name: "invalid pattern - too short", + input: ` + [INFO] Sending request to the artifactory API + [DEBUG] Using Key=AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmd [INFO] rwxtOp.jfrog.io [INFO] Response received: 200 OK `, @@ -124,10 +148,10 @@ func TestArtifactory_Pattern(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - // this detector use endpoint customizer interface so we need to enable them based on test case + // this detector uses endpoint customizer interface so we need to enable them based on test case d.UseFoundEndpoints(test.useFoundEndpoint) d.UseCloudEndpoint(test.useCloudEndpoint) - // if test case provide cloud endpoint use it + // if the test case provides cloud endpoint, then use it if test.useCloudEndpoint && test.cloudEndpoint != "" { d.SetCloudEndpoint(test.cloudEndpoint) }