Skip to content

Commit 9f85c83

Browse files
committed
Merge remote-tracking branch 'origin/release/v11.0.2' into release/v11.0.2
2 parents 745b463 + 8437b65 commit 9f85c83

File tree

4 files changed

+161
-56
lines changed

4 files changed

+161
-56
lines changed

plugins/modules-config/validations/o365.go

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,51 @@ import (
1313
)
1414

1515
const (
16-
loginUrl = "https://login.microsoftonline.com/"
1716
grantType = "client_credentials"
18-
scope = "https://manage.office.com/.default"
1917
endPointLogin = "/oauth2/v2.0/token"
2018
)
2119

20+
type CloudEnvironment string
21+
22+
const (
23+
CloudCommercial CloudEnvironment = "Commercial"
24+
CloudGCC CloudEnvironment = "GCC"
25+
CloudGCCHigh CloudEnvironment = "GCCHigh"
26+
CloudDoD CloudEnvironment = "DoD"
27+
)
28+
29+
type CloudConfig struct {
30+
LoginAuthority string
31+
Scope string
32+
}
33+
34+
func getCloudConfig(env CloudEnvironment) CloudConfig {
35+
configs := map[CloudEnvironment]CloudConfig{
36+
CloudCommercial: {
37+
LoginAuthority: "https://login.microsoftonline.com/",
38+
Scope: "https://manage.office.com/.default",
39+
},
40+
CloudGCC: {
41+
LoginAuthority: "https://login.microsoftonline.com/",
42+
Scope: "https://manage-gcc.office.com/.default",
43+
},
44+
CloudGCCHigh: {
45+
LoginAuthority: "https://login.microsoftonline.us/",
46+
Scope: "https://manage.office365.us/.default",
47+
},
48+
CloudDoD: {
49+
LoginAuthority: "https://login.microsoftonline.us/",
50+
Scope: "https://manage.protection.apps.mil/.default",
51+
},
52+
}
53+
54+
cloudConfig, exists := configs[env]
55+
if !exists {
56+
return configs[CloudCommercial]
57+
}
58+
return cloudConfig
59+
}
60+
2261
type MicrosoftLoginResponse struct {
2362
TokenType string `json:"token_type,omitempty"`
2463
Expires int `json:"expires_in,omitempty"`
@@ -30,6 +69,7 @@ type MicrosoftLoginResponse struct {
3069

3170
func ValidateO365Config(config *config.ModuleGroup) error {
3271
var clientId, clientSecret, tenantId string
72+
var cloudEnvironment CloudEnvironment = CloudCommercial
3373

3474
if config == nil {
3575
return fmt.Errorf("O365 configuration is nil")
@@ -43,6 +83,10 @@ func ValidateO365Config(config *config.ModuleGroup) error {
4383
clientSecret = cnf.ConfValue
4484
case "office365_tenant_id":
4585
tenantId = cnf.ConfValue
86+
case "office365_cloud_environment":
87+
if cnf.ConfValue != "" {
88+
cloudEnvironment = CloudEnvironment(cnf.ConfValue)
89+
}
4690
}
4791
}
4892

@@ -56,14 +100,16 @@ func ValidateO365Config(config *config.ModuleGroup) error {
56100
return fmt.Errorf("Tenant ID is required in O365 configuration")
57101
}
58102

103+
cloudConfig := getCloudConfig(cloudEnvironment)
104+
59105
// Validate credentials by attempting to get an access token
60-
requestUrl := fmt.Sprintf("%s%s%s", loginUrl, tenantId, endPointLogin)
106+
requestUrl := fmt.Sprintf("%s%s%s", cloudConfig.LoginAuthority, tenantId, endPointLogin)
61107

62108
data := url.Values{}
63109
data.Set("grant_type", grantType)
64110
data.Set("client_id", clientId)
65111
data.Set("client_secret", clientSecret)
66-
data.Set("scope", scope)
112+
data.Set("scope", cloudConfig.Scope)
67113

68114
client := &http.Client{
69115
Timeout: 10 * time.Second,

plugins/o365/go.mod

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ go 1.24.2
55
require (
66
github.com/google/uuid v1.6.0
77
github.com/threatwinds/go-sdk v1.0.43
8-
github.com/utmstack/config-client-go v1.2.7
8+
google.golang.org/grpc v1.74.2
9+
google.golang.org/protobuf v1.36.6
910
)
1011

1112
require (
@@ -45,8 +46,6 @@ require (
4546
golang.org/x/text v0.27.0 // indirect
4647
google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 // indirect
4748
google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 // indirect
48-
google.golang.org/grpc v1.74.2 // indirect
49-
google.golang.org/protobuf v1.36.6 // indirect
5049
gopkg.in/yaml.v3 v3.0.1 // indirect
5150
sigs.k8s.io/yaml v1.5.0 // indirect
5251
)

plugins/o365/go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,6 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
9393
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
9494
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
9595
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
96-
github.com/utmstack/config-client-go v1.2.7 h1:JeRdI5JjH1liNzMW3LmyevjuPd67J/yt9MAO3+oJAuM=
97-
github.com/utmstack/config-client-go v1.2.7/go.mod h1:kM0KoUizM9ZlcQp0qKviGTWn/+anT5Rfjx3zfZk79nM=
9896
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
9997
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
10098
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=

plugins/o365/main.go

Lines changed: 109 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,56 @@ import (
1717
"github.com/utmstack/UTMStack/plugins/o365/config"
1818
)
1919

20+
type CloudEnvironment string
21+
2022
const (
21-
loginUrl = "https://login.microsoftonline.com/"
22-
GRANTTYPE = "client_credentials"
23-
SCOPE = "https://manage.office.com/.default"
24-
endPointLogin = "/oauth2/v2.0/token"
25-
endPointStartSubscription = "/activity/feed/subscriptions/start"
26-
endPointContent = "/activity/feed/subscriptions/content"
27-
BASEURL = "https://manage.office.com/api/v1.0/"
28-
DefaultTenant = "ce66672c-e36d-4761-a8c8-90058fee1a24"
23+
GRANTTYPE = "client_credentials"
24+
endPointLogin = "/oauth2/v2.0/token"
25+
endPointStartSubscription = "/activity/feed/subscriptions/start"
26+
endPointContent = "/activity/feed/subscriptions/content"
27+
DefaultTenant = "ce66672c-e36d-4761-a8c8-90058fee1a24"
28+
apiVersion = "api/v1.0/"
29+
CloudCommercial CloudEnvironment = "Commercial"
30+
CloudGCC CloudEnvironment = "GCC"
31+
CloudGCCHigh CloudEnvironment = "GCCHigh"
32+
CloudDoD CloudEnvironment = "DoD"
2933
)
3034

31-
func GetMicrosoftLoginLink(tenant string) string {
32-
return fmt.Sprintf("%s%s%s", loginUrl, tenant, endPointLogin)
33-
}
34-
35-
func GetStartSubscriptionLink(tenant string) string {
36-
return fmt.Sprintf("%s%s%s", BASEURL, tenant, endPointStartSubscription)
35+
type CloudConfig struct {
36+
LoginAuthority string
37+
ManagementEndpoint string
38+
Scope string
3739
}
3840

39-
func GetContentLink(tenant string) string {
40-
return fmt.Sprintf("%s%s%s", BASEURL, tenant, endPointContent)
41-
}
41+
func GetCloudConfig(env CloudEnvironment) CloudConfig {
42+
configs := map[CloudEnvironment]CloudConfig{
43+
CloudCommercial: {
44+
LoginAuthority: "https://login.microsoftonline.com/",
45+
ManagementEndpoint: "https://manage.office.com/",
46+
Scope: "https://manage.office.com/.default",
47+
},
48+
CloudGCC: {
49+
LoginAuthority: "https://login.microsoftonline.com/",
50+
ManagementEndpoint: "https://manage-gcc.office.com/",
51+
Scope: "https://manage-gcc.office.com/.default",
52+
},
53+
CloudGCCHigh: {
54+
LoginAuthority: "https://login.microsoftonline.us/",
55+
ManagementEndpoint: "https://manage.office365.us/",
56+
Scope: "https://manage.office365.us/.default",
57+
},
58+
CloudDoD: {
59+
LoginAuthority: "https://login.microsoftonline.us/",
60+
ManagementEndpoint: "https://manage.protection.apps.mil/",
61+
Scope: "https://manage.protection.apps.mil/.default",
62+
},
63+
}
4264

43-
func GetTenantId() string {
44-
return DefaultTenant
65+
config, exists := configs[env]
66+
if !exists {
67+
return configs[CloudCommercial]
68+
}
69+
return config
4570
}
4671

4772
func main() {
@@ -65,29 +90,17 @@ func main() {
6590
for range ticker.C {
6691
endTime := time.Now().UTC()
6792

68-
if err := ConnectionChecker(loginUrl); err != nil {
69-
_ = catcher.Error("External connection failure detected: %v", err, nil)
70-
}
71-
7293
moduleConfig := config.GetConfig()
7394
if moduleConfig != nil && moduleConfig.ModuleActive {
95+
checkConfiguredEnvironments(moduleConfig.ModuleGroups)
96+
7497
var wg sync.WaitGroup
7598
wg.Add(len(moduleConfig.ModuleGroups))
7699

77100
for _, grp := range moduleConfig.ModuleGroups {
78101
go func(group *config.ModuleGroup) {
79102
defer wg.Done()
80-
var invalid bool
81-
for _, c := range group.ModuleGroupConfigurations {
82-
if strings.TrimSpace(c.ConfValue) == "" {
83-
invalid = true
84-
break
85-
}
86-
}
87-
88-
if !invalid {
89-
pull(startTime, endTime, group)
90-
}
103+
pull(startTime, endTime, group)
91104
}(grp)
92105
}
93106

@@ -98,6 +111,34 @@ func main() {
98111
}
99112
}
100113

114+
func checkConfiguredEnvironments(groups []*config.ModuleGroup) {
115+
uniqueAuthorities := make(map[string]CloudEnvironment)
116+
117+
for _, group := range groups {
118+
env := getGroupEnvironment(group)
119+
cloudConfig := GetCloudConfig(env)
120+
uniqueAuthorities[cloudConfig.LoginAuthority] = env
121+
}
122+
123+
for authority, env := range uniqueAuthorities {
124+
if err := ConnectionChecker(authority); err != nil {
125+
_ = catcher.Error("External connection failure detected", err, map[string]any{
126+
"environment": env,
127+
"authority": authority,
128+
})
129+
}
130+
}
131+
}
132+
133+
func getGroupEnvironment(group *config.ModuleGroup) CloudEnvironment {
134+
for _, cnf := range group.ModuleGroupConfigurations {
135+
if cnf.ConfKey == "office365_cloud_environment" && cnf.ConfValue != "" {
136+
return CloudEnvironment(cnf.ConfValue)
137+
}
138+
}
139+
return CloudCommercial
140+
}
141+
101142
func pull(startTime time.Time, endTime time.Time, group *config.ModuleGroup) {
102143
agent := GetOfficeProcessor(group)
103144

@@ -117,7 +158,7 @@ func pull(startTime time.Time, endTime time.Time, group *config.ModuleGroup) {
117158
for _, log := range logs {
118159
plugins.EnqueueLog(&plugins.Log{
119160
Id: uuid.New().String(),
120-
TenantId: GetTenantId(),
161+
TenantId: agent.TenantId,
121162
DataType: "o365",
122163
DataSource: group.GroupName,
123164
Timestamp: time.Now().UTC().Format(time.RFC3339Nano),
@@ -127,11 +168,13 @@ func pull(startTime time.Time, endTime time.Time, group *config.ModuleGroup) {
127168
}
128169

129170
type OfficeProcessor struct {
130-
Credentials MicrosoftLoginResponse
131-
TenantId string
132-
ClientId string
133-
ClientSecret string
134-
Subscriptions []string
171+
Credentials MicrosoftLoginResponse
172+
TenantId string
173+
ClientId string
174+
ClientSecret string
175+
Subscriptions []string
176+
CloudEnvironment CloudEnvironment
177+
CloudConfig CloudConfig
135178
}
136179

137180
type MicrosoftLoginResponse struct {
@@ -162,7 +205,10 @@ type ContentList struct {
162205
type ContentDetailsResponse []map[string]any
163206

164207
func GetOfficeProcessor(group *config.ModuleGroup) OfficeProcessor {
165-
offProc := OfficeProcessor{}
208+
offProc := OfficeProcessor{
209+
CloudEnvironment: CloudCommercial,
210+
}
211+
166212
for _, cnf := range group.ModuleGroupConfigurations {
167213
switch cnf.ConfKey {
168214
case "office365_client_id":
@@ -171,27 +217,34 @@ func GetOfficeProcessor(group *config.ModuleGroup) OfficeProcessor {
171217
offProc.ClientSecret = cnf.ConfValue
172218
case "office365_tenant_id":
173219
offProc.TenantId = cnf.ConfValue
220+
case "office365_cloud_environment":
221+
if cnf.ConfValue != "" {
222+
offProc.CloudEnvironment = CloudEnvironment(cnf.ConfValue)
223+
}
174224
}
175225
}
176226

177-
offProc.Subscriptions = append(offProc.Subscriptions, []string{
227+
offProc.CloudConfig = GetCloudConfig(offProc.CloudEnvironment)
228+
229+
offProc.Subscriptions = []string{
178230
"Audit.AzureActiveDirectory",
179231
"Audit.Exchange",
180232
"Audit.General",
181233
"DLP.All",
182-
"Audit.SharePoint"}...)
234+
"Audit.SharePoint",
235+
}
183236

184237
return offProc
185238
}
186239

187240
func (o *OfficeProcessor) GetAuth() error {
188-
requestUrl := GetMicrosoftLoginLink(o.TenantId)
241+
requestUrl := fmt.Sprintf("%s%s%s", o.CloudConfig.LoginAuthority, o.TenantId, endPointLogin)
189242

190243
data := url.Values{}
191244
data.Set("grant_type", GRANTTYPE)
192245
data.Set("client_id", o.ClientId)
193246
data.Set("client_secret", o.ClientSecret)
194-
data.Set("scope", SCOPE)
247+
data.Set("scope", o.CloudConfig.Scope)
195248

196249
headers := map[string]string{
197250
"Content-Type": "application/x-www-form-urlencoded",
@@ -230,7 +283,12 @@ func (o *OfficeProcessor) GetAuth() error {
230283

231284
func (o *OfficeProcessor) StartSubscriptions() error {
232285
for _, subscription := range o.Subscriptions {
233-
link := GetStartSubscriptionLink(o.TenantId) + "?contentType=" + subscription
286+
link := fmt.Sprintf("%s%s%s%s?contentType=%s",
287+
o.CloudConfig.ManagementEndpoint,
288+
apiVersion,
289+
o.TenantId,
290+
endPointStartSubscription,
291+
subscription)
234292
headers := map[string]string{
235293
"Content-Type": "application/json",
236294
"Authorization": fmt.Sprintf("%s %s", o.Credentials.TokenType, o.Credentials.AccessToken),
@@ -277,7 +335,11 @@ func (o *OfficeProcessor) StartSubscriptions() error {
277335
}
278336

279337
func (o *OfficeProcessor) GetContentList(subscription string, startTime time.Time, endTime time.Time) ([]ContentList, error) {
280-
link := GetContentLink(o.TenantId) + fmt.Sprintf("?startTime=%s&endTime=%s&contentType=%s",
338+
link := fmt.Sprintf("%s%s%s%s?startTime=%s&endTime=%s&contentType=%s",
339+
o.CloudConfig.ManagementEndpoint,
340+
apiVersion,
341+
o.TenantId,
342+
endPointContent,
281343
startTime.UTC().Format("2006-01-02T15:04:05"),
282344
endTime.UTC().Format("2006-01-02T15:04:05"),
283345
subscription)

0 commit comments

Comments
 (0)