Skip to content

Commit c18f08c

Browse files
committed
feat(azure_search): update logic
1 parent ef3a714 commit c18f08c

File tree

2 files changed

+99
-60
lines changed

2 files changed

+99
-60
lines changed

pkg/detectors/azuresearchadminkey/azuresearchadminkey.go

+63-41
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package azuresearchadminkey
33
import (
44
"context"
55
"fmt"
6+
"io"
67
"net/http"
7-
"strings"
88

99
regexp "github.com/wasilibs/go-re2"
1010

@@ -23,73 +23,95 @@ var _ detectors.Detector = (*Scanner)(nil)
2323

2424
var (
2525
defaultClient = common.SaneHttpClient()
26-
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
27-
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"azure"}) + `\b([0-9a-zA-Z]{52})\b`)
28-
servicePat = regexp.MustCompile(detectors.PrefixRegex([]string{"azure"}) + `\b([0-9a-zA-Z]{7,40})\b`)
26+
27+
servicePat = regexp.MustCompile(`\b([a-z0-9][a-z0-9-]{5,58}[a-z0-9])\.search\.windows\.net`)
28+
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"azure", "windows.net"}) + `\b([a-zA-Z0-9]{52})\b`)
2929
)
3030

3131
// Keywords are used for efficiently pre-filtering chunks.
3232
// Use identifiers in the secret preferably, or the provider name.
3333
func (s Scanner) Keywords() []string {
34-
return []string{"azure"}
34+
return []string{"search.windows.net"}
35+
}
36+
37+
func (s Scanner) Type() detectorspb.DetectorType {
38+
return detectorspb.DetectorType_AzureSearchAdminKey
39+
}
40+
41+
func (s Scanner) Description() string {
42+
return "Azure Search is a search-as-a-service solution that allows developers to incorporate search capabilities into their applications. Azure Search Admin Keys can be used to manage and query search services."
3543
}
3644

3745
// FromData will find and optionally verify AzureSearchAdminKey secrets in a given set of bytes.
3846
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
3947
dataStr := string(data)
4048

