Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkg/detectors/artifactory/artifactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}]()
Expand All @@ -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 {
Expand Down
39 changes: 9 additions & 30 deletions pkg/detectors/artifactory/artifactory_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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{
{
Expand Down
58 changes: 41 additions & 17 deletions pkg/detectors/artifactory/artifactory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,24 @@ 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",
input: `
<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>
<scope>GLOBAL</scope>
<id>{artifactory}</id>
<secret>{AQAAABAAA KUd8GOVfcXnIv1nJ5qmnNzrqkLvseoPRMuwsdDVr9QthonFogtMaoJ3pgtO4eHXC}</secret>
<secret>AKCp8budTFpbypBqQbGJPp7eHFi28fBivfWczrjbPb9erDff9LbXZbj6UsRExVXA8asWGc9fM</secret>
<domain>{HTTPnGQZ79vjWXze.jfrog.io}</domain>
<description>configuration for production</description>
<creationDate>2023-05-18T14:32:10Z</creationDate>
Expand All @@ -50,70 +53,91 @@ 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
`,
cloudEndpoint: "cloudendpoint.jfrog.io",
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
`,
cloudEndpoint: "cloudendpoint.jfrog.io",
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=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=cmVmdGtuOjAxOjEODA_1NTFAKEM6S2J2MGswemNzZzhaRnFlVUFAKEk3amlLcGZg
[DEBUG] Using Key=AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmd
[INFO] rwxtOp.jfrog.io
[INFO] Response received: 200 OK
`,
Expand All @@ -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)
}
Expand Down
Loading