From f8e28a10e81cd02134335d6f21e3696f1f500641 Mon Sep 17 00:00:00 2001 From: Preslav Gerchev Date: Fri, 12 Jan 2024 10:09:11 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20fetching=20of=20shared=20mail?= =?UTF-8?q?boxes=20in=20exchange.=20(#2999)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ Add fetching of shared mailboxes in exchange. * Update docs + min mondoo version. --------- Signed-off-by: Preslav --- .github/actions/spelling/expect.txt | 40 +++---- providers/ms365/connection/exchange_report.go | 23 +++- providers/ms365/resources/ms365.go | 45 ++++++- providers/ms365/resources/ms365.lr | 16 ++- providers/ms365/resources/ms365.lr.go | 112 ++++++++++++++++++ .../ms365/resources/ms365.lr.manifest.yaml | 12 ++ 6 files changed, 223 insertions(+), 25 deletions(-) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 2001180027..2f7c440e09 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -5,12 +5,13 @@ autoaccept autoscaler backupconfiguration bigquery +bytematchstatement cavium cdn certificatechains cmek -cpe Cooldown +cpe cryptokey customresources datapath @@ -21,19 +22,26 @@ dlq dlv ekm elbv +exo gcfs +geomatchstatement gistfile gpu gvnic +headerorder hostkeys -HSTS iap ilb ingresstls iotedge +ipsetforwardedipconfig +ipsetreferencestatement jira +jsonbody +labelmatchstatement linux loggingservice +managedrulegroupstatement managedzone mcr messagestoragepolicy @@ -44,19 +52,28 @@ nodepool nullgroup nullstring opcplc +orstatement Pids postgre pushconfig querypack +ratebasedstatement +regexmatchstatement +regexpatternsetreferencestatement resourcegroup rulegroup +rulegroupreferencestatement Sas scim serviceprincipals +singlequeryargument +sizeconstraintstatement Snat spdx sph spo +sqli +sqlimatchstatement sqlserver sshkeys testutils @@ -65,23 +82,6 @@ tpu vdcs Vtpm vulnerabilityassessmentsettings -wil vulnmgmt -bytematchstatement -geomatchstatement -headerorder -ipsetforwardedipconfig -ipsetreferencestatement -jsonbody -labelmatchstatement -managedrulegroupstatement -orstatement -ratebasedstatement -regexmatchstatement -regexpatternsetreferencestatement -rulegroupreferencestatement -singlequeryargument -sizeconstraintstatement -sqli -sqlimatchstatement +wil xssmatchstatement diff --git a/providers/ms365/connection/exchange_report.go b/providers/ms365/connection/exchange_report.go index 7fead925d6..123c16bfb1 100644 --- a/providers/ms365/connection/exchange_report.go +++ b/providers/ms365/connection/exchange_report.go @@ -42,7 +42,7 @@ $AtpPolicyForO365 = (Get-AtpPolicyForO365) $SharingPolicy = (Get-SharingPolicy) $RoleAssignmentPolicy = (Get-RoleAssignmentPolicy) $ExternalInOutlook = (Get-ExternalInOutlook) - +$ExoMailbox = (Get-EXOMailbox -RecipientTypeDetails SharedMailbox) $exchangeOnline = New-Object PSObject Add-Member -InputObject $exchangeOnline -MemberType NoteProperty -Name MalwareFilterPolicy -Value @($MalwareFilterPolicy) @@ -63,6 +63,7 @@ Add-Member -InputObject $exchangeOnline -MemberType NoteProperty -Name AtpPolicy Add-Member -InputObject $exchangeOnline -MemberType NoteProperty -Name SharingPolicy -Value @($SharingPolicy) Add-Member -InputObject $exchangeOnline -MemberType NoteProperty -Name RoleAssignmentPolicy -Value @($RoleAssignmentPolicy) Add-Member -InputObject $exchangeOnline -MemberType NoteProperty -Name ExternalInOutlook -Value @($ExternalInOutlook) +Add-Member -InputObject $exchangeOnline -MemberType NoteProperty -Name ExoMailbox -Value @($ExoMailbox) Disconnect-ExchangeOnline -Confirm:$false @@ -145,6 +146,8 @@ type ExchangeOnlineReport struct { SharingPolicy []interface{} `json:"SharingPolicy"` RoleAssignmentPolicy []interface{} `json:"RoleAssignmentPolicy"` ExternalInOutlook []*ExternalSender `json:"ExternalInOutlook"` + // note: this only contains shared mailboxes + ExoMailbox []*ExoMailbox `json:"ExoMailbox"` } type ExternalSender struct { @@ -152,3 +155,21 @@ type ExternalSender struct { Enabled bool `json:"Enabled"` AllowList []string `json:"AllowList"` } + +type ExoMailbox struct { + ExternalDirectoryObjectId string `json:"ExternalDirectoryObjectId"` + UserPrincipalName string `json:"UserPrincipalName"` + Alias string `json:"Alias"` + DisplayName string `json:"DisplayName"` + EmailAddresses []string `json:"EmailAddresses"` + PrimarySmtpAddress string `json:"PrimarySmtpAddress"` + RecipientType string `json:"RecipientType"` + RecipientTypeDetails string `json:"RecipientTypeDetails"` + Identity string `json:"Identity"` + Id string `json:"Id"` + ExchangeVersion string `json:"ExchangeVersion"` + Name string `json:"Name"` + DistinguishedName string `json:"DistinguishedName"` + OrganizationId string `json:"OrganizationId"` + Guid string `json:"Guid"` +} diff --git a/providers/ms365/resources/ms365.go b/providers/ms365/resources/ms365.go index fcad06d5fa..92f2fa0c3a 100644 --- a/providers/ms365/resources/ms365.go +++ b/providers/ms365/resources/ms365.go @@ -21,10 +21,37 @@ func (m *mqlMs365ExchangeonlineExternalSender) id() (string, error) { return m.Identity.Data, nil } +func (m *mqlMs365ExchangeonlineExoMailbox) id() (string, error) { + return m.Identity.Data, nil +} + func (m *mqlMs365SharepointonlineSite) id() (string, error) { return m.Url.Data, nil } +func (m *mqlMs365ExchangeonlineExoMailbox) user() (*mqlMicrosoftUser, error) { + externalId := m.ExternalDirectoryObjectId.Data + if externalId == "" { + return nil, errors.New("no externalDirectoryObjectId provided, cannot find user for mailbox") + } + microsoft, err := m.MqlRuntime.CreateResource(m.MqlRuntime, "microsoft", map[string]*llx.RawData{}) + if err != nil { + return nil, err + } + mqlMicrosoft := microsoft.(*mqlMicrosoft) + users := mqlMicrosoft.GetUsers() + if users.Error != nil { + return nil, users.Error + } + for _, u := range users.Data { + mqlUser := u.(*mqlMicrosoftUser) + if mqlUser.Id.Data == externalId { + return mqlUser, nil + } + } + return nil, errors.New("cannot find user for exchange mailbox") +} + func initMs365Exchangeonline(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) { conn := runtime.Connection.(*connection.Ms365Connection) ctx := context.Background() @@ -83,6 +110,20 @@ func initMs365Exchangeonline(runtime *plugin.Runtime, args map[string]*llx.RawDa externalInOutlook = append(externalInOutlook, mql) } + + sharedMailboxes := []interface{}{} + for _, m := range report.ExoMailbox { + mql, err := CreateResource(runtime, "ms365.exchangeonline.exoMailbox", + map[string]*llx.RawData{ + "identity": llx.StringData(m.Identity), + "externalDirectoryObjectId": llx.StringData(m.ExternalDirectoryObjectId), + }) + if err != nil { + return args, nil, err + } + + sharedMailboxes = append(sharedMailboxes, mql) + } args["malwareFilterPolicy"] = llx.ArrayData(malwareFilterPolicy, types.Any) args["hostedOutboundSpamFilterPolicy"] = llx.ArrayData(hostedOutboundSpamFilterPolicy, types.Any) args["transportRule"] = llx.ArrayData(transportRule, types.Any) @@ -101,7 +142,7 @@ func initMs365Exchangeonline(runtime *plugin.Runtime, args map[string]*llx.RawDa args["sharingPolicy"] = llx.ArrayData(sharingPolicy, types.Any) args["roleAssignmentPolicy"] = llx.ArrayData(roleAssignmentPolicy, types.Any) args["externalInOutlook"] = llx.ArrayData(externalInOutlook, types.ResourceLike) - + args["sharedMailboxes"] = llx.ArrayData(sharedMailboxes, types.ResourceLike) return args, nil, nil } @@ -204,7 +245,7 @@ func initMs365Teams(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[ "meetingChatEnabledType": llx.StringData(teamsPolicy.MeetingChatEnabledType), "designatedPresenterRoleMode": llx.StringData(teamsPolicy.DesignatedPresenterRoleMode), "allowExternalParticipantGiveRequestControl": llx.BoolData(teamsPolicy.AllowExternalParticipantGiveRequestControl), - "allowSecurityEndUserReporting": llx.BoolData(teamsPolicy.AllowSecurityEndUserReporting), + "allowSecurityEndUserReporting": llx.BoolData(teamsPolicy.AllowSecurityEndUserReporting), }) if err != nil { return args, nil, err diff --git a/providers/ms365/resources/ms365.lr b/providers/ms365/resources/ms365.lr index a63c635d3f..a3b56a9e0d 100644 --- a/providers/ms365/resources/ms365.lr +++ b/providers/ms365/resources/ms365.lr @@ -344,7 +344,7 @@ private microsoft.devicemanagement.devicecompliancepolicy @defaults("id displayN properties dict } -// Microsoft 365 ExchangeOnline +// Microsoft 365 Exchange Online ms365.exchangeonline { // List of malware filter policies malwareFilterPolicy []dict @@ -382,9 +382,11 @@ ms365.exchangeonline { roleAssignmentPolicy []dict // List of external sender configurations externalInOutlook []ms365.exchangeonline.externalSender + // List of shared mailboxes + sharedMailboxes []ms365.exchangeonline.exoMailbox } -// Microsoft 365 ExchangeOnline ExternalSender +// Microsoft 365 Exchange Online External Sender private ms365.exchangeonline.externalSender { // The identity of the external sender identity string @@ -394,6 +396,16 @@ private ms365.exchangeonline.externalSender { enabled bool } +// Microsoft 365 Exchange Online Mailbox +private ms365.exchangeonline.exoMailbox { + // The identity of the mailbox + identity string + // The user linked to this mailbox + user() microsoft.user + // The identity of the external object linked to this mailbox + externalDirectoryObjectId string +} + // Microsoft 365 SharePoint Online ms365.sharepointonline { // SharePoint Online tenant diff --git a/providers/ms365/resources/ms365.lr.go b/providers/ms365/resources/ms365.lr.go index 6225fd69c6..8e0a2ad0c5 100644 --- a/providers/ms365/resources/ms365.lr.go +++ b/providers/ms365/resources/ms365.lr.go @@ -98,6 +98,10 @@ func init() { // to override args, implement: initMs365ExchangeonlineExternalSender(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) Create: createMs365ExchangeonlineExternalSender, }, + "ms365.exchangeonline.exoMailbox": { + // to override args, implement: initMs365ExchangeonlineExoMailbox(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) + Create: createMs365ExchangeonlineExoMailbox, + }, "ms365.sharepointonline": { Init: initMs365Sharepointonline, Create: createMs365Sharepointonline, @@ -642,6 +646,9 @@ var getDataFields = map[string]func(r plugin.Resource) *plugin.DataRes{ "ms365.exchangeonline.externalInOutlook": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlMs365Exchangeonline).GetExternalInOutlook()).ToDataRes(types.Array(types.Resource("ms365.exchangeonline.externalSender"))) }, + "ms365.exchangeonline.sharedMailboxes": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMs365Exchangeonline).GetSharedMailboxes()).ToDataRes(types.Array(types.Resource("ms365.exchangeonline.exoMailbox"))) + }, "ms365.exchangeonline.externalSender.identity": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlMs365ExchangeonlineExternalSender).GetIdentity()).ToDataRes(types.String) }, @@ -651,6 +658,15 @@ var getDataFields = map[string]func(r plugin.Resource) *plugin.DataRes{ "ms365.exchangeonline.externalSender.enabled": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlMs365ExchangeonlineExternalSender).GetEnabled()).ToDataRes(types.Bool) }, + "ms365.exchangeonline.exoMailbox.identity": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMs365ExchangeonlineExoMailbox).GetIdentity()).ToDataRes(types.String) + }, + "ms365.exchangeonline.exoMailbox.user": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMs365ExchangeonlineExoMailbox).GetUser()).ToDataRes(types.Resource("microsoft.user")) + }, + "ms365.exchangeonline.exoMailbox.externalDirectoryObjectId": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMs365ExchangeonlineExoMailbox).GetExternalDirectoryObjectId()).ToDataRes(types.String) + }, "ms365.sharepointonline.spoTenant": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlMs365Sharepointonline).GetSpoTenant()).ToDataRes(types.Dict) }, @@ -1422,6 +1438,10 @@ var setDataFields = map[string]func(r plugin.Resource, v *llx.RawData) bool { r.(*mqlMs365Exchangeonline).ExternalInOutlook, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) return }, + "ms365.exchangeonline.sharedMailboxes": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMs365Exchangeonline).SharedMailboxes, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) + return + }, "ms365.exchangeonline.externalSender.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { r.(*mqlMs365ExchangeonlineExternalSender).__id, ok = v.Value.(string) return @@ -1438,6 +1458,22 @@ var setDataFields = map[string]func(r plugin.Resource, v *llx.RawData) bool { r.(*mqlMs365ExchangeonlineExternalSender).Enabled, ok = plugin.RawToTValue[bool](v.Value, v.Error) return }, + "ms365.exchangeonline.exoMailbox.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMs365ExchangeonlineExoMailbox).__id, ok = v.Value.(string) + return + }, + "ms365.exchangeonline.exoMailbox.identity": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMs365ExchangeonlineExoMailbox).Identity, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "ms365.exchangeonline.exoMailbox.user": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMs365ExchangeonlineExoMailbox).User, ok = plugin.RawToTValue[*mqlMicrosoftUser](v.Value, v.Error) + return + }, + "ms365.exchangeonline.exoMailbox.externalDirectoryObjectId": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMs365ExchangeonlineExoMailbox).ExternalDirectoryObjectId, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, "ms365.sharepointonline.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { r.(*mqlMs365Sharepointonline).__id, ok = v.Value.(string) return @@ -3236,6 +3272,7 @@ type mqlMs365Exchangeonline struct { SharingPolicy plugin.TValue[[]interface{}] RoleAssignmentPolicy plugin.TValue[[]interface{}] ExternalInOutlook plugin.TValue[[]interface{}] + SharedMailboxes plugin.TValue[[]interface{}] } // createMs365Exchangeonline creates a new instance of this resource @@ -3342,6 +3379,10 @@ func (c *mqlMs365Exchangeonline) GetExternalInOutlook() *plugin.TValue[[]interfa return &c.ExternalInOutlook } +func (c *mqlMs365Exchangeonline) GetSharedMailboxes() *plugin.TValue[[]interface{}] { + return &c.SharedMailboxes +} + // mqlMs365ExchangeonlineExternalSender for the ms365.exchangeonline.externalSender resource type mqlMs365ExchangeonlineExternalSender struct { MqlRuntime *plugin.Runtime @@ -3401,6 +3442,77 @@ func (c *mqlMs365ExchangeonlineExternalSender) GetEnabled() *plugin.TValue[bool] return &c.Enabled } +// mqlMs365ExchangeonlineExoMailbox for the ms365.exchangeonline.exoMailbox resource +type mqlMs365ExchangeonlineExoMailbox struct { + MqlRuntime *plugin.Runtime + __id string + // optional: if you define mqlMs365ExchangeonlineExoMailboxInternal it will be used here + Identity plugin.TValue[string] + User plugin.TValue[*mqlMicrosoftUser] + ExternalDirectoryObjectId plugin.TValue[string] +} + +// createMs365ExchangeonlineExoMailbox creates a new instance of this resource +func createMs365ExchangeonlineExoMailbox(runtime *plugin.Runtime, args map[string]*llx.RawData) (plugin.Resource, error) { + res := &mqlMs365ExchangeonlineExoMailbox{ + MqlRuntime: runtime, + } + + err := SetAllData(res, args) + if err != nil { + return res, err + } + + if res.__id == "" { + res.__id, err = res.id() + if err != nil { + return nil, err + } + } + + if runtime.HasRecording { + args, err = runtime.ResourceFromRecording("ms365.exchangeonline.exoMailbox", res.__id) + if err != nil || args == nil { + return res, err + } + return res, SetAllData(res, args) + } + + return res, nil +} + +func (c *mqlMs365ExchangeonlineExoMailbox) MqlName() string { + return "ms365.exchangeonline.exoMailbox" +} + +func (c *mqlMs365ExchangeonlineExoMailbox) MqlID() string { + return c.__id +} + +func (c *mqlMs365ExchangeonlineExoMailbox) GetIdentity() *plugin.TValue[string] { + return &c.Identity +} + +func (c *mqlMs365ExchangeonlineExoMailbox) GetUser() *plugin.TValue[*mqlMicrosoftUser] { + return plugin.GetOrCompute[*mqlMicrosoftUser](&c.User, func() (*mqlMicrosoftUser, error) { + if c.MqlRuntime.HasRecording { + d, err := c.MqlRuntime.FieldResourceFromRecording("ms365.exchangeonline.exoMailbox", c.__id, "user") + if err != nil { + return nil, err + } + if d != nil { + return d.Value.(*mqlMicrosoftUser), nil + } + } + + return c.user() + }) +} + +func (c *mqlMs365ExchangeonlineExoMailbox) GetExternalDirectoryObjectId() *plugin.TValue[string] { + return &c.ExternalDirectoryObjectId +} + // mqlMs365Sharepointonline for the ms365.sharepointonline resource type mqlMs365Sharepointonline struct { MqlRuntime *plugin.Runtime diff --git a/providers/ms365/resources/ms365.lr.manifest.yaml b/providers/ms365/resources/ms365.lr.manifest.yaml index 7aaa2eaaa3..75b6819bb0 100755 --- a/providers/ms365/resources/ms365.lr.manifest.yaml +++ b/providers/ms365/resources/ms365.lr.manifest.yaml @@ -257,12 +257,24 @@ resources: roleAssignmentPolicy: {} safeAttachmentPolicy: {} safeLinksPolicy: {} + sharedMailboxes: + min_mondoo_version: 9.2.13 sharingPolicy: {} transportRule: {} min_mondoo_version: 5.15.0 platform: name: - microsoft365 + ms365.exchangeonline.exoMailbox: + fields: + externalDirectoryObjectId: {} + identity: {} + user: {} + is_private: true + min_mondoo_version: 9.2.13 + platform: + name: + - microsoft365 ms365.exchangeonline.externalSender: fields: allowList: {}