41-
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
42-
serviceMatches := servicePat.FindAllStringSubmatch(dataStr, -1)
43-
44-
for _, match := range matches {
45-
resMatch := strings.TrimSpace(match[1])
46-
for _, serviceMatch := range serviceMatches {
47-
resServiceMatch := strings.TrimSpace(serviceMatch[1])
49+
// Deduplicate results.
50+
serviceMatches := make(map[string]struct{})
51+
for _, matches := range servicePat.FindAllStringSubmatch(dataStr, -1) {
52+
serviceMatches[matches[1]] = struct{}{}
53+
}
54+
keyMatches := make(map[string]struct{})
55+
for _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {
56+
k := matches[1]
57+
if detectors.StringShannonEntropy(k) < 4 {
58+
continue
59+
}
60+
keyMatches[k] = struct{}{}
61+
}
4862

49-
s1 := detectors.Result{
63+
for key := range keyMatches {
64+
for service := range serviceMatches {
65+
r := detectors.Result{
5066
DetectorType: detectorspb.DetectorType_AzureSearchAdminKey,
51-
Raw: []byte(resMatch),
52-
RawV2: []byte(resMatch + resServiceMatch),
67+
Raw: []byte(key),
68+
RawV2: []byte(`{"service":"` + service + `","key":"` + key + `"}`),
5369
}
5470

5571
if verify {
5672
client := s.client
5773
if client == nil {
5874
client = defaultClient
5975
}
60-
req, err := http.NewRequestWithContext(ctx, "GET", "https://"+resServiceMatch+".search.windows.net/servicestats?api-version=2023-10-01-Preview", nil)
61-
if err != nil {
62-
continue
63-
}
64-
req.Header.Add("api-key", resMatch)
65-
66-
res, err := client.Do(req)
67-
if err == nil {
68-
defer res.Body.Close()
69-
if res.StatusCode >= 200 && res.StatusCode < 300 {
70-
s1.Verified = true
71-
} else if res.StatusCode == 401 || res.StatusCode == 403 {
72-
// The secret is determinately not verified (nothing to do)
73-
} else {
74-
err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
75-
s1.SetVerificationError(err, resMatch)
76-
}
77-
} else {
78-
s1.SetVerificationError(err, resMatch)
79-
}
76+
77+
isVerified, verificationErr := verifyMatch(ctx, client, service, key)
78+
r.Verified = isVerified
79+
r.SetVerificationError(verificationErr, key)
8080
}
8181

82-
results = append(results, s1)
82+
results = append(results, r)
83+
if r.Verified {
84+
break
85+
}
8386
}
8487
}
8588

8689
return results, nil
8790
}
8891

89-
func (s Scanner) Type() detectorspb.DetectorType {
90-
return detectorspb.DetectorType_AzureSearchAdminKey
91-
}
92+
func verifyMatch(ctx context.Context, client *http.Client, service string, key string) (bool, error) {
93+
req, err := http.NewRequestWithContext(ctx, "GET", "https://"+service+".search.windows.net/servicestats?api-version=2023-10-01-Preview", nil)
94+
if err != nil {
95+
return false, err
96+
}
97+
req.Header.Set("api-key", key)
9298

93-
func (s Scanner) Description() string {
94-
return "Azure Search is a search-as-a-service solution that allows developers to incorporate search capabilities into their applications. Azure Search Admin Keys can be used to manage and query search services."
99+
res, err := client.Do(req)
100+
if err != nil {
101+
return false, err
102+
}
103+
defer func() {
104+
_, _ = io.Copy(io.Discard, res.Body)
105+
_ = res.Body.Close()
106+
}()
107+
108+
switch res.StatusCode {
109+
case http.StatusOK:
110+
return true, nil
111+
case http.StatusUnauthorized, http.StatusForbidden:
112+
// The secret is determinately not verified.
113+
return false, nil
114+
default:
115+
return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
116+
}
95117
}

pkg/detectors/azuresearchadminkey/azuresearchadminkey_test.go

+36-19
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,6 @@ import (
1010
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
1111
)
1212

13-
var (
14-
validPattern = `
15-
azure:
16-
azureKey: wRRPyhjv8m6JGRujUUrPKa8d3rJ0mrGAxhmqf3A68OgZmlWUJyma
17-
azureService: TestingService01
18-
`
19-
invalidPattern = `
20-
azure:
21-
Key: wRRPyhjv8m6JGRujUUr-PK#a8d3rJ0mrGAxhmqf3A68OgZmlWUJyma
22-
Service: TS01
23-
`
24-
)
25-
2613
func TestAzureSearchAdminKey_Pattern(t *testing.T) {
2714
d := Scanner{}
2815
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
@@ -33,14 +20,44 @@ func TestAzureSearchAdminKey_Pattern(t *testing.T) {
3320
want []string
3421
}{
3522
{
36-
name: "valid pattern",
37-
input: validPattern,
38-
want: []string{"wRRPyhjv8m6JGRujUUrPKa8d3rJ0mrGAxhmqf3A68OgZmlWUJymaTestingService01", "wRRPyhjv8m6JGRujUUrPKa8d3rJ0mrGAxhmqf3A68OgZmlWUJymaazureKey"},
23+
name: "valid pattern",
24+
input: `
25+
azure:
26+
azureKey: wRRPyhjv8m6JGRujUUrPKa8d3rJ0mrGAxhmqf3A68OgZmlWUJyma
27+
azureService: testingservice01.search.windows.net
28+
`,
29+
want: []string{`{"service":"testingservice01","key":"wRRPyhjv8m6JGRujUUrPKa8d3rJ0mrGAxhmqf3A68OgZmlWUJyma"}`},
30+
},
31+
{
32+
name: "jupyter notebook",
33+
input: `
34+
{
35+
"cell_type": "code",
36+
"execution_count": 7,
37+
"id": "b188568f",
38+
"metadata": {},
39+
"outputs": [
40+
{
41+
"name": "stdout",
42+
"output_type": "stream",
43+
"text": [
44+
"https://news-search.search.windows.net azureblob-index 2021-04-30-Preview iOExxhJ2A2wjdAGTjsMxASsJj3y3zUR54kjNTNpW9hAzSeD8PE3k\n"
45+
]
46+
}
47+
],
48+
"source": [
49+
"print(os.getenv(\"AZURE_COGNITIVE_SEARCH_ENDPOINT\"), os.getenv('AZURE_COGNITIVE_SEARCH_INDEX_NAME'), os.getenv('AZURE_COGNITIVE_SEARCH_API_VERSION'), os.getenv(\"AZURE_COGNITIVE_SEARCH_KEY\"))"
50+
]`,
51+
want: []string{`{"service":"news-search","key":"iOExxhJ2A2wjdAGTjsMxASsJj3y3zUR54kjNTNpW9hAzSeD8PE3k"}`},
3952
},
4053
{
41-
name: "invalid pattern",
42-
input: invalidPattern,
43-
want: nil,
54+
name: "invalid pattern",
55+
input: `
56+
azure:
57+
Key: wRRPyhjv8m6JGRujUUr-PK#a8d3rJ0mrGAxhmqf3A68OgZmlWUJyma
58+
Service: TS01.search.windows.net
59+
`,
60+
want: nil,
4461
},
4562
}
4663

0 commit comments

Comments
 (0)