@@ -3,8 +3,8 @@ package azuresearchadminkey
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ "io"
6
7
"net/http"
7
- "strings"
8
8
9
9
regexp "github.com/wasilibs/go-re2"
10
10
@@ -23,73 +23,95 @@ var _ detectors.Detector = (*Scanner)(nil)
23
23
24
24
var (
25
25
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` )
29
29
)
30
30
31
31
// Keywords are used for efficiently pre-filtering chunks.
32
32
// Use identifiers in the secret preferably, or the provider name.
33
33
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."
35
43
}
36
44
37
45
// FromData will find and optionally verify AzureSearchAdminKey secrets in a given set of bytes.
38
46
func (s Scanner ) FromData (ctx context.Context , verify bool , data []byte ) (results []detectors.Result , err error ) {
39
47
dataStr := string (data )
40
48
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
+ }
48
62
49
- s1 := detectors.Result {
63
+ for key := range keyMatches {
64
+ for service := range serviceMatches {
65
+ r := detectors.Result {
50
66
DetectorType : detectorspb .DetectorType_AzureSearchAdminKey ,
51
- Raw : []byte (resMatch ),
52
- RawV2 : []byte (resMatch + resServiceMatch ),
67
+ Raw : []byte (key ),
68
+ RawV2 : []byte (`{"service":"` + service + `","key":"` + key + `"}` ),
53
69
}
54
70
55
71
if verify {
56
72
client := s .client
57
73
if client == nil {
58
74
client = defaultClient
59
75
}
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 )
80
80
}
81
81
82
- results = append (results , s1 )
82
+ results = append (results , r )
83
+ if r .Verified {
84
+ break
85
+ }
83
86
}
84
87
}
85
88
86
89
return results , nil
87
90
}
88
91
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 )
92
98
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
+ }
95
117
}
0 commit comments