@@ -3,7 +3,6 @@ package couchbase
33import (
44 "context"
55 "fmt"
6- "strings"
76 "time"
87 "unicode"
98
@@ -23,35 +22,18 @@ type Scanner struct {
2322var _ detectors.Detector = (* Scanner )(nil )
2423
2524var (
26-
2725 // Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
28- connectionStringPat = regexp .MustCompile (`\bcb\.[a-z0-9]+\.cloud\.couchbase\.com\b` )
29- usernamePat = `?()/\+=\s\n`
30- passwordPat = `^<>;.*&|£\n\s`
31- // passwordPat = regexp.MustCompile(`(?i)(?:pass|pwd)(?:.|[\n\r]){0,15}(\b[^<>;.*&|£\n\s]{8,100}$)`)
32- // passwordPat = regexp.MustCompile(`(?im)(?:pass|pwd)\S{0,40}?[:=\s]{1,3}[ '"=]{0,1}([^:?()/\+=\s\n]{4,40})\b`)
26+ connectionStringPat = regexp .MustCompile (`\b(cb\.[a-z0-9]+\.cloud\.couchbase\.com)\b` )
27+ usernamePat = common .UsernameRegexCheck (`?()/\+=\s\n` )
28+ passwordPat = common .PasswordRegexCheck (`^<>;.*&|£\n\s` )
3329)
3430
35- func meetsCouchbasePasswordRequirements (password string ) (string , bool ) {
36- var hasLower , hasUpper , hasNumber , hasSpecialChar bool
37- for _ , char := range password {
38- switch {
39- case unicode .IsLower (char ):
40- hasLower = true
41- case unicode .IsUpper (char ):
42- hasUpper = true
43- case unicode .IsNumber (char ):
44- hasNumber = true
45- case unicode .IsPunct (char ) || unicode .IsSymbol (char ):
46- hasSpecialChar = true
47- }
48-
49- if hasLower && hasUpper && hasNumber && hasSpecialChar {
50- return password , true
51- }
52- }
31+ func (s Scanner ) Type () detectorspb.DetectorType {
32+ return detectorspb .DetectorType_Couchbase
33+ }
5334
54- return "" , false
35+ func (s Scanner ) Description () string {
36+ return "Couchbase is a distributed NoSQL cloud database. Couchbase credentials can be used to access and modify data within the Couchbase database."
5537}
5638
5739// Keywords are used for efficiently pre-filtering chunks.
@@ -64,77 +46,37 @@ func (s Scanner) Keywords() []string {
6446func (s Scanner ) FromData (ctx context.Context , verify bool , data []byte ) (results []detectors.Result , err error ) {
6547 dataStr := string (data )
6648
67- connectionStringMatches := connectionStringPat . FindAllStringSubmatch ( dataStr , - 1 )
49+ var uniqueConnStrings , uniqueUsernames , uniquePasswords = make ( map [ string ] struct {}), make ( map [ string ] struct {}), make ( map [ string ] struct {} )
6850
69- // prepend 'couchbases://' to the connection string as the connection
70- // string format is couchbases://cb.stuff.cloud.couchbase.com but the
71- // cb.stuff.cloud.couchbase.com may be separated from the couchbases:// in codebases.
72- for i , connectionStringMatch := range connectionStringMatches {
73- connectionStringMatches [i ][0 ] = "couchbases://" + connectionStringMatch [0 ]
51+ for _ , match := range connectionStringPat .FindAllStringSubmatch (dataStr , - 1 ) {
52+ uniqueConnStrings ["couchbases://" + match [1 ]] = struct {}{}
7453 }
7554
76- usernameRegexState := common .UsernameRegexCheck (usernamePat )
77- usernameMatches := usernameRegexState .Matches (data )
78-
79- passwordRegexState := common .PasswordRegexCheck (passwordPat )
80- passwordMatches := passwordRegexState .Matches (data )
81-
82- for _ , connectionStringMatch := range connectionStringMatches {
83- resConnectionStringMatch := strings .TrimSpace (connectionStringMatch [0 ])
84-
85- for _ , resUsernameMatch := range usernameMatches {
55+ for _ , match := range usernamePat .Matches (data ) {
56+ uniqueUsernames [match ] = struct {}{}
57+ }
8658
87- for _ , resPasswordMatch := range passwordMatches {
88- _ , metPasswordRequirements := meetsCouchbasePasswordRequirements (resPasswordMatch )
59+ for _ , match := range passwordPat .Matches (data ) {
60+ uniquePasswords [match ] = struct {}{}
61+ }
8962
90- if ! metPasswordRequirements {
63+ for connString := range uniqueConnStrings {
64+ for username := range uniqueUsernames {
65+ for password := range uniquePasswords {
66+ if ! isValidCouchbasePassword (password ) {
9167 continue
9268 }
9369
9470 s1 := detectors.Result {
9571 DetectorType : detectorspb .DetectorType_Couchbase ,
96- Raw : []byte (fmt . Sprintf ( " %s:%s@%s" , resUsernameMatch , resPasswordMatch , resConnectionStringMatch ) ),
72+ Raw : fmt . Appendf ( []byte ("" ), " %s:%s@%s" , username , password , connString ),
9773 }
9874
9975 if verify {
100-
101- options := gocb.ClusterOptions {
102- Authenticator : gocb.PasswordAuthenticator {
103- Username : resUsernameMatch ,
104- Password : resPasswordMatch ,
105- },
106- }
107-
108- // Sets a pre-configured profile called "wan-development" to help avoid latency issues
109- // when accessing Capella from a different Wide Area Network
110- // or Availability Zone (e.g. your laptop).
111- if err := options .ApplyProfile (gocb .ClusterConfigProfileWanDevelopment ); err != nil {
112- continue
113- }
114-
115- // Initialize the Connection
116- cluster , err := gocb .Connect (resConnectionStringMatch , options )
117- if err != nil {
118- continue
119- }
120-
121- // We'll ping the KV nodes in our cluster.
122- pings , err := cluster .Ping (& gocb.PingOptions {
123- Timeout : time .Second * 5 ,
124- })
125-
126- if err != nil {
127- continue
128- }
129-
130- for _ , ping := range pings .Services {
131- for _ , pingEndpoint := range ping {
132- if pingEndpoint .State == gocb .PingStateOk {
133- s1 .Verified = true
134- break
135- }
136- }
137- }
76+ isVerified , verificationErr := verifyCouchBase (username , password , connString )
77+ s1 .Verified = isVerified
78+ s1 .SetVerificationError (verificationErr )
79+ s1 .SetPrimarySecretValue (connString )
13880 }
13981
14082 results = append (results , s1 )
@@ -144,10 +86,62 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
14486 return results , nil
14587}
14688
147- func (s Scanner ) Type () detectorspb.DetectorType {
148- return detectorspb .DetectorType_Couchbase
89+ func verifyCouchBase (username , password , connString string ) (bool , error ) {
90+ options := gocb.ClusterOptions {
91+ Authenticator : gocb.PasswordAuthenticator {
92+ Username : username ,
93+ Password : password ,
94+ },
95+ }
96+
97+ // Sets a pre-configured profile called "wan-development" to help avoid latency issues
98+ // when accessing Capella from a different Wide Area Network
99+ // or Availability Zone (e.g. your laptop).
100+ if err := options .ApplyProfile (gocb .ClusterConfigProfileWanDevelopment ); err != nil {
101+ return false , err
102+ }
103+
104+ // Initialize the Connection
105+ cluster , err := gocb .Connect (connString , options )
106+ if err != nil {
107+ return false , err
108+ }
109+
110+ // We'll ping the KV nodes in our cluster.
111+ pings , err := cluster .Ping (& gocb.PingOptions {
112+ Timeout : time .Second * 5 ,
113+ })
114+
115+ if err != nil {
116+ return false , err
117+ }
118+
119+ for _ , ping := range pings .Services {
120+ for _ , pingEndpoint := range ping {
121+ if pingEndpoint .State == gocb .PingStateOk {
122+ return true , nil
123+ }
124+ }
125+ }
126+
127+ return false , nil
149128}
150129
151- func (s Scanner ) Description () string {
152- return "Couchbase is a distributed NoSQL cloud database. Couchbase credentials can be used to access and modify data within the Couchbase database."
130+ func isValidCouchbasePassword (password string ) bool {
131+ var hasLower , hasUpper , hasNumber , hasSpecialChar bool
132+
133+ for _ , r := range password {
134+ switch {
135+ case unicode .IsLower (r ):
136+ hasLower = true
137+ case unicode .IsUpper (r ):
138+ hasUpper = true
139+ case unicode .IsNumber (r ):
140+ hasNumber = true
141+ case unicode .IsPunct (r ), unicode .IsSymbol (r ):
142+ hasSpecialChar = true
143+ }
144+ }
145+
146+ return hasLower && hasUpper && hasNumber && hasSpecialChar
153147}
0 commit comments