Skip to content

Commit bcbb73f

Browse files
committed
feat(detectors): create azure_entra base package
feat(azure): finish pending items for review
1 parent 2b513ae commit bcbb73f

15 files changed

+1295
-186
lines changed

pkg/detectors/azure/azure.go

-92
This file was deleted.

pkg/detectors/azure/azure_test.go

-90
This file was deleted.

pkg/detectors/azure_entra/common.go

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package azure_entra
2+
3+
import (
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"
8+
"io"
9+
"net/http"
10+
"strings"
11+
12+
regexp "github.com/wasilibs/go-re2"
13+
14+
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
15+
)
16+
17+
const uuidStr = `[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}`
18+
19+
var (
20+
// Tenants can be identified with a UUID or an `*.onmicrosoft.com` domain.
21+
//
22+
// See:
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
24+
// https://learn.microsoft.com/en-us/microsoft-365/admin/setup/domains-faq?view=o365-worldwide#why-do-i-have-an--onmicrosoft-com--domain
25+
tenantIdPat = regexp.MustCompile(fmt.Sprintf(
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,
31+
))
32+
tenantOnMicrosoftPat = regexp.MustCompile(`([\w-]+\.onmicrosoft\.com)`)
33+
34+
clientIdPat = regexp.MustCompile(fmt.Sprintf(
35+
`(?i)(?:(?:app(?:lication)?|client)(?:[ ._-]?id)?|username| -u)(?:.|\s){0,45}?(%s)`, uuidStr))
36+
)
37+
38+
// FindTenantIdMatches returns a list of potential tenant IDs in the provided |data|.
39+
func FindTenantIdMatches(data string) map[string]struct{} {
40+
uniqueMatches := make(map[string]struct{})
41+
42+
for _, match := range tenantIdPat.FindAllStringSubmatch(data, -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+
}
51+
if _, ok := detectors.UuidFalsePositives[detectors.FalsePositive(m)]; ok {
52+
continue
53+
}
54+
uniqueMatches[m] = struct{}{}
55+
}
56+
for _, match := range tenantOnMicrosoftPat.FindAllStringSubmatch(data, -1) {
57+
uniqueMatches[match[1]] = struct{}{}
58+
}
59+
return uniqueMatches
60+
}
61+
62+
// FindClientIdMatches returns a list of potential client UUIDs in the provided |data|.
63+
func FindClientIdMatches(data string) map[string]struct{} {
64+
uniqueMatches := make(map[string]struct{})
65+
for _, match := range clientIdPat.FindAllStringSubmatch(data, -1) {
66+
m := strings.ToLower(match[1])
67+
if _, ok := detectors.UuidFalsePositives[detectors.FalsePositive(m)]; ok {
68+
continue
69+
}
70+
uniqueMatches[m] = struct{}{}
71+
}
72+
return uniqueMatches
73+
}
74+
75+
var (
76+
tenantCache = simple.NewCache[bool]()
77+
tenantGroup singleflight.Group
78+
)
79+
80+
// TenantExists returns whether the tenant exists according to Microsoft's well-known OpenID endpoint.
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+
100+
tenantUrl := fmt.Sprintf("https://login.microsoftonline.com/%s/.well-known/openid-configuration", tenant)
101+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, tenantUrl, nil)
102+
if err != nil {
103+
return false
104+
}
105+
106+
res, err := client.Do(req)
107+
if err != nil {
108+
logger.Error(err, "Failed to check if tenant exists")
109+
return false
110+
}
111+
defer func() {
112+
_, _ = io.Copy(io.Discard, res.Body)
113+
_ = res.Body.Close()
114+
}()
115+
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+
}
127+
}

0 commit comments

Comments
 (0)