From 5367de59f8a7a2f8fa374513d87e469aa3bd924a Mon Sep 17 00:00:00 2001 From: Christoph Hartmann Date: Fri, 16 Aug 2024 19:10:22 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=B9=20improve=20azure=20policy=20assig?= =?UTF-8?q?nments=20resource?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- providers/azure/resources/armsecurity.go | 228 ++++++++++++++++++ providers/azure/resources/azure.lr | 16 +- providers/azure/resources/azure.lr.go | 202 ++++++++++++++++ .../azure/resources/azure.lr.manifest.yaml | 12 +- providers/azure/resources/cloud_defender.go | 217 ++--------------- providers/azure/resources/policy.go | 32 +-- 6 files changed, 468 insertions(+), 239 deletions(-) create mode 100644 providers/azure/resources/armsecurity.go diff --git a/providers/azure/resources/armsecurity.go b/providers/azure/resources/armsecurity.go new file mode 100644 index 0000000000..e8be3b81e0 --- /dev/null +++ b/providers/azure/resources/armsecurity.go @@ -0,0 +1,228 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package resources + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + security "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/security/armsecurity" + "go.mondoo.com/cnquery/v11/providers/azure/connection" +) + +type armSecurityConn struct { + subscriptionId string + host string + token azcore.TokenCredential +} + +func (a armSecurityConn) GetToken() (azcore.AccessToken, error) { + return a.token.GetToken(context.Background(), policy.TokenRequestOptions{ + Scopes: []string{"https://management.core.windows.net//.default"}, + }) +} + +func getArmSecurityConnection(ctx context.Context, conn *connection.AzureConnection, subId string) (armSecurityConn, error) { + token := conn.Token() + + ep := cloud.AzurePublic.Services[cloud.ResourceManager].Endpoint + return armSecurityConn{subId, ep, token}, nil +} + +func getPolicyAssignments(ctx context.Context, conn armSecurityConn) (PolicyAssignments, error) { + token, err := conn.GetToken() + if err != nil { + return PolicyAssignments{}, err + } + urlPath := "/subscriptions/{subscriptionId}/providers/Microsoft.Authorization/policyAssignments" + urlPath = strings.ReplaceAll(urlPath, "{subscriptionId}", url.PathEscape(conn.subscriptionId)) + urlPath = runtime.JoinPaths(conn.host, urlPath) + client := http.Client{} + req, err := http.NewRequest("GET", urlPath, nil) + if err != nil { + return PolicyAssignments{}, err + } + q := req.URL.Query() + q.Set("api-version", "2022-06-01") + req.URL.RawQuery = q.Encode() + req.Header.Set("Accept", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.Token)) + + resp, err := client.Do(req) + if err != nil { + return PolicyAssignments{}, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return PolicyAssignments{}, errors.New("failed to fetch security contacts from " + urlPath + ": " + resp.Status) + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return PolicyAssignments{}, err + } + result := PolicyAssignments{} + err = json.Unmarshal(raw, &result) + return result, err +} + +// the armsecurity.NewListPager is broken, see https://github.com/Azure/azure-sdk-for-go/issues/19740. +// until it's fixed, we can fetch them manually +func getSecurityContacts(ctx context.Context, conn armSecurityConn) ([]security.Contact, error) { + token, err := conn.GetToken() + if err != nil { + return []security.Contact{}, err + } + urlPath := "/subscriptions/{subscriptionId}/providers/Microsoft.Security/securityContacts" + urlPath = strings.ReplaceAll(urlPath, "{subscriptionId}", url.PathEscape(conn.subscriptionId)) + urlPath = runtime.JoinPaths(conn.host, urlPath) + client := http.Client{} + req, err := http.NewRequest("GET", urlPath, nil) + if err != nil { + return []security.Contact{}, err + } + q := req.URL.Query() + q.Set("api-version", "2020-01-01-preview") + req.URL.RawQuery = q.Encode() + req.Header.Set("Accept", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.Token)) + + resp, err := client.Do(req) + if err != nil { + return []security.Contact{}, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return []security.Contact{}, errors.New("failed to fetch security contacts from " + urlPath + ": " + resp.Status) + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return []security.Contact{}, err + } + result := []security.Contact{} + err = json.Unmarshal(raw, &result) + if err != nil { + // fallback, try to unmarshal to ContactList + contactList := &security.ContactList{} + err = json.Unmarshal(raw, contactList) + if err != nil { + return nil, err + } + for _, c := range contactList.Value { + if c != nil { + result = append(result, *c) + } + } + } + + return result, err +} + +func getServerVulnAssessmentSettings(ctx context.Context, conn armSecurityConn) (ServerVulnerabilityAssessmentsSettingsList, error) { + token, err := conn.GetToken() + if err != nil { + return ServerVulnerabilityAssessmentsSettingsList{}, err + } + urlPath := "/subscriptions/{subscriptionId}/providers/Microsoft.Security/serverVulnerabilityAssessmentsSettings" + urlPath = strings.ReplaceAll(urlPath, "{subscriptionId}", url.PathEscape(conn.subscriptionId)) + urlPath = runtime.JoinPaths(conn.host, urlPath) + client := http.Client{} + req, err := http.NewRequest("GET", urlPath, nil) + if err != nil { + return ServerVulnerabilityAssessmentsSettingsList{}, err + } + q := req.URL.Query() + q.Set("api-version", "2022-01-01-preview") + req.URL.RawQuery = q.Encode() + req.Header.Set("Accept", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.Token)) + + resp, err := client.Do(req) + if err != nil { + return ServerVulnerabilityAssessmentsSettingsList{}, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return ServerVulnerabilityAssessmentsSettingsList{}, errors.New("failed to fetch security contacts from " + urlPath + ": " + resp.Status) + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return ServerVulnerabilityAssessmentsSettingsList{}, err + } + result := ServerVulnerabilityAssessmentsSettingsList{} + err = json.Unmarshal(raw, &result) + return result, err +} + +// https://learn.microsoft.com/en-us/azure/templates/microsoft.authorization/policyassignments?pivots=deployment-language-bicep#property-values +type PolicyAssignment struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Location string `json:"location,omitempty"` + Identity struct { + Type string `json:"type"` + PrincipalID string `json:"principalId"` + TenantID string `json:"tenantId"` + } `json:"identity,omitempty"` + Properties struct { + DisplayName string `json:"displayName"` + Description string `json:"description"` + AssignmentType string `json:"assignmentType"` + EnforcementMode string `json:"enforcementMode"` + Metadata struct { + Category string `json:"category"` + } `json:"metadata"` + PolicyDefinitionID string `json:"policyDefinitionId"` + Parameters struct { + AllowedSkus struct { + Value string `json:"value"` + } `json:"allowedSkus"` + } `json:"parameters"` + Scope string `json:"scope"` + NotScopes []interface{} `json:"notScopes"` + } `json:"properties"` +} + +type PolicyAssignments struct { + PolicyAssignments []PolicyAssignment `json:"value"` +} + +type ServerVulnerabilityAssessmentsSettings struct { + Properties struct { + SelectedProvider string `json:"selectedProvider"` + } `json:"properties"` + SystemData struct { + CreatedBy string `json:"createdBy"` + CreatedByType string `json:"createdByType"` + CreatedAt time.Time `json:"createdAt"` + LastModifiedBy string `json:"lastModifiedBy"` + LastModifiedByType string `json:"lastModifiedByType"` + LastModifiedAt time.Time `json:"lastModifiedAt"` + } `json:"systemData"` + Kind string `json:"kind"` + Name string `json:"name"` + Type string `json:"type"` + ID string `json:"id"` +} + +type ServerVulnerabilityAssessmentsSettingsList struct { + Settings []ServerVulnerabilityAssessmentsSettings `json:"value"` +} diff --git a/providers/azure/resources/azure.lr b/providers/azure/resources/azure.lr index 8ffb3ec3d9..86ed97c740 100644 --- a/providers/azure/resources/azure.lr +++ b/providers/azure/resources/azure.lr @@ -1913,15 +1913,19 @@ private azure.subscription.policy @defaults("subscriptionId") { // Subscription identifier subscriptionId string // List of policy assignments in the subscription - policyAssignments() []azure.subscription.policy.assignment + assignments() []azure.subscription.policy.assignment } // Azure Policy Assignment -private azure.subscription.policy.assignment @defaults("name scope type") { - // Policy Assignment Name +private azure.subscription.policy.assignment @defaults("name enforcementMode") { + // Policy Definition ID + id string + // Policy Name name string - // Policy Assignment Scope + // Policy Scope scope string - // Policy Assignment Type - type string + // Policy Description + description string + // Policy Enforcement Mode + enforcementMode string } \ No newline at end of file diff --git a/providers/azure/resources/azure.lr.go b/providers/azure/resources/azure.lr.go index 18567348ed..f90bbee54b 100644 --- a/providers/azure/resources/azure.lr.go +++ b/providers/azure/resources/azure.lr.go @@ -410,6 +410,14 @@ func init() { // to override args, implement: initAzureSubscriptionAdvisorServiceSecurityScore(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) Create: createAzureSubscriptionAdvisorServiceSecurityScore, }, + "azure.subscription.policy": { + Init: initAzureSubscriptionPolicy, + Create: createAzureSubscriptionPolicy, + }, + "azure.subscription.policy.assignment": { + // to override args, implement: initAzureSubscriptionPolicyAssignment(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) + Create: createAzureSubscriptionPolicyAssignment, + }, } } @@ -556,6 +564,9 @@ var getDataFields = map[string]func(r plugin.Resource) *plugin.DataRes{ "azure.subscription.advisor": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlAzureSubscription).GetAdvisor()).ToDataRes(types.Resource("azure.subscription.advisorService")) }, + "azure.subscription.policy": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlAzureSubscription).GetPolicy()).ToDataRes(types.Resource("azure.subscription.policy")) + }, "azure.subscription.resourcegroup.id": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlAzureSubscriptionResourcegroup).GetId()).ToDataRes(types.String) }, @@ -2743,6 +2754,27 @@ var getDataFields = map[string]func(r plugin.Resource) *plugin.DataRes{ "azure.subscription.advisorService.securityScore.consumptionUnits": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlAzureSubscriptionAdvisorServiceSecurityScore).GetConsumptionUnits()).ToDataRes(types.Float) }, + "azure.subscription.policy.subscriptionId": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlAzureSubscriptionPolicy).GetSubscriptionId()).ToDataRes(types.String) + }, + "azure.subscription.policy.assignments": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlAzureSubscriptionPolicy).GetAssignments()).ToDataRes(types.Array(types.Resource("azure.subscription.policy.assignment"))) + }, + "azure.subscription.policy.assignment.id": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlAzureSubscriptionPolicyAssignment).GetId()).ToDataRes(types.String) + }, + "azure.subscription.policy.assignment.name": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlAzureSubscriptionPolicyAssignment).GetName()).ToDataRes(types.String) + }, + "azure.subscription.policy.assignment.scope": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlAzureSubscriptionPolicyAssignment).GetScope()).ToDataRes(types.String) + }, + "azure.subscription.policy.assignment.description": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlAzureSubscriptionPolicyAssignment).GetDescription()).ToDataRes(types.String) + }, + "azure.subscription.policy.assignment.enforcementMode": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlAzureSubscriptionPolicyAssignment).GetEnforcementMode()).ToDataRes(types.String) + }, } func GetData(resource plugin.Resource, field string, args map[string]*llx.RawData) *plugin.DataRes { @@ -2867,6 +2899,10 @@ var setDataFields = map[string]func(r plugin.Resource, v *llx.RawData) bool { r.(*mqlAzureSubscription).Advisor, ok = plugin.RawToTValue[*mqlAzureSubscriptionAdvisorService](v.Value, v.Error) return }, + "azure.subscription.policy": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlAzureSubscription).Policy, ok = plugin.RawToTValue[*mqlAzureSubscriptionPolicy](v.Value, v.Error) + return + }, "azure.subscription.resourcegroup.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { r.(*mqlAzureSubscriptionResourcegroup).__id, ok = v.Value.(string) return @@ -6167,6 +6203,42 @@ var setDataFields = map[string]func(r plugin.Resource, v *llx.RawData) bool { r.(*mqlAzureSubscriptionAdvisorServiceSecurityScore).ConsumptionUnits, ok = plugin.RawToTValue[float64](v.Value, v.Error) return }, + "azure.subscription.policy.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlAzureSubscriptionPolicy).__id, ok = v.Value.(string) + return + }, + "azure.subscription.policy.subscriptionId": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlAzureSubscriptionPolicy).SubscriptionId, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "azure.subscription.policy.assignments": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlAzureSubscriptionPolicy).Assignments, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) + return + }, + "azure.subscription.policy.assignment.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlAzureSubscriptionPolicyAssignment).__id, ok = v.Value.(string) + return + }, + "azure.subscription.policy.assignment.id": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlAzureSubscriptionPolicyAssignment).Id, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "azure.subscription.policy.assignment.name": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlAzureSubscriptionPolicyAssignment).Name, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "azure.subscription.policy.assignment.scope": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlAzureSubscriptionPolicyAssignment).Scope, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "azure.subscription.policy.assignment.description": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlAzureSubscriptionPolicyAssignment).Description, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "azure.subscription.policy.assignment.enforcementMode": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlAzureSubscriptionPolicyAssignment).EnforcementMode, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, } func SetData(resource plugin.Resource, field string, val *llx.RawData) error { @@ -6261,6 +6333,7 @@ type mqlAzureSubscription struct { CloudDefender plugin.TValue[*mqlAzureSubscriptionCloudDefenderService] Aks plugin.TValue[*mqlAzureSubscriptionAksService] Advisor plugin.TValue[*mqlAzureSubscriptionAdvisorService] + Policy plugin.TValue[*mqlAzureSubscriptionPolicy] } // createAzureSubscription creates a new instance of this resource @@ -6608,6 +6681,10 @@ func (c *mqlAzureSubscription) GetAdvisor() *plugin.TValue[*mqlAzureSubscription }) } +func (c *mqlAzureSubscription) GetPolicy() *plugin.TValue[*mqlAzureSubscriptionPolicy] { + return &c.Policy +} + // mqlAzureSubscriptionResourcegroup for the azure.subscription.resourcegroup resource type mqlAzureSubscriptionResourcegroup struct { MqlRuntime *plugin.Runtime @@ -15638,3 +15715,128 @@ func (c *mqlAzureSubscriptionAdvisorServiceSecurityScore) GetCategoryCount() *pl func (c *mqlAzureSubscriptionAdvisorServiceSecurityScore) GetConsumptionUnits() *plugin.TValue[float64] { return &c.ConsumptionUnits } + +// mqlAzureSubscriptionPolicy for the azure.subscription.policy resource +type mqlAzureSubscriptionPolicy struct { + MqlRuntime *plugin.Runtime + __id string + // optional: if you define mqlAzureSubscriptionPolicyInternal it will be used here + SubscriptionId plugin.TValue[string] + Assignments plugin.TValue[[]interface{}] +} + +// createAzureSubscriptionPolicy creates a new instance of this resource +func createAzureSubscriptionPolicy(runtime *plugin.Runtime, args map[string]*llx.RawData) (plugin.Resource, error) { + res := &mqlAzureSubscriptionPolicy{ + 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("azure.subscription.policy", res.__id) + if err != nil || args == nil { + return res, err + } + return res, SetAllData(res, args) + } + + return res, nil +} + +func (c *mqlAzureSubscriptionPolicy) MqlName() string { + return "azure.subscription.policy" +} + +func (c *mqlAzureSubscriptionPolicy) MqlID() string { + return c.__id +} + +func (c *mqlAzureSubscriptionPolicy) GetSubscriptionId() *plugin.TValue[string] { + return &c.SubscriptionId +} + +func (c *mqlAzureSubscriptionPolicy) GetAssignments() *plugin.TValue[[]interface{}] { + return plugin.GetOrCompute[[]interface{}](&c.Assignments, func() ([]interface{}, error) { + if c.MqlRuntime.HasRecording { + d, err := c.MqlRuntime.FieldResourceFromRecording("azure.subscription.policy", c.__id, "assignments") + if err != nil { + return nil, err + } + if d != nil { + return d.Value.([]interface{}), nil + } + } + + return c.assignments() + }) +} + +// mqlAzureSubscriptionPolicyAssignment for the azure.subscription.policy.assignment resource +type mqlAzureSubscriptionPolicyAssignment struct { + MqlRuntime *plugin.Runtime + __id string + // optional: if you define mqlAzureSubscriptionPolicyAssignmentInternal it will be used here + Id plugin.TValue[string] + Name plugin.TValue[string] + Scope plugin.TValue[string] + Description plugin.TValue[string] + EnforcementMode plugin.TValue[string] +} + +// createAzureSubscriptionPolicyAssignment creates a new instance of this resource +func createAzureSubscriptionPolicyAssignment(runtime *plugin.Runtime, args map[string]*llx.RawData) (plugin.Resource, error) { + res := &mqlAzureSubscriptionPolicyAssignment{ + 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("azure.subscription.policy.assignment", res.__id) + if err != nil || args == nil { + return res, err + } + return res, SetAllData(res, args) + } + + return res, nil +} + +func (c *mqlAzureSubscriptionPolicyAssignment) MqlName() string { + return "azure.subscription.policy.assignment" +} + +func (c *mqlAzureSubscriptionPolicyAssignment) MqlID() string { + return c.__id +} + +func (c *mqlAzureSubscriptionPolicyAssignment) GetId() *plugin.TValue[string] { + return &c.Id +} + +func (c *mqlAzureSubscriptionPolicyAssignment) GetName() *plugin.TValue[string] { + return &c.Name +} + +func (c *mqlAzureSubscriptionPolicyAssignment) GetScope() *plugin.TValue[string] { + return &c.Scope +} + +func (c *mqlAzureSubscriptionPolicyAssignment) GetDescription() *plugin.TValue[string] { + return &c.Description +} + +func (c *mqlAzureSubscriptionPolicyAssignment) GetEnforcementMode() *plugin.TValue[string] { + return &c.EnforcementMode +} diff --git a/providers/azure/resources/azure.lr.manifest.yaml b/providers/azure/resources/azure.lr.manifest.yaml index a08cffd30e..774638fb48 100644 --- a/providers/azure/resources/azure.lr.manifest.yaml +++ b/providers/azure/resources/azure.lr.manifest.yaml @@ -13,8 +13,6 @@ resources: desc: Use the `azure.subscription` resource to assess the configuration of Azure subscriptions. fields: - Policy: - min_mondoo_version: 9.0.0 advisor: {} aks: {} authorization: {} @@ -32,8 +30,6 @@ resources: network: {} policy: min_mondoo_version: 9.0.0 - policyAssignments: - min_mondoo_version: 9.0.0 postgreSql: {} resourceGroups: {} resources: {} @@ -1962,7 +1958,7 @@ resources: url: https://learn.microsoft.com/en-us/azure/virtual-network/ azure.subscription.policy: fields: - policyAssignments: {} + assignments: {} subscriptionId: {} is_private: true min_mondoo_version: 9.0.0 @@ -1971,9 +1967,11 @@ resources: - azure azure.subscription.policy.assignment: fields: + description: {} + enforcementMode: {} + id: {} name: {} scope: {} - type: {} is_private: true min_mondoo_version: 9.0.0 platform: @@ -2340,8 +2338,6 @@ resources: id: {} maxSizeBytes: {} name: {} - policyAssignments: - min_mondoo_version: 9.0.0 readScale: {} recoveryServicesRecoveryPointResourceId: {} requestedServiceObjectiveName: {} diff --git a/providers/azure/resources/cloud_defender.go b/providers/azure/resources/cloud_defender.go index 5d50c1dafc..c0dc82ea7b 100644 --- a/providers/azure/resources/cloud_defender.go +++ b/providers/azure/resources/cloud_defender.go @@ -5,25 +5,16 @@ package resources import ( "context" - "encoding/json" "errors" "fmt" - "io" - "net/http" - "net/url" - "strings" - "time" - "go.mondoo.com/cnquery/v11/llx" "go.mondoo.com/cnquery/v11/providers-sdk/v1/plugin" "go.mondoo.com/cnquery/v11/providers-sdk/v1/util/convert" "go.mondoo.com/cnquery/v11/providers/azure/connection" "go.mondoo.com/cnquery/v11/types" + "strings" "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/security/armsecurity" security "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/security/armsecurity" ) @@ -65,12 +56,6 @@ func (a *mqlAzureSubscriptionCloudDefenderService) defenderForServers() (interfa ctx := context.Background() token := conn.Token() subId := a.SubscriptionId.Data - rawToken, err := token.GetToken(ctx, policy.TokenRequestOptions{ - Scopes: []string{"https://management.core.windows.net//.default"}, - }) - if err != nil { - return nil, err - } clientFactory, err := armsecurity.NewClientFactory(subId, token, nil) if err != nil { return nil, err @@ -80,12 +65,15 @@ func (a *mqlAzureSubscriptionCloudDefenderService) defenderForServers() (interfa return nil, err } - ep := cloud.AzurePublic.Services[cloud.ResourceManager].Endpoint - list, err := getPolicyAssignments(ctx, subId, ep, rawToken.Token) + armConn, err := getArmSecurityConnection(ctx, conn, subId) if err != nil { return nil, err } - serverVASetings, err := getServerVulnAssessmentSettings(ctx, subId, ep, rawToken.Token) + list, err := getPolicyAssignments(ctx, armConn) + if err != nil { + return nil, err + } + serverVASetings, err := getServerVulnAssessmentSettings(ctx, armConn) if err != nil { return nil, err } @@ -374,18 +362,14 @@ func (a *mqlAzureSubscriptionCloudDefenderService) monitoringAgentAutoProvision( func (a *mqlAzureSubscriptionCloudDefenderService) defenderForContainers() (interface{}, error) { conn := a.MqlRuntime.Connection.(*connection.AzureConnection) ctx := context.Background() - token := conn.Token() subId := a.SubscriptionId.Data - rawToken, err := token.GetToken(ctx, policy.TokenRequestOptions{ - Scopes: []string{"https://management.core.windows.net//.default"}, - }) + armConn, err := getArmSecurityConnection(ctx, conn, subId) if err != nil { return nil, err } - ep := cloud.AzurePublic.Services[cloud.ResourceManager].Endpoint - pas, err := getPolicyAssignments(ctx, subId, ep, rawToken.Token) + pas, err := getPolicyAssignments(ctx, armConn) if err != nil { return nil, err } @@ -420,7 +404,7 @@ func (a *mqlAzureSubscriptionCloudDefenderService) defenderForContainers() (inte } // Check if Defender for Containers is enabled by querying the pricing tier - clientFactory, err := armsecurity.NewClientFactory(subId, token, nil) + clientFactory, err := armsecurity.NewClientFactory(subId, armConn.token, nil) if err != nil { return nil, err } @@ -447,13 +431,12 @@ func (a *mqlAzureSubscriptionCloudDefenderService) defenderForContainers() (inte func (a *mqlAzureSubscriptionCloudDefenderService) securityContacts() ([]interface{}, error) { conn := a.MqlRuntime.Connection.(*connection.AzureConnection) ctx := context.Background() - token := conn.Token() subId := a.SubscriptionId.Data - rawToken, err := token.GetToken(ctx, policy.TokenRequestOptions{ - Scopes: []string{"https://management.core.windows.net//.default"}, - }) - ep := cloud.AzurePublic.Services[cloud.ResourceManager].Endpoint - list, err := getSecurityContacts(ctx, subId, ep, rawToken.Token) + armConn, err := getArmSecurityConnection(ctx, conn, subId) + if err != nil { + return nil, err + } + list, err := getSecurityContacts(ctx, armConn) if err != nil { return nil, err } @@ -487,173 +470,3 @@ func (a *mqlAzureSubscriptionCloudDefenderService) securityContacts() ([]interfa } return res, nil } - -func getPolicyAssignments(ctx context.Context, subscriptionId, host, token string) (PolicyAssignments, error) { - urlPath := "/subscriptions/{subscriptionId}/providers/Microsoft.Authorization/policyAssignments" - urlPath = strings.ReplaceAll(urlPath, "{subscriptionId}", url.PathEscape(subscriptionId)) - urlPath = runtime.JoinPaths(host, urlPath) - client := http.Client{} - req, err := http.NewRequest("GET", urlPath, nil) - if err != nil { - return PolicyAssignments{}, err - } - q := req.URL.Query() - q.Set("api-version", "2022-06-01") - req.URL.RawQuery = q.Encode() - req.Header.Set("Accept", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - - resp, err := client.Do(req) - if err != nil { - return PolicyAssignments{}, err - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return PolicyAssignments{}, errors.New("failed to fetch security contacts from " + urlPath + ": " + resp.Status) - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return PolicyAssignments{}, err - } - result := PolicyAssignments{} - err = json.Unmarshal(raw, &result) - return result, err -} - -// the armsecurity.NewListPager is broken, see https://github.com/Azure/azure-sdk-for-go/issues/19740. -// until it's fixed, we can fetch them manually -func getSecurityContacts(ctx context.Context, subscriptionId, host, token string) ([]security.Contact, error) { - urlPath := "/subscriptions/{subscriptionId}/providers/Microsoft.Security/securityContacts" - urlPath = strings.ReplaceAll(urlPath, "{subscriptionId}", url.PathEscape(subscriptionId)) - urlPath = runtime.JoinPaths(host, urlPath) - client := http.Client{} - req, err := http.NewRequest("GET", urlPath, nil) - if err != nil { - return []security.Contact{}, err - } - q := req.URL.Query() - q.Set("api-version", "2020-01-01-preview") - req.URL.RawQuery = q.Encode() - req.Header.Set("Accept", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - - resp, err := client.Do(req) - if err != nil { - return []security.Contact{}, err - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return []security.Contact{}, errors.New("failed to fetch security contacts from " + urlPath + ": " + resp.Status) - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return []security.Contact{}, err - } - result := []security.Contact{} - err = json.Unmarshal(raw, &result) - if err != nil { - // fallback, try to unmarshal to ContactList - contactList := &security.ContactList{} - err = json.Unmarshal(raw, contactList) - if err != nil { - return nil, err - } - for _, c := range contactList.Value { - if c != nil { - result = append(result, *c) - } - } - } - - return result, err -} - -func getServerVulnAssessmentSettings(ctx context.Context, subscriptionId, host, token string) (ServerVulnerabilityAssessmentsSettingsList, error) { - urlPath := "/subscriptions/{subscriptionId}/providers/Microsoft.Security/serverVulnerabilityAssessmentsSettings" - urlPath = strings.ReplaceAll(urlPath, "{subscriptionId}", url.PathEscape(subscriptionId)) - urlPath = runtime.JoinPaths(host, urlPath) - client := http.Client{} - req, err := http.NewRequest("GET", urlPath, nil) - if err != nil { - return ServerVulnerabilityAssessmentsSettingsList{}, err - } - q := req.URL.Query() - q.Set("api-version", "2022-01-01-preview") - req.URL.RawQuery = q.Encode() - req.Header.Set("Accept", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - - resp, err := client.Do(req) - if err != nil { - return ServerVulnerabilityAssessmentsSettingsList{}, err - } - defer resp.Body.Close() - - if resp.StatusCode != 200 { - return ServerVulnerabilityAssessmentsSettingsList{}, errors.New("failed to fetch security contacts from " + urlPath + ": " + resp.Status) - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return ServerVulnerabilityAssessmentsSettingsList{}, err - } - result := ServerVulnerabilityAssessmentsSettingsList{} - err = json.Unmarshal(raw, &result) - return result, err -} - -type PolicyAssignment struct { - ID string `json:"id"` - Type string `json:"type"` - Name string `json:"name"` - Location string `json:"location,omitempty"` - Identity struct { - Type string `json:"type"` - PrincipalID string `json:"principalId"` - TenantID string `json:"tenantId"` - } `json:"identity,omitempty"` - Properties struct { - DisplayName string `json:"displayName"` - Description string `json:"description"` - Metadata struct { - Category string `json:"category"` - } `json:"metadata"` - PolicyDefinitionID string `json:"policyDefinitionId"` - Parameters struct { - AllowedSkus struct { - Value string `json:"value"` - } `json:"allowedSkus"` - } `json:"parameters"` - Scope string `json:"scope"` - NotScopes []interface{} `json:"notScopes"` - } `json:"properties"` -} -type PolicyAssignments struct { - PolicyAssignments []PolicyAssignment `json:"value"` -} - -type ServerVulnerabilityAssessmentsSettings struct { - Properties struct { - SelectedProvider string `json:"selectedProvider"` - } `json:"properties"` - SystemData struct { - CreatedBy string `json:"createdBy"` - CreatedByType string `json:"createdByType"` - CreatedAt time.Time `json:"createdAt"` - LastModifiedBy string `json:"lastModifiedBy"` - LastModifiedByType string `json:"lastModifiedByType"` - LastModifiedAt time.Time `json:"lastModifiedAt"` - } `json:"systemData"` - Kind string `json:"kind"` - Name string `json:"name"` - Type string `json:"type"` - ID string `json:"id"` -} - -type ServerVulnerabilityAssessmentsSettingsList struct { - Settings []ServerVulnerabilityAssessmentsSettings `json:"value"` -} diff --git a/providers/azure/resources/policy.go b/providers/azure/resources/policy.go index 13ec6779d0..3b1c7bdc1c 100644 --- a/providers/azure/resources/policy.go +++ b/providers/azure/resources/policy.go @@ -7,13 +7,9 @@ import ( "context" "errors" "fmt" - "go.mondoo.com/cnquery/v11/llx" "go.mondoo.com/cnquery/v11/providers-sdk/v1/plugin" "go.mondoo.com/cnquery/v11/providers/azure/connection" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" ) func initAzureSubscriptionPolicy(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) { @@ -30,30 +26,17 @@ func initAzureSubscriptionPolicy(runtime *plugin.Runtime, args map[string]*llx.R return args, nil, nil } -func (a *mqlAzureSubscriptionPolicyAssignment) id() (string, error) { - // Ensure that all the parts of the ID are available - if a.Scope.Data == "" || a.Name.Data == "" { - return "", errors.New("missing required fields to generate id") - } - - return fmt.Sprintf("azure.subscription.policy/%s/%s", a.Scope.Data, a.Name.Data), nil -} - -func (a *mqlAzureSubscriptionPolicy) policyAssignments() ([]interface{}, error) { +func (a *mqlAzureSubscriptionPolicy) assignments() ([]interface{}, error) { conn := a.MqlRuntime.Connection.(*connection.AzureConnection) ctx := context.Background() - token := conn.Token() subId := a.SubscriptionId.Data - rawToken, err := token.GetToken(ctx, policy.TokenRequestOptions{ - Scopes: []string{"https://management.core.windows.net//.default"}, - }) + armConn, err := getArmSecurityConnection(ctx, conn, subId) if err != nil { return nil, err } - ep := cloud.AzurePublic.Services[cloud.ResourceManager].Endpoint - pas, err := getPolicyAssignments(ctx, subId, ep, rawToken.Token) + pas, err := getPolicyAssignments(ctx, armConn) if err != nil { return nil, err } @@ -61,9 +44,12 @@ func (a *mqlAzureSubscriptionPolicy) policyAssignments() ([]interface{}, error) res := []interface{}{} for _, assignment := range pas.PolicyAssignments { assignmentData := map[string]*llx.RawData{ - "name": llx.StringData(assignment.Properties.DisplayName), - "scope": llx.StringData(assignment.Properties.Scope), - "type": llx.StringData(assignment.Type), + "__id": llx.StringData(fmt.Sprintf("azure.subscription.policy/%s/%s", assignment.Properties.Scope, assignment.Properties.DisplayName)), + "id": llx.StringData(assignment.Properties.PolicyDefinitionID), + "name": llx.StringData(assignment.Properties.DisplayName), + "scope": llx.StringData(assignment.Properties.Scope), + "description": llx.StringData(assignment.Properties.Description), + "enforcementMode": llx.StringData(assignment.Properties.EnforcementMode), } mqlAssignment, err := CreateResource(a.MqlRuntime, "azure.subscription.policy.assignment", assignmentData)