1
1
package azure_entra
2
2
3
3
import (
4
- "context"
5
4
"fmt"
5
+ "github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple"
6
+ "github.com/trufflesecurity/trufflehog/v3/pkg/context"
7
+ "golang.org/x/sync/singleflight"
6
8
"io"
7
9
"net/http"
8
10
"strings"
21
23
// https://learn.microsoft.com/en-us/partner-center/account-settings/find-ids-and-domain-names#find-the-microsoft-azure-ad-tenant-id-and-primary-domain-name
22
24
// https://learn.microsoft.com/en-us/microsoft-365/admin/setup/domains-faq?view=o365-worldwide#why-do-i-have-an--onmicrosoft-com--domain
23
25
tenantIdPat = regexp .MustCompile (fmt .Sprintf (
24
- `(?i)(?:login\.microsoftonline\.com/|sts\.windows\.net/|(?:t[ae]n[ae]nt(?:[ ._-]?id)?|\btid)(?:.|\s){0,45}?)(%s)` , uuidStr ,
26
+ //language=regexp
27
+ `(?i)(?:(?:login\.microsoftonline\.com/|(?:login|sts)\.windows\.net/|(?:t[ae]n[ae]nt(?:[ ._-]?id)?|\btid)(?:.|\s){0,60}?)(%s)|https?://(%s)|X-AnchorMailbox(?:.|\s){0,60}?@(%s))` ,
28
+ uuidStr ,
29
+ uuidStr ,
30
+ uuidStr ,
25
31
))
26
32
tenantOnMicrosoftPat = regexp .MustCompile (`([\w-]+\.onmicrosoft\.com)` )
27
33
@@ -34,7 +40,14 @@ func FindTenantIdMatches(data string) map[string]struct{} {
34
40
uniqueMatches := make (map [string ]struct {})
35
41
36
42
for _ , match := range tenantIdPat .FindAllStringSubmatch (data , - 1 ) {
37
- m := strings .ToLower (match [1 ])
43
+ var m string
44
+ if match [1 ] != "" {
45
+ m = strings .ToLower (match [1 ])
46
+ } else if match [2 ] != "" {
47
+ m = strings .ToLower (match [2 ])
48
+ } else if match [3 ] != "" {
49
+ m = strings .ToLower (match [3 ])
50
+ }
38
51
if _ , ok := detectors .UuidFalsePositives [detectors .FalsePositive (m )]; ok {
39
52
continue
40
53
}
@@ -59,19 +72,56 @@ func FindClientIdMatches(data string) map[string]struct{} {
59
72
return uniqueMatches
60
73
}
61
74
75
+ var (
76
+ tenantCache = simple .NewCache [bool ]()
77
+ tenantGroup singleflight.Group
78
+ )
79
+
62
80
// TenantExists returns whether the tenant exists according to Microsoft's well-known OpenID endpoint.
63
81
func TenantExists (ctx context.Context , client * http.Client , tenant string ) bool {
82
+ // Use cached value where possible.
83
+ if tenantExists , isCached := tenantCache .Get (tenant ); isCached {
84
+ return tenantExists
85
+ }
86
+
87
+ // https://www.codingexplorations.com/blog/understanding-singleflight-in-golang-a-solution-for-eliminating-redundant-work
88
+ tenantExists , _ , _ := tenantGroup .Do (tenant , func () (interface {}, error ) {
89
+ result := queryTenant (ctx , client , tenant )
90
+ tenantCache .Set (tenant , result )
91
+ return result , nil
92
+ })
93
+
94
+ return tenantExists .(bool )
95
+ }
96
+
97
+ func queryTenant (ctx context.Context , client * http.Client , tenant string ) bool {
98
+ logger := ctx .Logger ().WithName ("azure" ).WithValues ("tenant" , tenant )
99
+
64
100
tenantUrl := fmt .Sprintf ("https://login.microsoftonline.com/%s/.well-known/openid-configuration" , tenant )
65
101
req , err := http .NewRequestWithContext (ctx , http .MethodGet , tenantUrl , nil )
66
102
if err != nil {
67
103
return false
68
104
}
69
105
70
106
res , err := client .Do (req )
107
+ if err != nil {
108
+ logger .Error (err , "Failed to check if tenant exists" )
109
+ return false
110
+ }
71
111
defer func () {
72
112
_ , _ = io .Copy (io .Discard , res .Body )
73
113
_ = res .Body .Close ()
74
114
}()
75
115
76
- return res .StatusCode == http .StatusOK
116
+ switch res .StatusCode {
117
+ case http .StatusOK :
118
+ return true
119
+ case http .StatusBadRequest :
120
+ logger .V (4 ).Info ("Tenant does not exist." )
121
+ return false
122
+ default :
123
+ bodyBytes , _ := io .ReadAll (res .Body )
124
+ logger .Error (nil , "WARNING: Unexpected response when checking if tenant exists" , "status_code" , res .StatusCode , "body" , string (bodyBytes ))
125
+ return false
126
+ }
77
127
}
0 commit comments