@@ -2,14 +2,15 @@ package jiratoken
2
2
3
3
import (
4
4
"context"
5
- b64 "encoding/base64"
6
5
"fmt"
7
6
"net/http"
8
7
"strings"
9
8
10
9
regexp "github.com/wasilibs/go-re2"
11
10
11
+ "github.com/trufflesecurity/trufflehog/v3/pkg/common"
12
12
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
13
+ v1 "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jiratoken/v1"
13
14
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
14
15
)
15
16
@@ -29,45 +30,55 @@ var (
29
30
30
31
// https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/
31
32
// Tokens created after Jan 18 2023 use a variable length
32
- tokenPat = regexp .MustCompile (detectors .PrefixRegex ([]string {"jira" }) + `\b([A-Za-z0-9+/=_-]+=[A-Za-z0-9]{8})\b` )
33
- domainPat = regexp .MustCompile (detectors .PrefixRegex ([]string {"jira" }) + `\b((?:[a-zA-Z0-9-]{1,24}\.)+[a-zA-Z0-9-]{2,24}\.[a-zA-Z0-9-]{2,16})\b` )
34
- emailPat = regexp .MustCompile (detectors .PrefixRegex ([]string {"jira" }) + `\b([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,})\b` )
33
+ tokenPat = regexp .MustCompile (detectors .PrefixRegex ([]string {"atlassian" , "confluence" , " jira" }) + `\b(ATATT [A-Za-z0-9+/=_-]+=[A-Za-z0-9]{8})\b` )
34
+ domainPat = regexp .MustCompile (detectors .PrefixRegex ([]string {"atlassian" , "confluence" , " jira" }) + `\b((?:[a-zA-Z0-9-]{1,24}\.)+[a-zA-Z0-9-]{2,24}\.[a-zA-Z0-9-]{2,16})\b` )
35
+ emailPat = regexp .MustCompile (detectors .PrefixRegex ([]string {"atlassian" , "confluence" , " jira" }) + common . EmailPattern )
35
36
)
36
37
37
- const (
38
- failedAuth = "AUTHENTICATED_FAILED"
39
- loginReasonHeaderKey = "X-Seraph-LoginReason"
40
- )
38
+ func (s Scanner ) getClient () * http.Client {
39
+ if s .client != nil {
40
+ return s .client
41
+ }
42
+ return defaultClient
43
+ }
41
44
42
45
// Keywords are used for efficiently pre-filtering chunks.
43
46
// Use identifiers in the secret preferably, or the provider name.
44
47
func (s Scanner ) Keywords () []string {
45
- return []string {"jira" }
48
+ return []string {"atlassian" , "confluence" , " jira" }
46
49
}
47
50
48
51
// FromData will find and optionally verify JiraToken secrets in a given set of bytes.
49
52
func (s Scanner ) FromData (ctx context.Context , verify bool , data []byte ) (results []detectors.Result , err error ) {
50
53
dataStr := string (data )
51
54
52
- tokens := tokenPat .FindAllStringSubmatch (dataStr , - 1 )
53
- domains := domainPat .FindAllStringSubmatch (dataStr , - 1 )
54
- emails := emailPat .FindAllStringSubmatch (dataStr , - 1 )
55
+ var uniqueTokens , uniqueDomains , uniqueEmails = make (map [string ]struct {}), make (map [string ]struct {}), make (map [string ]struct {})
55
56
56
- for _ , email := range emails {
57
- if len (email ) != 2 {
58
- continue
59
- }
60
- resEmail := strings .TrimSpace (email [1 ])
57
+ for _ , token := range tokenPat .FindAllStringSubmatch (dataStr , - 1 ) {
58
+ uniqueTokens [token [1 ]] = struct {}{}
59
+ }
60
+
61
+ for _ , domain := range domainPat .FindAllStringSubmatch (dataStr , - 1 ) {
62
+ uniqueDomains [domain [1 ]] = struct {}{}
63
+ }
61
64
62
- for _ , token := range tokens {
63
- resToken := strings .TrimSpace (token [1 ])
64
- for _ , domain := range domains {
65
- resDomain := strings .TrimSpace (domain [1 ])
65
+ for _ , email := range emailPat .FindAllStringSubmatch (dataStr , - 1 ) {
66
+ uniqueEmails [strings .ToLower (email [1 ])] = struct {}{}
67
+ }
66
68
69
+ if len (uniqueDomains ) == 0 {
70
+ // reason: https://community.atlassian.com/forums/Jira-Product-Discovery-questions/Authorization-issues-with-GRAPHQL/qaq-p/2640943
71
+ // In case we don't find any domain matches we can use this as the graphql API works with this domain if our authentication is valid
72
+ uniqueDomains ["api.atlassian.com" ] = struct {}{}
73
+ }
74
+
75
+ for email := range uniqueEmails {
76
+ for token := range uniqueTokens {
77
+ for domain := range uniqueDomains {
67
78
s1 := detectors.Result {
68
79
DetectorType : detectorspb .DetectorType_JiraToken ,
69
- Raw : []byte (resToken ),
70
- RawV2 : []byte (fmt .Sprintf ("%s:%s:%s" , resEmail , resToken , resDomain )),
80
+ Raw : []byte (token ),
81
+ RawV2 : []byte (fmt .Sprintf ("%s:%s:%s" , email , token , domain )),
71
82
ExtraData : map [string ]string {
72
83
"rotation_guide" : "https://howtorotate.com/docs/tutorials/atlassian/" ,
73
84
"version" : fmt .Sprintf ("%d" , s .Version ()),
@@ -76,9 +87,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
76
87
77
88
if verify {
78
89
client := s .getClient ()
79
- isVerified , verificationErr := verifyJiratoken (ctx , client , resEmail , resDomain , resToken )
90
+ isVerified , verificationErr := v1 . VerifyJiraToken (ctx , client , email , domain , token )
80
91
s1 .Verified = isVerified
81
- s1 .SetVerificationError (verificationErr , resToken )
92
+ s1 .SetVerificationError (verificationErr , token )
82
93
}
83
94
84
95
results = append (results , s1 )
@@ -89,42 +100,6 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
89
100
return results , nil
90
101
}
91
102
92
- func (s Scanner ) getClient () * http.Client {
93
- if s .client != nil {
94
- return s .client
95
- }
96
- return defaultClient
97
- }
98
-
99
- func verifyJiratoken (ctx context.Context , client * http.Client , email , domain , token string ) (bool , error ) {
100
- data := fmt .Sprintf ("%s:%s" , email , token )
101
- sEnc := b64 .StdEncoding .EncodeToString ([]byte (data ))
102
- req , err := http .NewRequestWithContext (ctx , "GET" , "https://" + domain + "/rest/api/3/dashboard" , nil )
103
- if err != nil {
104
- return false , err
105
- }
106
- req .Header .Add ("Accept" , "application/json" )
107
- req .Header .Add ("Authorization" , fmt .Sprintf ("Basic %s" , sEnc ))
108
- res , err := client .Do (req )
109
- if err != nil {
110
- return false , err
111
- }
112
- defer res .Body .Close ()
113
-
114
- // If the request is successful and the login reason is not failed authentication, then the token is valid.
115
- // This is because Jira returns a 200 status code even if the token is invalid.
116
- // Jira returns a default dashboard page.
117
- if ! (res .StatusCode >= 200 && res .StatusCode < 300 ) {
118
- return false , fmt .Errorf ("unexpected HTTP response status %d" , res .StatusCode )
119
- }
120
-
121
- if res .Header .Get (loginReasonHeaderKey ) != failedAuth {
122
- return true , nil
123
- }
124
-
125
- return false , nil
126
- }
127
-
128
103
func (s Scanner ) Type () detectorspb.DetectorType {
129
104
return detectorspb .DetectorType_JiraToken
130
105
}
0 commit comments