From bd82ab7a05df1a13b808ef7681804faa02cbdb7b Mon Sep 17 00:00:00 2001 From: cmendible <266546+cmendible@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:15:25 +0200 Subject: [PATCH] missing rules --- cmd/azqr/ng.go | 2 +- go.mod | 2 + go.sum | 4 ++ internal/renderers/excel/impacted.go | 2 +- internal/renderers/report_data.go | 10 ++++- internal/scanners/it/it.go | 51 +++++++++++++++++---- internal/scanners/it/rules.go | 42 +++++++++++++++++ internal/scanners/log/log.go | 52 +++++++++++++++++---- internal/scanners/log/rules.go | 54 ++++++++++++++++++++++ internal/scanners/ng/ng.go | 58 +++++++++++++++++++----- internal/scanners/ng/rules.go | 67 ++++++++++++++++++++++++++++ internal/scanners/nsg/nsg.go | 51 ++++++++++++++++++--- internal/scanners/nsg/rules.go | 67 ++++++++++++++++++++++++++++ internal/scanners/nw/nw.go | 53 ++++++++++++++++++---- internal/scanners/nw/rules.go | 54 ++++++++++++++++++++++ internal/scanners/pdnsz/pdnsz.go | 2 + internal/scanners/pep/pep.go | 52 +++++++++++++++++---- internal/scanners/pep/rules.go | 54 ++++++++++++++++++++++ internal/scanners/pip/pip.go | 52 +++++++++++++++++---- internal/scanners/pip/rules.go | 54 ++++++++++++++++++++++ internal/scanners/rt/rt.go | 51 ++++++++++++++++++--- internal/scanners/rt/rules.go | 54 ++++++++++++++++++++++ internal/scanners/scanners_list.go | 2 +- 23 files changed, 820 insertions(+), 70 deletions(-) create mode 100644 internal/scanners/it/rules.go create mode 100644 internal/scanners/log/rules.go create mode 100644 internal/scanners/ng/rules.go create mode 100644 internal/scanners/nsg/rules.go create mode 100644 internal/scanners/nw/rules.go create mode 100644 internal/scanners/pep/rules.go create mode 100644 internal/scanners/pip/rules.go create mode 100644 internal/scanners/rt/rules.go diff --git a/cmd/azqr/ng.go b/cmd/azqr/ng.go index 83ad3b08..b3107b24 100644 --- a/cmd/azqr/ng.go +++ b/cmd/azqr/ng.go @@ -20,7 +20,7 @@ var ngCmd = &cobra.Command{ Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { serviceScanners := []azqr.IAzureScanner{ - &ng.NatGatewwayScanner{}, + &ng.NatGatewayScanner{}, } scan(cmd, serviceScanners) diff --git a/go.mod b/go.mod index 57a751ee..992d7dc3 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6 v6.1.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/operationalinsights/armoperationalinsights/v2 v2.0.0-beta.4 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis v1.0.0 @@ -46,6 +47,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/synapse/armsynapse v0.8.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/trafficmanager/armtrafficmanager v1.3.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/virtualmachineimagebuilder/armvirtualmachineimagebuilder/v2 v2.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/webpubsub/armwebpubsub v1.3.0 github.com/google/uuid v1.6.0 github.com/rs/zerolog v1.33.0 diff --git a/go.sum b/go.sum index 41d1ebfb..d7eddce1 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0/go.mod h1:243D9iHbcQXoFUtgHJwL7gl2zx1aDuDMjvBZVGr2uW0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6 v6.1.0 h1:Fd+iaEa+JBwzYo6OTWYSNqyvlPSLciMGsmsnYCKcXM0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6 v6.1.0/go.mod h1:ulHyBFJOI0ONiRL4vcJTmS7rx18jQQlEPmAgo80cRdM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/operationalinsights/armoperationalinsights/v2 v2.0.0-beta.4 h1:VwalLmc4ugRHT4DFpNw2un/atApgAk90LJeuLUcSZn4= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/operationalinsights/armoperationalinsights/v2 v2.0.0-beta.4/go.mod h1:66Yvwp7y+reikAA12FlUZI5faaIl3cUr/mLg9X5A9RM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0 h1:0hXKrsbh2M6CQyW0TDC9Bsyd99vQmrOxiBTUfQHZjPA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql v1.2.0/go.mod h1:bvZZor36Jg9q9kouuMyfJ+ay77+qK+YUfThXH1FdXjU= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers v1.1.0 h1:HzqcSJWx32XQdr8KtxAu/SZJj0PqDo9tKf2YGPdynV0= @@ -96,6 +98,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/synapse/armsynapse v0.8.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/synapse/armsynapse v0.8.0/go.mod h1:IzuvA34YNVnlifc1+KhCouAKEf1VYzV439FOpyfTHzA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/trafficmanager/armtrafficmanager v1.3.0 h1:e3kTG23M5ps+DjvPolK4dcgohDY8sHsXU7zrdHj1WzY= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/trafficmanager/armtrafficmanager v1.3.0/go.mod h1:Os5dq8Cvvz97rJauZhZJAfKHN+OEvF/0nVmHzF4aVys= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/virtualmachineimagebuilder/armvirtualmachineimagebuilder/v2 v2.3.0 h1:oMC000T4/6AQREdUeR7pL/e1qcrKfznthEaj2DGKOo4= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/virtualmachineimagebuilder/armvirtualmachineimagebuilder/v2 v2.3.0/go.mod h1:+iH0q9O/v2R4DlcvTrdXKcKUhxazcu4gTBb/QCfkDP4= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/webpubsub/armwebpubsub v1.3.0 h1:NyzzELDBMwCl+jHnUAEzv/4t9tp0vVn78vUou/7yqvM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/webpubsub/armwebpubsub v1.3.0/go.mod h1:3cqAZX7JxhdbywHK3b1iaO/VcP9Kv+yvZ/s44EO2+LI= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= diff --git a/internal/renderers/excel/impacted.go b/internal/renderers/excel/impacted.go index 005ddc52..f7dc8600 100644 --- a/internal/renderers/excel/impacted.go +++ b/internal/renderers/excel/impacted.go @@ -22,7 +22,7 @@ func renderImpactedResources(f *excelize.File, data *renderers.ReportData) { headers := records[0] createFirstRow(f, sheetName, headers) - if len(data.AprlData) > 0 { + if len(records) > 0 { records = records[1:] currentRow := 4 for _, row := range records { diff --git a/internal/renderers/report_data.go b/internal/renderers/report_data.go index 859f688c..d91f4d14 100644 --- a/internal/renderers/report_data.go +++ b/internal/renderers/report_data.go @@ -70,7 +70,7 @@ func (rd *ReportData) ResourcesTable() [][]string { rows := [][]string{} for _, r := range rd.Resources { sla := "" - + for _, a := range rd.AzqrData { if strings.EqualFold(strings.ToLower(a.ResourceID()), strings.ToLower(r.ID)) { for _, rc := range a.Recommendations { @@ -334,6 +334,10 @@ func NewReportData(outputFile string, mask bool) ReportData { } func MaskSubscriptionID(subscriptionID string, mask bool) string { + if len(subscriptionID) < 36 { + return "" + } + if !mask { return subscriptionID } @@ -343,6 +347,10 @@ func MaskSubscriptionID(subscriptionID string, mask bool) string { } func MaskSubscriptionIDInResourceID(resourceID string, mask bool) string { + if !strings.HasPrefix(resourceID, "/subscriptions/") { + return "" + } + if !mask { return resourceID } diff --git a/internal/scanners/it/it.go b/internal/scanners/it/it.go index ea011b3a..57cd2401 100644 --- a/internal/scanners/it/it.go +++ b/internal/scanners/it/it.go @@ -5,29 +5,64 @@ package it import ( "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/virtualmachineimagebuilder/armvirtualmachineimagebuilder/v2" ) // ImageTemplateScanner - Scanner for Image Template type ImageTemplateScanner struct { config *azqr.ScannerConfig + client *armvirtualmachineimagebuilder.VirtualMachineImageTemplatesClient } // Init - Initializes the Image Template Scanner func (a *ImageTemplateScanner) Init(config *azqr.ScannerConfig) error { a.config = config - return nil + var err error + a.client, err = armvirtualmachineimagebuilder.NewVirtualMachineImageTemplatesClient(config.SubscriptionID, config.Cred, config.ClientOptions) + return err } // Scan - Scans all Image Template in a Resource Group -func (a *ImageTemplateScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { - azqr.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0]) - return []azqr.AzqrServiceResult{}, nil +func (c *ImageTemplateScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { + azqr.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0]) + + svcs, err := c.list() + if err != nil { + return nil, err + } + engine := azqr.RecommendationEngine{} + rules := c.GetRecommendations() + results := []azqr.AzqrServiceResult{} + + for _, w := range svcs { + rr := engine.EvaluateRecommendations(rules, w, scanContext) + + results = append(results, azqr.AzqrServiceResult{ + SubscriptionID: c.config.SubscriptionID, + SubscriptionName: c.config.SubscriptionName, + ResourceGroup: azqr.GetResourceGroupFromResourceID(*w.ID), + ServiceName: *w.Name, + Type: *w.Type, + Location: *w.Location, + Recommendations: rr, + }) + } + return results, nil } +func (c *ImageTemplateScanner) list() ([]*armvirtualmachineimagebuilder.ImageTemplate, error) { + pager := c.client.NewListPager(nil) + + svcs := make([]*armvirtualmachineimagebuilder.ImageTemplate, 0) + for pager.More() { + resp, err := pager.NextPage(c.config.Ctx) + if err != nil { + return nil, err + } + svcs = append(svcs, resp.Value...) + } + return svcs, nil +} func (a *ImageTemplateScanner) ResourceTypes() []string { return []string{"Microsoft.VirtualMachineImages/imageTemplates"} } - -func (a *ImageTemplateScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { - return map[string]azqr.AzqrRecommendation{} -} diff --git a/internal/scanners/it/rules.go b/internal/scanners/it/rules.go new file mode 100644 index 00000000..c677a4d8 --- /dev/null +++ b/internal/scanners/it/rules.go @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package it + +import ( + "strings" + + "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/virtualmachineimagebuilder/armvirtualmachineimagebuilder/v2" +) + +// GetRules - Returns the rules for the ImageTemplateScanner +func (a *ImageTemplateScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { + return map[string]azqr.AzqrRecommendation{ + "it-006": { + RecommendationID: "it-006", + ResourceType: "Microsoft.VirtualMachineImages/imageTemplates", + Category: azqr.CategoryGovernance, + Recommendation: "Image Template Name should comply with naming conventions", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armvirtualmachineimagebuilder.ImageTemplate) + caf := strings.HasPrefix(*c.Name, "it") + return !caf, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations", + }, + "it-007": { + RecommendationID: "it-007", + ResourceType: "Microsoft.VirtualMachineImages/imageTemplates", + Category: azqr.CategoryGovernance, + Recommendation: "Image Template should have tags", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armvirtualmachineimagebuilder.ImageTemplate) + return len(c.Tags) == 0, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json", + }, + } +} diff --git a/internal/scanners/log/log.go b/internal/scanners/log/log.go index c0aca162..7520a7f5 100644 --- a/internal/scanners/log/log.go +++ b/internal/scanners/log/log.go @@ -5,29 +5,65 @@ package log import ( "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/operationalinsights/armoperationalinsights/v2" ) // LogAnalyticsScanner - Scanner for Log Analytics workspace type LogAnalyticsScanner struct { config *azqr.ScannerConfig + client *armoperationalinsights.WorkspacesClient } // Init - Initializes the Log Analytics workspace Scanner func (a *LogAnalyticsScanner) Init(config *azqr.ScannerConfig) error { a.config = config - return nil + var err error + a.client, err = armoperationalinsights.NewWorkspacesClient(config.SubscriptionID, config.Cred, config.ClientOptions) + return err } // Scan - Scans all Log Analytics workspace in a Resource Group -func (a *LogAnalyticsScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { - azqr.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0]) - return []azqr.AzqrServiceResult{}, nil +func (c *LogAnalyticsScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { + azqr.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0]) + + svcs, err := c.list() + if err != nil { + return nil, err + } + engine := azqr.RecommendationEngine{} + rules := c.GetRecommendations() + results := []azqr.AzqrServiceResult{} + + for _, w := range svcs { + rr := engine.EvaluateRecommendations(rules, w, scanContext) + + results = append(results, azqr.AzqrServiceResult{ + SubscriptionID: c.config.SubscriptionID, + SubscriptionName: c.config.SubscriptionName, + ResourceGroup: azqr.GetResourceGroupFromResourceID(*w.ID), + ServiceName: *w.Name, + Type: *w.Type, + Location: *w.Location, + Recommendations: rr, + }) + } + return results, nil } -func (a *LogAnalyticsScanner) ResourceTypes() []string { - return []string{"Microsoft.OperationalInsights/workspaces"} +func (c *LogAnalyticsScanner) list() ([]*armoperationalinsights.Workspace, error) { + pager := c.client.NewListPager(nil) + + svcs := make([]*armoperationalinsights.Workspace, 0) + for pager.More() { + resp, err := pager.NextPage(c.config.Ctx) + if err != nil { + return nil, err + } + svcs = append(svcs, resp.Value...) + } + return svcs, nil } -func (a *LogAnalyticsScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { - return map[string]azqr.AzqrRecommendation{} +func (a *LogAnalyticsScanner) ResourceTypes() []string { + return []string{"Microsoft.OperationalInsights/workspaces"} } diff --git a/internal/scanners/log/rules.go b/internal/scanners/log/rules.go new file mode 100644 index 00000000..0f4ec27d --- /dev/null +++ b/internal/scanners/log/rules.go @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package log + +import ( + "strings" + + "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/operationalinsights/armoperationalinsights/v2" +) + +// GetRules - Returns the rules for the LogAnalyticsScanner +func (a *LogAnalyticsScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { + return map[string]azqr.AzqrRecommendation{ + "log-003": { + RecommendationID: "log-003", + ResourceType: "Microsoft.OperationalInsights/workspaces", + Category: azqr.CategoryHighAvailability, + Recommendation: "Log Analytics Workspace SLA", + RecommendationType: azqr.TypeSLA, + Impact: azqr.ImpactHigh, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + return false, "99.9%" + }, + LearnMoreUrl: "https://www.microsoft.com/licensing/docs/view/Service-Level-Agreements-SLA-for-Online-Services", + }, + "log-006": { + RecommendationID: "log-006", + ResourceType: "Microsoft.OperationalInsights/workspaces", + Category: azqr.CategoryGovernance, + Recommendation: "Log Analytics Workspace Name should comply with naming conventions", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armoperationalinsights.Workspace) + caf := strings.HasPrefix(*c.Name, "log") + return !caf, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations", + }, + "log-007": { + RecommendationID: "log-007", + ResourceType: "Microsoft.OperationalInsights/workspaces", + Category: azqr.CategoryGovernance, + Recommendation: "Log Analytics Workspace should have tags", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armoperationalinsights.Workspace) + return len(c.Tags) == 0, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json", + }, + } +} diff --git a/internal/scanners/ng/ng.go b/internal/scanners/ng/ng.go index 664bc846..bcb6cf16 100644 --- a/internal/scanners/ng/ng.go +++ b/internal/scanners/ng/ng.go @@ -5,29 +5,65 @@ package ng import ( "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" ) -// NatGatewwayScanner - Scanner for NAT Gateway -type NatGatewwayScanner struct { +// NatGatewayScanner - Scanner for NAT Gateway +type NatGatewayScanner struct { config *azqr.ScannerConfig + client *armnetwork.NatGatewaysClient } // Init - Initializes the NAT Gateway Scanner -func (a *NatGatewwayScanner) Init(config *azqr.ScannerConfig) error { +func (a *NatGatewayScanner) Init(config *azqr.ScannerConfig) error { a.config = config - return nil + var err error + a.client, err = armnetwork.NewNatGatewaysClient(config.SubscriptionID, config.Cred, config.ClientOptions) + return err } // Scan - Scans all NAT Gateway in a Resource Group -func (a *NatGatewwayScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { - azqr.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0]) - return []azqr.AzqrServiceResult{}, nil +func (c *NatGatewayScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { + azqr.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0]) + + svcs, err := c.list() + if err != nil { + return nil, err + } + engine := azqr.RecommendationEngine{} + rules := c.GetRecommendations() + results := []azqr.AzqrServiceResult{} + + for _, w := range svcs { + rr := engine.EvaluateRecommendations(rules, w, scanContext) + + results = append(results, azqr.AzqrServiceResult{ + SubscriptionID: c.config.SubscriptionID, + SubscriptionName: c.config.SubscriptionName, + ResourceGroup: azqr.GetResourceGroupFromResourceID(*w.ID), + ServiceName: *w.Name, + Type: *w.Type, + Location: *w.Location, + Recommendations: rr, + }) + } + return results, nil } -func (a *NatGatewwayScanner) ResourceTypes() []string { - return []string{"Microsoft.Network/natGateways"} +func (c *NatGatewayScanner) list() ([]*armnetwork.NatGateway, error) { + pager := c.client.NewListAllPager(nil) + + svcs := make([]*armnetwork.NatGateway, 0) + for pager.More() { + resp, err := pager.NextPage(c.config.Ctx) + if err != nil { + return nil, err + } + svcs = append(svcs, resp.Value...) + } + return svcs, nil } -func (a *NatGatewwayScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { - return map[string]azqr.AzqrRecommendation{} +func (a *NatGatewayScanner) ResourceTypes() []string { + return []string{"Microsoft.Network/natGateways"} } diff --git a/internal/scanners/ng/rules.go b/internal/scanners/ng/rules.go new file mode 100644 index 00000000..cf28289e --- /dev/null +++ b/internal/scanners/ng/rules.go @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package ng + +import ( + "strings" + + "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" +) + +// GetRules - Returns the rules for the NatGatewayScanner +func (a *NatGatewayScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { + return map[string]azqr.AzqrRecommendation{ + "ng-001": { + RecommendationID: "ng-001", + ResourceType: "Microsoft.Network/natGateways", + Category: azqr.CategoryMonitoringAndAlerting, + Recommendation: "NAT Gateway should have diagnostic settings enabled", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + service := target.(*armnetwork.SecurityGroup) + _, ok := scanContext.DiagnosticsSettings[strings.ToLower(*service.ID)] + return !ok, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/nat-gateway/nat-metrics", + }, + "ng-003": { + RecommendationID: "ng-003", + ResourceType: "Microsoft.Network/natGateways", + Category: azqr.CategoryHighAvailability, + Recommendation: "NAT Gateway SLA", + RecommendationType: azqr.TypeSLA, + Impact: azqr.ImpactHigh, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + return false, "99.99%" + }, + LearnMoreUrl: "https://www.microsoft.com/licensing/docs/view/Service-Level-Agreements-SLA-for-Online-Services", + }, + "ng-006": { + RecommendationID: "ng-006", + ResourceType: "Microsoft.Network/natGateways", + Category: azqr.CategoryGovernance, + Recommendation: "NAT Gateway Name should comply with naming conventions", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armnetwork.SecurityGroup) + caf := strings.HasPrefix(*c.Name, "ng") + return !caf, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations", + }, + "ng-007": { + RecommendationID: "ng-007", + ResourceType: "Microsoft.Network/natGateways", + Category: azqr.CategoryGovernance, + Recommendation: "NAT Gateway should have tags", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armnetwork.SecurityGroup) + return len(c.Tags) == 0, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json", + }, + } +} diff --git a/internal/scanners/nsg/nsg.go b/internal/scanners/nsg/nsg.go index 3cf600ac..2708780e 100644 --- a/internal/scanners/nsg/nsg.go +++ b/internal/scanners/nsg/nsg.go @@ -5,29 +5,66 @@ package nsg import ( "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" ) // NSGScanner - Scanner for NSG type NSGScanner struct { config *azqr.ScannerConfig + client *armnetwork.SecurityGroupsClient } // Init - Initializes the NSG Scanner func (a *NSGScanner) Init(config *azqr.ScannerConfig) error { a.config = config - return nil + var err error + a.client, err = armnetwork.NewSecurityGroupsClient(config.SubscriptionID, config.Cred, config.ClientOptions) + return err } // Scan - Scans all NSG in a Resource Group -func (a *NSGScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { - azqr.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0]) - return []azqr.AzqrServiceResult{}, nil +func (c *NSGScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { + azqr.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0]) + + svcs, err := c.list() + if err != nil { + return nil, err + } + engine := azqr.RecommendationEngine{} + rules := c.GetRecommendations() + results := []azqr.AzqrServiceResult{} + + for _, w := range svcs { + rr := engine.EvaluateRecommendations(rules, w, scanContext) + + results = append(results, azqr.AzqrServiceResult{ + SubscriptionID: c.config.SubscriptionID, + SubscriptionName: c.config.SubscriptionName, + ResourceGroup: azqr.GetResourceGroupFromResourceID(*w.ID), + ServiceName: *w.Name, + Type: *w.Type, + Location: *w.Location, + Recommendations: rr, + }) + } + return results, nil +} + +func (c *NSGScanner) list() ([]*armnetwork.SecurityGroup, error) { + pager := c.client.NewListAllPager(nil) + + svcs := make([]*armnetwork.SecurityGroup, 0) + for pager.More() { + resp, err := pager.NextPage(c.config.Ctx) + if err != nil { + return nil, err + } + svcs = append(svcs, resp.Value...) + } + return svcs, nil } func (a *NSGScanner) ResourceTypes() []string { return []string{"Microsoft.Network/networkSecurityGroups"} } -func (a *NSGScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { - return map[string]azqr.AzqrRecommendation{} -} diff --git a/internal/scanners/nsg/rules.go b/internal/scanners/nsg/rules.go new file mode 100644 index 00000000..d2247645 --- /dev/null +++ b/internal/scanners/nsg/rules.go @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package nsg + +import ( + "strings" + + "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" +) + +// GetRules - Returns the rules for the NSGScanner +func (a *NSGScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { + return map[string]azqr.AzqrRecommendation{ + "nsg-001": { + RecommendationID: "nsg-001", + ResourceType: "Microsoft.Network/networkSecurityGroups", + Category: azqr.CategoryMonitoringAndAlerting, + Recommendation: "NSG should have diagnostic settings enabled", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + service := target.(*armnetwork.SecurityGroup) + _, ok := scanContext.DiagnosticsSettings[strings.ToLower(*service.ID)] + return !ok, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/virtual-network/virtual-network-nsg-manage-log", + }, + "nsg-003": { + RecommendationID: "nsg-003", + ResourceType: "Microsoft.Network/networkSecurityGroups", + Category: azqr.CategoryHighAvailability, + Recommendation: "NSG SLA", + RecommendationType: azqr.TypeSLA, + Impact: azqr.ImpactHigh, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + return false, "99.99%" + }, + LearnMoreUrl: "https://www.microsoft.com/licensing/docs/view/Service-Level-Agreements-SLA-for-Online-Services", + }, + "nsg-006": { + RecommendationID: "nsg-006", + ResourceType: "Microsoft.Network/networkSecurityGroups", + Category: azqr.CategoryGovernance, + Recommendation: "NSG Name should comply with naming conventions", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armnetwork.SecurityGroup) + caf := strings.HasPrefix(*c.Name, "nsg") + return !caf, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations", + }, + "nsg-007": { + RecommendationID: "nsg-007", + ResourceType: "Microsoft.Network/networkSecurityGroups", + Category: azqr.CategoryGovernance, + Recommendation: "NSG should have tags", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armnetwork.SecurityGroup) + return len(c.Tags) == 0, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json", + }, + } +} diff --git a/internal/scanners/nw/nw.go b/internal/scanners/nw/nw.go index c99a560d..12dcbf50 100644 --- a/internal/scanners/nw/nw.go +++ b/internal/scanners/nw/nw.go @@ -5,29 +5,64 @@ package nw import ( "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" ) // NetworkWatcherScanner - Scanner for Network Watcher type NetworkWatcherScanner struct { config *azqr.ScannerConfig + client *armnetwork.WatchersClient } // Init - Initializes the Network Watcher Scanner func (a *NetworkWatcherScanner) Init(config *azqr.ScannerConfig) error { a.config = config - return nil + var err error + a.client, err = armnetwork.NewWatchersClient(config.SubscriptionID, config.Cred, config.ClientOptions) + return err } // Scan - Scans all Network Watcher in a Resource Group -func (a *NetworkWatcherScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { - azqr.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0]) - return []azqr.AzqrServiceResult{}, nil -} +func (c *NetworkWatcherScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { + azqr.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0]) -func (a *NetworkWatcherScanner) ResourceTypes() []string { - return []string{"Microsoft.Network/networkWatcherScanners"} + svcs, err := c.list() + if err != nil { + return nil, err + } + engine := azqr.RecommendationEngine{} + rules := c.GetRecommendations() + results := []azqr.AzqrServiceResult{} + + for _, w := range svcs { + rr := engine.EvaluateRecommendations(rules, w, scanContext) + + results = append(results, azqr.AzqrServiceResult{ + SubscriptionID: c.config.SubscriptionID, + SubscriptionName: c.config.SubscriptionName, + ResourceGroup: azqr.GetResourceGroupFromResourceID(*w.ID), + ServiceName: *w.Name, + Type: *w.Type, + Location: *w.Location, + Recommendations: rr, + }) + } + return results, nil } -func (a *NetworkWatcherScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { - return map[string]azqr.AzqrRecommendation{} +func (c *NetworkWatcherScanner) list() ([]*armnetwork.Watcher, error) { + pager := c.client.NewListAllPager(nil) + + svcs := make([]*armnetwork.Watcher, 0) + for pager.More() { + resp, err := pager.NextPage(c.config.Ctx) + if err != nil { + return nil, err + } + svcs = append(svcs, resp.Value...) + } + return svcs, nil +} +func (a *NetworkWatcherScanner) ResourceTypes() []string { + return []string{"Microsoft.Network/networkWatchers"} } diff --git a/internal/scanners/nw/rules.go b/internal/scanners/nw/rules.go new file mode 100644 index 00000000..9273471d --- /dev/null +++ b/internal/scanners/nw/rules.go @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package nw + +import ( + "strings" + + "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" +) + +// GetRules - Returns the rules for the NetworkWatcherScanner +func (a *NetworkWatcherScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { + return map[string]azqr.AzqrRecommendation{ + "nw-003": { + RecommendationID: "nw-003", + ResourceType: "Microsoft.Network/networkWatchers", + Category: azqr.CategoryHighAvailability, + Recommendation: "Network Watcher SLA", + RecommendationType: azqr.TypeSLA, + Impact: azqr.ImpactHigh, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + return false, "99.9%" + }, + LearnMoreUrl: "https://www.microsoft.com/licensing/docs/view/Service-Level-Agreements-SLA-for-Online-Services", + }, + "nw-006": { + RecommendationID: "nw-006", + ResourceType: "Microsoft.Network/networkWatchers", + Category: azqr.CategoryGovernance, + Recommendation: "Network Watcher Name should comply with naming conventions", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armnetwork.Watcher) + caf := strings.HasPrefix(*c.Name, "nw") + return !caf, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations", + }, + "nw-007": { + RecommendationID: "nw-007", + ResourceType: "Microsoft.Network/networkWatchers", + Category: azqr.CategoryGovernance, + Recommendation: "Network Watcher should have tags", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armnetwork.Watcher) + return len(c.Tags) == 0, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json", + }, + } +} diff --git a/internal/scanners/pdnsz/pdnsz.go b/internal/scanners/pdnsz/pdnsz.go index 62cb2331..ce2cce2e 100644 --- a/internal/scanners/pdnsz/pdnsz.go +++ b/internal/scanners/pdnsz/pdnsz.go @@ -31,3 +31,5 @@ func (a *PrivateDNSZoneScanner) ResourceTypes() []string { func (a *PrivateDNSZoneScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { return map[string]azqr.AzqrRecommendation{} } + +// TODO: version 6.1.0 of armentowrk does not allow listing per subscription yet. \ No newline at end of file diff --git a/internal/scanners/pep/pep.go b/internal/scanners/pep/pep.go index cc38cf58..56e63f69 100644 --- a/internal/scanners/pep/pep.go +++ b/internal/scanners/pep/pep.go @@ -5,29 +5,65 @@ package pep import ( "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" ) // PrivateEndpointScanner - Scanner for Private Endpoint type PrivateEndpointScanner struct { config *azqr.ScannerConfig + client *armnetwork.PrivateEndpointsClient } // Init - Initializes the Private Endpoint Scanner func (a *PrivateEndpointScanner) Init(config *azqr.ScannerConfig) error { a.config = config - return nil + var err error + a.client, err = armnetwork.NewPrivateEndpointsClient(config.SubscriptionID, config.Cred, config.ClientOptions) + return err } // Scan - Scans all Private Endpoint in a Resource Group -func (a *PrivateEndpointScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { - azqr.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0]) - return []azqr.AzqrServiceResult{}, nil +func (c *PrivateEndpointScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { + azqr.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0]) + + svcs, err := c.list() + if err != nil { + return nil, err + } + engine := azqr.RecommendationEngine{} + rules := c.GetRecommendations() + results := []azqr.AzqrServiceResult{} + + for _, w := range svcs { + rr := engine.EvaluateRecommendations(rules, w, scanContext) + + results = append(results, azqr.AzqrServiceResult{ + SubscriptionID: c.config.SubscriptionID, + SubscriptionName: c.config.SubscriptionName, + ResourceGroup: azqr.GetResourceGroupFromResourceID(*w.ID), + ServiceName: *w.Name, + Type: *w.Type, + Location: *w.Location, + Recommendations: rr, + }) + } + return results, nil } -func (a *PrivateEndpointScanner) ResourceTypes() []string { - return []string{"Microsoft.Network/privateEndpoints"} +func (c *PrivateEndpointScanner) list() ([]*armnetwork.PrivateEndpoint, error) { + pager := c.client.NewListBySubscriptionPager(nil) + + svcs := make([]*armnetwork.PrivateEndpoint, 0) + for pager.More() { + resp, err := pager.NextPage(c.config.Ctx) + if err != nil { + return nil, err + } + svcs = append(svcs, resp.Value...) + } + return svcs, nil } -func (a *PrivateEndpointScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { - return map[string]azqr.AzqrRecommendation{} +func (a *PrivateEndpointScanner) ResourceTypes() []string { + return []string{"Microsoft.Network/privateEndpoints"} } diff --git a/internal/scanners/pep/rules.go b/internal/scanners/pep/rules.go new file mode 100644 index 00000000..3a99cca7 --- /dev/null +++ b/internal/scanners/pep/rules.go @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package pep + +import ( + "strings" + + "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" +) + +// GetRules - Returns the rules for the PrivateEndpointScanner +func (a *PrivateEndpointScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { + return map[string]azqr.AzqrRecommendation{ + "pep-003": { + RecommendationID: "pep-003", + ResourceType: "Microsoft.Network/privateEndpoints", + Category: azqr.CategoryHighAvailability, + Recommendation: "Private Endpoint SLA", + RecommendationType: azqr.TypeSLA, + Impact: azqr.ImpactHigh, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + return false, "99.99%" + }, + LearnMoreUrl: "https://www.microsoft.com/licensing/docs/view/Service-Level-Agreements-SLA-for-Online-Services", + }, + "pep-006": { + RecommendationID: "pep-006", + ResourceType: "Microsoft.Network/privateEndpoints", + Category: azqr.CategoryGovernance, + Recommendation: "Private Endpoint Name should comply with naming conventions", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armnetwork.PrivateEndpoint) + caf := strings.HasPrefix(*c.Name, "pep") + return !caf, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations", + }, + "pep-007": { + RecommendationID: "pep-007", + ResourceType: "Microsoft.Network/privateEndpoints", + Category: azqr.CategoryGovernance, + Recommendation: "Private Endpoint should have tags", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armnetwork.PrivateEndpoint) + return len(c.Tags) == 0, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json", + }, + } +} diff --git a/internal/scanners/pip/pip.go b/internal/scanners/pip/pip.go index 6d307459..14b27292 100644 --- a/internal/scanners/pip/pip.go +++ b/internal/scanners/pip/pip.go @@ -5,29 +5,65 @@ package pip import ( "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" ) // PublicIPScanner - Scanner for Public IP type PublicIPScanner struct { config *azqr.ScannerConfig + client *armnetwork.PublicIPAddressesClient } // Init - Initializes the Public IP Scanner func (a *PublicIPScanner) Init(config *azqr.ScannerConfig) error { a.config = config - return nil + var err error + a.client, err = armnetwork.NewPublicIPAddressesClient(config.SubscriptionID, config.Cred, config.ClientOptions) + return err } // Scan - Scans all Public IP in a Resource Group -func (a *PublicIPScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { - azqr.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0]) - return []azqr.AzqrServiceResult{}, nil +func (c *PublicIPScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { + azqr.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0]) + + svcs, err := c.list() + if err != nil { + return nil, err + } + engine := azqr.RecommendationEngine{} + rules := c.GetRecommendations() + results := []azqr.AzqrServiceResult{} + + for _, w := range svcs { + rr := engine.EvaluateRecommendations(rules, w, scanContext) + + results = append(results, azqr.AzqrServiceResult{ + SubscriptionID: c.config.SubscriptionID, + SubscriptionName: c.config.SubscriptionName, + ResourceGroup: azqr.GetResourceGroupFromResourceID(*w.ID), + ServiceName: *w.Name, + Type: *w.Type, + Location: *w.Location, + Recommendations: rr, + }) + } + return results, nil } -func (a *PublicIPScanner) ResourceTypes() []string { - return []string{"Microsoft.Network/publicIPAddresses"} +func (c *PublicIPScanner) list() ([]*armnetwork.PublicIPAddress, error) { + pager := c.client.NewListAllPager(nil) + + svcs := make([]*armnetwork.PublicIPAddress, 0) + for pager.More() { + resp, err := pager.NextPage(c.config.Ctx) + if err != nil { + return nil, err + } + svcs = append(svcs, resp.Value...) + } + return svcs, nil } -func (a *PublicIPScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { - return map[string]azqr.AzqrRecommendation{} +func (a *PublicIPScanner) ResourceTypes() []string { + return []string{"Microsoft.Network/publicIPAddresses"} } diff --git a/internal/scanners/pip/rules.go b/internal/scanners/pip/rules.go new file mode 100644 index 00000000..655e747c --- /dev/null +++ b/internal/scanners/pip/rules.go @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package pip + +import ( + "strings" + + "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" +) + +// GetRules - Returns the rules for the PublicIPScanner +func (a *PublicIPScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { + return map[string]azqr.AzqrRecommendation{ + "pip-003": { + RecommendationID: "pip-003", + ResourceType: "Microsoft.Network/publicIPAddresses", + Category: azqr.CategoryHighAvailability, + Recommendation: "Public IP SLA", + RecommendationType: azqr.TypeSLA, + Impact: azqr.ImpactHigh, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + return false, "99.99%" + }, + LearnMoreUrl: "https://www.microsoft.com/licensing/docs/view/Service-Level-Agreements-SLA-for-Online-Services", + }, + "pip-006": { + RecommendationID: "pip-006", + ResourceType: "Microsoft.Network/publicIPAddresses", + Category: azqr.CategoryGovernance, + Recommendation: "Public IP Name should comply with naming conventions", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armnetwork.PublicIPAddress) + caf := strings.HasPrefix(*c.Name, "pip") + return !caf, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations", + }, + "pip-007": { + RecommendationID: "pip-007", + ResourceType: "Microsoft.Network/publicIPAddresses", + Category: azqr.CategoryGovernance, + Recommendation: "Public IP should have tags", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armnetwork.PublicIPAddress) + return len(c.Tags) == 0, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json", + }, + } +} diff --git a/internal/scanners/rt/rt.go b/internal/scanners/rt/rt.go index a79aed73..353c5844 100644 --- a/internal/scanners/rt/rt.go +++ b/internal/scanners/rt/rt.go @@ -5,29 +5,66 @@ package rt import ( "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" ) // RouteTableScanner - Scanner for Route Table type RouteTableScanner struct { config *azqr.ScannerConfig + client *armnetwork.RouteTablesClient } // Init - Initializes the Route Table Scanner func (a *RouteTableScanner) Init(config *azqr.ScannerConfig) error { a.config = config - return nil + var err error + a.client, err = armnetwork.NewRouteTablesClient(config.SubscriptionID, config.Cred, config.ClientOptions) + return err } // Scan - Scans all Route Table in a Resource Group -func (a *RouteTableScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { - azqr.LogSubscriptionScan(a.config.SubscriptionID, a.ResourceTypes()[0]) - return []azqr.AzqrServiceResult{}, nil +func (c *RouteTableScanner) Scan(scanContext *azqr.ScanContext) ([]azqr.AzqrServiceResult, error) { + azqr.LogSubscriptionScan(c.config.SubscriptionID, c.ResourceTypes()[0]) + + svcs, err := c.list() + if err != nil { + return nil, err + } + engine := azqr.RecommendationEngine{} + rules := c.GetRecommendations() + results := []azqr.AzqrServiceResult{} + + for _, w := range svcs { + rr := engine.EvaluateRecommendations(rules, w, scanContext) + + results = append(results, azqr.AzqrServiceResult{ + SubscriptionID: c.config.SubscriptionID, + SubscriptionName: c.config.SubscriptionName, + ResourceGroup: azqr.GetResourceGroupFromResourceID(*w.ID), + ServiceName: *w.Name, + Type: *w.Type, + Location: *w.Location, + Recommendations: rr, + }) + } + return results, nil +} + +func (c *RouteTableScanner) list() ([]*armnetwork.RouteTable, error) { + pager := c.client.NewListAllPager(nil) + + svcs := make([]*armnetwork.RouteTable, 0) + for pager.More() { + resp, err := pager.NextPage(c.config.Ctx) + if err != nil { + return nil, err + } + svcs = append(svcs, resp.Value...) + } + return svcs, nil } func (a *RouteTableScanner) ResourceTypes() []string { return []string{"Microsoft.Network/routeTables"} } -func (a *RouteTableScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { - return map[string]azqr.AzqrRecommendation{} -} diff --git a/internal/scanners/rt/rules.go b/internal/scanners/rt/rules.go new file mode 100644 index 00000000..03d0e3de --- /dev/null +++ b/internal/scanners/rt/rules.go @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package rt + +import ( + "strings" + + "github.com/Azure/azqr/internal/azqr" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" +) + +// GetRules - Returns the rules for the RouteTableScanner +func (a *RouteTableScanner) GetRecommendations() map[string]azqr.AzqrRecommendation { + return map[string]azqr.AzqrRecommendation{ + "udr-003": { + RecommendationID: "udr-003", + ResourceType: "Microsoft.Network/routeTables", + Category: azqr.CategoryHighAvailability, + Recommendation: "Rout Table SLA", + RecommendationType: azqr.TypeSLA, + Impact: azqr.ImpactHigh, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + return false, "99.99%" + }, + LearnMoreUrl: "https://www.microsoft.com/licensing/docs/view/Service-Level-Agreements-SLA-for-Online-Services", + }, + "udr-006": { + RecommendationID: "udr-006", + ResourceType: "Microsoft.Network/routeTables", + Category: azqr.CategoryGovernance, + Recommendation: "Rout Table Name should comply with naming conventions", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armnetwork.RouteTable) + caf := strings.HasPrefix(*c.Name, "rt") + return !caf, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations", + }, + "udr-007": { + RecommendationID: "udr-007", + ResourceType: "Microsoft.Network/routeTables", + Category: azqr.CategoryGovernance, + Recommendation: "Rout Table should have tags", + Impact: azqr.ImpactLow, + Eval: func(target interface{}, scanContext *azqr.ScanContext) (bool, string) { + c := target.(*armnetwork.RouteTable) + return len(c.Tags) == 0, "" + }, + LearnMoreUrl: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json", + }, + } +} diff --git a/internal/scanners/scanners_list.go b/internal/scanners/scanners_list.go index 209e01c9..b9add8b4 100644 --- a/internal/scanners/scanners_list.go +++ b/internal/scanners/scanners_list.go @@ -110,7 +110,7 @@ func GetScanners() []azqr.IAzureScanner { &maria.MariaScanner{}, &mysql.MySQLFlexibleScanner{}, &mysql.MySQLScanner{}, - &ng.NatGatewwayScanner{}, + &ng.NatGatewayScanner{}, &netapp.NetAppScanner{}, &nsg.NSGScanner{}, &nw.NetworkWatcherScanner{},