From 54e8063409fd97b42bde5969835c594644723007 Mon Sep 17 00:00:00 2001 From: Christoph Hartmann Date: Mon, 19 Aug 2024 23:22:49 +0200 Subject: [PATCH] =?UTF-8?q?=E2=AD=90=EF=B8=8F=20Microsoft=20365=20risky=20?= =?UTF-8?q?users=20resource=20(#4570)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- providers/ms365/resources/ms365.lr | 22 +++ providers/ms365/resources/ms365.lr.go | 179 ++++++++++++++++++ .../ms365/resources/ms365.lr.manifest.yaml | 13 ++ .../ms365/resources/security_riskyusers.go | 98 ++++++++++ ...curescores.go => security_securescores.go} | 4 +- providers/ms365/resources/users.go | 3 + 6 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 providers/ms365/resources/security_riskyusers.go rename providers/ms365/resources/{securescores.go => security_securescores.go} (95%) diff --git a/providers/ms365/resources/ms365.lr b/providers/ms365/resources/ms365.lr index 00b8942d3f..2b86ea3f09 100644 --- a/providers/ms365/resources/ms365.lr +++ b/providers/ms365/resources/ms365.lr @@ -395,6 +395,8 @@ microsoft.security { secureScores() []microsoft.security.securityscore // Latest security score latestSecureScores() microsoft.security.securityscore + // List Microsoft Entra users who are at risk + riskyUsers() []microsoft.security.riskyUser } // Microsoft Secure Score @@ -423,6 +425,26 @@ private microsoft.security.securityscore @defaults("id azureTenantId") { vendorInformation dict } +// Microsoft Entra users who are at risk +microsoft.security.riskyUser @defaults("principalName riskLevel riskState lastUpdatedAt"){ + // Risky user ID + id string + // User name + name string + // User principal + principalName string + // Entra User + user() microsoft.user + // Risk detail + riskDetail string + // Risk level + riskLevel string + // Risk state + riskState string + // Risk last updated + lastUpdatedAt time +} + // Microsoft policies microsoft.policies { // Authorization policy diff --git a/providers/ms365/resources/ms365.lr.go b/providers/ms365/resources/ms365.lr.go index 9ed3be0bcd..6bed5886ce 100644 --- a/providers/ms365/resources/ms365.lr.go +++ b/providers/ms365/resources/ms365.lr.go @@ -78,6 +78,10 @@ func init() { // to override args, implement: initMicrosoftSecuritySecurityscore(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) Create: createMicrosoftSecuritySecurityscore, }, + "microsoft.security.riskyUser": { + // to override args, implement: initMicrosoftSecurityRiskyUser(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) + Create: createMicrosoftSecurityRiskyUser, + }, "microsoft.policies": { // to override args, implement: initMicrosoftPolicies(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) Create: createMicrosoftPolicies, @@ -722,6 +726,9 @@ var getDataFields = map[string]func(r plugin.Resource) *plugin.DataRes{ "microsoft.security.latestSecureScores": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlMicrosoftSecurity).GetLatestSecureScores()).ToDataRes(types.Resource("microsoft.security.securityscore")) }, + "microsoft.security.riskyUsers": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftSecurity).GetRiskyUsers()).ToDataRes(types.Array(types.Resource("microsoft.security.riskyUser"))) + }, "microsoft.security.securityscore.id": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlMicrosoftSecuritySecurityscore).GetId()).ToDataRes(types.String) }, @@ -755,6 +762,30 @@ var getDataFields = map[string]func(r plugin.Resource) *plugin.DataRes{ "microsoft.security.securityscore.vendorInformation": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlMicrosoftSecuritySecurityscore).GetVendorInformation()).ToDataRes(types.Dict) }, + "microsoft.security.riskyUser.id": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftSecurityRiskyUser).GetId()).ToDataRes(types.String) + }, + "microsoft.security.riskyUser.name": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftSecurityRiskyUser).GetName()).ToDataRes(types.String) + }, + "microsoft.security.riskyUser.principalName": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftSecurityRiskyUser).GetPrincipalName()).ToDataRes(types.String) + }, + "microsoft.security.riskyUser.user": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftSecurityRiskyUser).GetUser()).ToDataRes(types.Resource("microsoft.user")) + }, + "microsoft.security.riskyUser.riskDetail": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftSecurityRiskyUser).GetRiskDetail()).ToDataRes(types.String) + }, + "microsoft.security.riskyUser.riskLevel": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftSecurityRiskyUser).GetRiskLevel()).ToDataRes(types.String) + }, + "microsoft.security.riskyUser.riskState": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftSecurityRiskyUser).GetRiskState()).ToDataRes(types.String) + }, + "microsoft.security.riskyUser.lastUpdatedAt": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftSecurityRiskyUser).GetLastUpdatedAt()).ToDataRes(types.Time) + }, "microsoft.policies.authorizationPolicy": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlMicrosoftPolicies).GetAuthorizationPolicy()).ToDataRes(types.Dict) }, @@ -1795,6 +1826,10 @@ var setDataFields = map[string]func(r plugin.Resource, v *llx.RawData) bool { r.(*mqlMicrosoftSecurity).LatestSecureScores, ok = plugin.RawToTValue[*mqlMicrosoftSecuritySecurityscore](v.Value, v.Error) return }, + "microsoft.security.riskyUsers": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftSecurity).RiskyUsers, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) + return + }, "microsoft.security.securityscore.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { r.(*mqlMicrosoftSecuritySecurityscore).__id, ok = v.Value.(string) return @@ -1843,6 +1878,42 @@ var setDataFields = map[string]func(r plugin.Resource, v *llx.RawData) bool { r.(*mqlMicrosoftSecuritySecurityscore).VendorInformation, ok = plugin.RawToTValue[interface{}](v.Value, v.Error) return }, + "microsoft.security.riskyUser.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftSecurityRiskyUser).__id, ok = v.Value.(string) + return + }, + "microsoft.security.riskyUser.id": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftSecurityRiskyUser).Id, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "microsoft.security.riskyUser.name": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftSecurityRiskyUser).Name, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "microsoft.security.riskyUser.principalName": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftSecurityRiskyUser).PrincipalName, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "microsoft.security.riskyUser.user": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftSecurityRiskyUser).User, ok = plugin.RawToTValue[*mqlMicrosoftUser](v.Value, v.Error) + return + }, + "microsoft.security.riskyUser.riskDetail": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftSecurityRiskyUser).RiskDetail, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "microsoft.security.riskyUser.riskLevel": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftSecurityRiskyUser).RiskLevel, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "microsoft.security.riskyUser.riskState": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftSecurityRiskyUser).RiskState, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "microsoft.security.riskyUser.lastUpdatedAt": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftSecurityRiskyUser).LastUpdatedAt, ok = plugin.RawToTValue[*time.Time](v.Value, v.Error) + return + }, "microsoft.policies.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { r.(*mqlMicrosoftPolicies).__id, ok = v.Value.(string) return @@ -3883,6 +3954,7 @@ type mqlMicrosoftSecurity struct { // optional: if you define mqlMicrosoftSecurityInternal it will be used here SecureScores plugin.TValue[[]interface{}] LatestSecureScores plugin.TValue[*mqlMicrosoftSecuritySecurityscore] + RiskyUsers plugin.TValue[[]interface{}] } // createMicrosoftSecurity creates a new instance of this resource @@ -3949,6 +4021,22 @@ func (c *mqlMicrosoftSecurity) GetLatestSecureScores() *plugin.TValue[*mqlMicros }) } +func (c *mqlMicrosoftSecurity) GetRiskyUsers() *plugin.TValue[[]interface{}] { + return plugin.GetOrCompute[[]interface{}](&c.RiskyUsers, func() ([]interface{}, error) { + if c.MqlRuntime.HasRecording { + d, err := c.MqlRuntime.FieldResourceFromRecording("microsoft.security", c.__id, "riskyUsers") + if err != nil { + return nil, err + } + if d != nil { + return d.Value.([]interface{}), nil + } + } + + return c.riskyUsers() + }) +} + // mqlMicrosoftSecuritySecurityscore for the microsoft.security.securityscore resource type mqlMicrosoftSecuritySecurityscore struct { MqlRuntime *plugin.Runtime @@ -4048,6 +4136,97 @@ func (c *mqlMicrosoftSecuritySecurityscore) GetVendorInformation() *plugin.TValu return &c.VendorInformation } +// mqlMicrosoftSecurityRiskyUser for the microsoft.security.riskyUser resource +type mqlMicrosoftSecurityRiskyUser struct { + MqlRuntime *plugin.Runtime + __id string + // optional: if you define mqlMicrosoftSecurityRiskyUserInternal it will be used here + Id plugin.TValue[string] + Name plugin.TValue[string] + PrincipalName plugin.TValue[string] + User plugin.TValue[*mqlMicrosoftUser] + RiskDetail plugin.TValue[string] + RiskLevel plugin.TValue[string] + RiskState plugin.TValue[string] + LastUpdatedAt plugin.TValue[*time.Time] +} + +// createMicrosoftSecurityRiskyUser creates a new instance of this resource +func createMicrosoftSecurityRiskyUser(runtime *plugin.Runtime, args map[string]*llx.RawData) (plugin.Resource, error) { + res := &mqlMicrosoftSecurityRiskyUser{ + MqlRuntime: runtime, + } + + err := SetAllData(res, args) + if err != nil { + return res, err + } + + // to override __id implement: id() (string, error) + + if runtime.HasRecording { + args, err = runtime.ResourceFromRecording("microsoft.security.riskyUser", res.__id) + if err != nil || args == nil { + return res, err + } + return res, SetAllData(res, args) + } + + return res, nil +} + +func (c *mqlMicrosoftSecurityRiskyUser) MqlName() string { + return "microsoft.security.riskyUser" +} + +func (c *mqlMicrosoftSecurityRiskyUser) MqlID() string { + return c.__id +} + +func (c *mqlMicrosoftSecurityRiskyUser) GetId() *plugin.TValue[string] { + return &c.Id +} + +func (c *mqlMicrosoftSecurityRiskyUser) GetName() *plugin.TValue[string] { + return &c.Name +} + +func (c *mqlMicrosoftSecurityRiskyUser) GetPrincipalName() *plugin.TValue[string] { + return &c.PrincipalName +} + +func (c *mqlMicrosoftSecurityRiskyUser) GetUser() *plugin.TValue[*mqlMicrosoftUser] { + return plugin.GetOrCompute[*mqlMicrosoftUser](&c.User, func() (*mqlMicrosoftUser, error) { + if c.MqlRuntime.HasRecording { + d, err := c.MqlRuntime.FieldResourceFromRecording("microsoft.security.riskyUser", c.__id, "user") + if err != nil { + return nil, err + } + if d != nil { + return d.Value.(*mqlMicrosoftUser), nil + } + } + + return c.user() + }) +} + +func (c *mqlMicrosoftSecurityRiskyUser) GetRiskDetail() *plugin.TValue[string] { + return &c.RiskDetail +} + +func (c *mqlMicrosoftSecurityRiskyUser) GetRiskLevel() *plugin.TValue[string] { + return &c.RiskLevel +} + +func (c *mqlMicrosoftSecurityRiskyUser) GetRiskState() *plugin.TValue[string] { + return &c.RiskState +} + +func (c *mqlMicrosoftSecurityRiskyUser) GetLastUpdatedAt() *plugin.TValue[*time.Time] { + return &c.LastUpdatedAt +} + // mqlMicrosoftPolicies for the microsoft.policies resource type mqlMicrosoftPolicies struct { MqlRuntime *plugin.Runtime diff --git a/providers/ms365/resources/ms365.lr.manifest.yaml b/providers/ms365/resources/ms365.lr.manifest.yaml index 667d077e90..c4c397305d 100755 --- a/providers/ms365/resources/ms365.lr.manifest.yaml +++ b/providers/ms365/resources/ms365.lr.manifest.yaml @@ -254,8 +254,21 @@ resources: microsoft.security: fields: latestSecureScores: {} + riskyUsers: + min_mondoo_version: 9.0.0 secureScores: {} min_mondoo_version: 5.15.0 + microsoft.security.riskyUser: + fields: + id: {} + lastUpdatedAt: {} + name: {} + principalName: {} + riskDetail: {} + riskLevel: {} + riskState: {} + user: {} + min_mondoo_version: 9.0.0 microsoft.security.securityscore: fields: activeUserCount: {} diff --git a/providers/ms365/resources/security_riskyusers.go b/providers/ms365/resources/security_riskyusers.go new file mode 100644 index 0000000000..5013929dd3 --- /dev/null +++ b/providers/ms365/resources/security_riskyusers.go @@ -0,0 +1,98 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package resources + +import ( + "context" + "github.com/microsoftgraph/msgraph-sdk-go/identityprotection" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "go.mondoo.com/cnquery/v11/llx" + "go.mondoo.com/cnquery/v11/providers-sdk/v1/plugin" + "go.mondoo.com/cnquery/v11/providers/ms365/connection" +) + +// riskyUsers returns a list of risky users +// requires IdentityRiskyUser.Read.All permission +// see https://learn.microsoft.com/en-us/graph/api/resources/riskyuser?view=graph-rest-1.0 +func (a *mqlMicrosoftSecurity) riskyUsers() ([]interface{}, error) { + conn := a.MqlRuntime.Connection.(*connection.Ms365Connection) + graphClient, err := conn.GraphClient() + if err != nil { + return nil, err + } + ctx := context.Background() + + filter := "riskState eq 'atRisk'" + resp, err := graphClient.IdentityProtection().RiskyUsers().Get(ctx, &identityprotection.RiskyUsersRequestBuilderGetRequestConfiguration{ + QueryParameters: &identityprotection.RiskyUsersRequestBuilderGetQueryParameters{ + Filter: &filter, + }, + }) + if err != nil { + return nil, transformError(err) + } + + res := []interface{}{} + users := resp.GetValue() + for i := range users { + riskyUser := users[i] + mqlResource, err := newMqlMicrosoftRiskyUser(a.MqlRuntime, riskyUser) + if err != nil { + return nil, err + } + + res = append(res, mqlResource) + } + return res, nil +} + +func newMqlMicrosoftRiskyUser(runtime *plugin.Runtime, riskyUser models.RiskyUserable) (*mqlMicrosoftSecurityRiskyUser, error) { + if riskyUser == nil { + return nil, nil + } + + var detail *string + if riskyUser.GetRiskDetail() != nil { + detailData := riskyUser.GetRiskDetail().String() + detail = &detailData + } + + var riskLevel *string + if riskyUser.GetRiskLevel() != nil { + riskLevelData := riskyUser.GetRiskLevel().String() + riskLevel = &riskLevelData + } + + var riskState *string + if riskyUser.GetRiskState() != nil { + riskStateData := riskyUser.GetRiskState().String() + riskState = &riskStateData + } + + mqlResource, err := CreateResource(runtime, "microsoft.security.riskyUser", + map[string]*llx.RawData{ + "__id": llx.StringDataPtr(riskyUser.GetId()), + "id": llx.StringDataPtr(riskyUser.GetId()), + "name": llx.StringDataPtr(riskyUser.GetUserDisplayName()), + "principalName": llx.StringDataPtr(riskyUser.GetUserPrincipalName()), + "riskDetail": llx.StringDataPtr(detail), + "riskLevel": llx.StringDataPtr(riskLevel), + "riskState": llx.StringDataPtr(riskState), + "lastUpdatedAt": llx.TimeDataPtr(riskyUser.GetRiskLastUpdatedDateTime()), + }) + if err != nil { + return nil, err + } + return mqlResource.(*mqlMicrosoftSecurityRiskyUser), nil +} + +func (r *mqlMicrosoftSecurityRiskyUser) user() (*mqlMicrosoftUser, error) { + user, err := NewResource(r.MqlRuntime, "microsoft.user", map[string]*llx.RawData{ + "id": llx.StringData(r.Id.Data), + }) + if err != nil { + return nil, err + } + return user.(*mqlMicrosoftUser), nil +} diff --git a/providers/ms365/resources/securescores.go b/providers/ms365/resources/security_securescores.go similarity index 95% rename from providers/ms365/resources/securescores.go rename to providers/ms365/resources/security_securescores.go index 6c32ee3d22..9446074da0 100644 --- a/providers/ms365/resources/securescores.go +++ b/providers/ms365/resources/security_securescores.go @@ -20,7 +20,7 @@ func (m *mqlMicrosoftSecuritySecurityscore) id() (string, error) { return m.Id.Data, nil } -func msSecureScoreToMql(runtime *plugin.Runtime, score models.SecureScoreable) (*mqlMicrosoftSecuritySecurityscore, error) { +func newMqlMicrosoftSecureScore(runtime *plugin.Runtime, score models.SecureScoreable) (*mqlMicrosoftSecuritySecurityscore, error) { if score == nil { return nil, nil } @@ -109,7 +109,7 @@ func (a *mqlMicrosoftSecurity) secureScores() ([]interface{}, error) { scores := resp.GetValue() for i := range scores { score := scores[i] - mqlResource, err := msSecureScoreToMql(a.MqlRuntime, score) + mqlResource, err := newMqlMicrosoftSecureScore(a.MqlRuntime, score) if err != nil { return nil, err } diff --git a/providers/ms365/resources/users.go b/providers/ms365/resources/users.go index 9ac7651667..93d8c480df 100644 --- a/providers/ms365/resources/users.go +++ b/providers/ms365/resources/users.go @@ -22,6 +22,9 @@ var userSelectFields = []string{ "jobTitle", "mail", "mobilePhone", "otherMails", "officeLocation", "postalCode", "state", "streetAddress", "surname", "userPrincipalName", "userType", } +// users reads all users from Entra ID +// Permissions: User.Read.All, Directory.Read.All +// see https://learn.microsoft.com/en-us/graph/api/user-list?view=graph-rest-1.0&tabs=http func (a *mqlMicrosoft) users() ([]interface{}, error) { conn := a.MqlRuntime.Connection.(*connection.Ms365Connection) graphClient, err := conn.GraphClient()