Skip to content

Commit

Permalink
chore: Improved filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
cmendible committed Jul 30, 2024
1 parent fa85e75 commit 5afed26
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 154 deletions.
3 changes: 2 additions & 1 deletion cmd/azqr/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ var rulesCmd = &cobra.Command{
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
serviceScanners := scanners.GetScanners()
aprl := internal.GetAprlRecommendations()
aprlScanner := internal.AprlScanner{}
aprl := aprlScanner.GetAprlRecommendations()

fmt.Println("# | Id | Resource Type | Category | Impact | Recommendation | Learn")
fmt.Println("---|---|---|---|---|---|---")
Expand Down
3 changes: 2 additions & 1 deletion cmd/azqr/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,6 @@ func scan(cmd *cobra.Command, serviceScanners []azqr.IAzureScanner) {
UseAzqrRecommendations: azqr,
}

internal.Scan(&params)
scanner := internal.Scanner{}
scanner.Scan(&params)
}
36 changes: 28 additions & 8 deletions internal/aprl_scan.go → internal/aprl_scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"time"

"github.com/Azure/azqr/internal/azqr"
"github.com/Azure/azqr/internal/filters"
"github.com/Azure/azqr/internal/graph"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/rs/zerolog/log"
Expand All @@ -26,8 +25,12 @@ import (
//go:embed aprl/azure-specialized-workloads/**/kql/*.kql
var embededFiles embed.FS

type (
AprlScanner struct{}
)

// GetAprlRecommendations returns a map with all APRL recommendations
func GetAprlRecommendations() map[string]map[string]azqr.AprlRecommendation {
func (sc AprlScanner) GetAprlRecommendations() map[string]map[string]azqr.AprlRecommendation {
r := map[string]map[string]azqr.AprlRecommendation{}

fsys, err := fs.Sub(embededFiles, "aprl/azure-resources")
Expand Down Expand Up @@ -95,19 +98,19 @@ func GetAprlRecommendations() map[string]map[string]azqr.AprlRecommendation {
}

// AprlScan scans Azure resources using Azure Proactive Resiliency Library v2 (APRL)
func AprlScan(ctx context.Context, cred azcore.TokenCredential, params *ScanParams, filters *filters.Filters, subscriptions map[string]string) (map[string]map[string]azqr.AprlRecommendation, []azqr.AprlResult) {
func (sc AprlScanner) Scan(ctx context.Context, cred azcore.TokenCredential, serviceScanners []azqr.IAzureScanner, filters *azqr.Filters, subscriptions map[string]string) (map[string]map[string]azqr.AprlRecommendation, []azqr.AprlResult) {
recommendations := map[string]map[string]azqr.AprlRecommendation{}
results := []azqr.AprlResult{}
rules := []azqr.AprlRecommendation{}
graph := graph.NewGraphQuery(cred)

// get APRL recommendations
aprl := GetAprlRecommendations()
aprl := sc.GetAprlRecommendations()

for _, s := range params.ServiceScanners {
for _, s := range serviceScanners {
for _, t := range s.ResourceTypes() {
azqr.LogResourceTypeScan(t)
gr := azqr.GetGraphRules(t, aprl)
gr := getGraphRules(t, filters, aprl)
for _, r := range gr {
rules = append(rules, r)
}
Expand Down Expand Up @@ -148,7 +151,7 @@ func AprlScan(ctx context.Context, cred azcore.TokenCredential, params *ScanPara
s := time.Duration(b * 7)
time.Sleep(s * time.Second)
}
res, err := graphScan(ctx, graph, r, subscriptions)
res, err := sc.graphScan(ctx, graph, r, subscriptions)
if err != nil {
log.Fatal().Err(err).Msg("Failed to scan")
}
Expand All @@ -171,7 +174,7 @@ func AprlScan(ctx context.Context, cred azcore.TokenCredential, params *ScanPara
return recommendations, results
}

func graphScan(ctx context.Context, graphClient *graph.GraphQuery, rules []azqr.AprlRecommendation, subscriptions map[string]string) ([]azqr.AprlResult, error) {
func (sc AprlScanner) graphScan(ctx context.Context, graphClient *graph.GraphQuery, rules []azqr.AprlRecommendation, subscriptions map[string]string) ([]azqr.AprlResult, error) {
results := []azqr.AprlResult{}
subs := make([]*string, 0, len(subscriptions))
for s := range subscriptions {
Expand Down Expand Up @@ -258,3 +261,20 @@ func graphScan(ctx context.Context, graphClient *graph.GraphQuery, rules []azqr.

return results, nil
}

func getGraphRules(service string, filters *azqr.Filters, aprl map[string]map[string]azqr.AprlRecommendation) map[string]azqr.AprlRecommendation {
r := map[string]azqr.AprlRecommendation{}
if i, ok := aprl[strings.ToLower(service)]; ok {
for _, recommendation := range i {
if filters.Azqr.Exclude.IsRecommendationExcluded(recommendation.RecommendationID) ||
strings.Contains(recommendation.GraphQuery, "cannot-be-validated-with-arg") ||
strings.Contains(recommendation.GraphQuery, "under-development") ||
strings.Contains(recommendation.GraphQuery, "under development") {
continue
}

r[recommendation.RecommendationID] = recommendation
}
}
return r
}
155 changes: 72 additions & 83 deletions internal/azqr/azqr.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"fmt"
"strings"

"github.com/Azure/azqr/internal/filters"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v2"
Expand All @@ -32,7 +31,7 @@ type (

// ScanContext - Struct for Scanner Context
ScanContext struct {
Exclusions *filters.Exclude
Filters *Filters
PrivateEndpoints map[string]bool
DiagnosticsSettings map[string]bool
PublicIPs map[string]*armnetwork.PublicIPAddress
Expand Down Expand Up @@ -191,9 +190,6 @@ func (e *RecommendationEngine) EvaluateRecommendations(rules map[string]AzqrReco
results := map[string]AzqrResult{}

for k, rule := range rules {
if scanContext.Exclusions.IsRecommendationExcluded(rule.RecommendationID) {
continue
}
results[k] = e.evaluateRecommendation(rule, target, scanContext)
}

Expand Down Expand Up @@ -254,38 +250,6 @@ func LogResourceTypeScan(serviceType string) {
log.Info().Msgf("Scanning subscriptions for %s", serviceType)
}

// GetGraphRules - Get Graph Rules for a service type
func GetGraphRules(service string, aprl map[string]map[string]AprlRecommendation) map[string]AprlRecommendation {
r := map[string]AprlRecommendation{}
if i, ok := aprl[strings.ToLower(service)]; ok {
for _, recommendation := range i {
if strings.Contains(recommendation.GraphQuery, "cannot-be-validated-with-arg") ||
strings.Contains(recommendation.GraphQuery, "under-development") ||
strings.Contains(recommendation.GraphQuery, "under development") {
continue
}

r[recommendation.RecommendationID] = recommendation
}
}
return r
}

// GetSubsctiptionFromResourceID - Get Subscription ID from Resource ID
func GetSubsctiptionFromResourceID(resourceID string) string {
parts := strings.Split(resourceID, "/")
return parts[2]
}

// GetResourceGroupFromResourceID - Get Resource Group from Resource ID
func GetResourceGroupFromResourceID(resourceID string) string {
parts := strings.Split(resourceID, "/")
if len(parts) < 5 {
return ""
}
return parts[4]
}

func ShouldSkipError(err error) bool {
var respErr *azcore.ResponseError
if errors.As(err, &respErr) {
Expand All @@ -298,52 +262,52 @@ func ShouldSkipError(err error) bool {
return false
}

func ListResourceGroups(ctx context.Context, cred azcore.TokenCredential, resourceGroup string, subscriptionID string, exclusions *filters.Filters, options *arm.ClientOptions) []string {
resourceGroups := []string{}
if resourceGroup != "" {
exists, err := checkExistenceResourceGroup(ctx, subscriptionID, resourceGroup, cred, options)
if err != nil {
log.Fatal().Err(err).Msg("Failed to check existence of Resource Group")
}

if !exists {
log.Fatal().Msgf("Resource Group %s does not exist", resourceGroup)
}

if exclusions.Azqr.Exclude.IsResourceGroupExcluded(fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", subscriptionID, resourceGroup)) {
log.Info().Msgf("Skipping subscriptions/...%s/resourceGroups/%s", subscriptionID[29:], resourceGroup)
return resourceGroups
}

resourceGroups = append(resourceGroups, resourceGroup)
} else {
rgs, err := ListResourceGroup(ctx, cred, subscriptionID, options)
if err != nil {
log.Fatal().Err(err).Msg("Failed to list Resource Groups")
}
for _, rg := range rgs {
if exclusions.Azqr.Exclude.IsResourceGroupExcluded(fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", subscriptionID, *rg.Name)) {
log.Info().Msgf("Skipping subscriptions/...%s/resourceGroups/%s", subscriptionID[29:], *rg.Name)
continue
}
resourceGroups = append(resourceGroups, *rg.Name)
}
}
return resourceGroups
}

func checkExistenceResourceGroup(ctx context.Context, subscriptionID string, resourceGroupName string, cred azcore.TokenCredential, options *arm.ClientOptions) (bool, error) {
resourceGroupClient, err := armresources.NewResourceGroupsClient(subscriptionID, cred, options)
if err != nil {
return false, err
}

boolResp, err := resourceGroupClient.CheckExistence(ctx, resourceGroupName, nil)
if err != nil {
return false, err
}
return boolResp.Success, nil
}
// func ListResourceGroups(ctx context.Context, cred azcore.TokenCredential, resourceGroup string, subscriptionID string, exclusions *filters.Filters, options *arm.ClientOptions) []string {
// resourceGroups := []string{}
// if resourceGroup != "" {
// exists, err := checkExistenceResourceGroup(ctx, subscriptionID, resourceGroup, cred, options)
// if err != nil {
// log.Fatal().Err(err).Msg("Failed to check existence of Resource Group")
// }

// if !exists {
// log.Fatal().Msgf("Resource Group %s does not exist", resourceGroup)
// }

// if exclusions.Azqr.Exclude.IsResourceGroupExcluded(fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", subscriptionID, resourceGroup)) {
// log.Info().Msgf("Skipping subscriptions/...%s/resourceGroups/%s", subscriptionID[29:], resourceGroup)
// return resourceGroups
// }

// resourceGroups = append(resourceGroups, resourceGroup)
// } else {
// rgs, err := ListResourceGroup(ctx, cred, subscriptionID, options)
// if err != nil {
// log.Fatal().Err(err).Msg("Failed to list Resource Groups")
// }
// for _, rg := range rgs {
// if exclusions.Azqr.Exclude.IsResourceGroupExcluded(fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", subscriptionID, *rg.Name)) {
// log.Info().Msgf("Skipping subscriptions/...%s/resourceGroups/%s", subscriptionID[29:], *rg.Name)
// continue
// }
// resourceGroups = append(resourceGroups, *rg.Name)
// }
// }
// return resourceGroups
// }

// func checkExistenceResourceGroup(ctx context.Context, subscriptionID string, resourceGroupName string, cred azcore.TokenCredential, options *arm.ClientOptions) (bool, error) {
// resourceGroupClient, err := armresources.NewResourceGroupsClient(subscriptionID, cred, options)
// if err != nil {
// return false, err
// }

// boolResp, err := resourceGroupClient.CheckExistence(ctx, resourceGroupName, nil)
// if err != nil {
// return false, err
// }
// return boolResp.Success, nil
// }

func ListResourceGroup(ctx context.Context, cred azcore.TokenCredential, subscriptionID string, options *arm.ClientOptions) ([]*armresources.ResourceGroup, error) {
resourceGroupClient, err := armresources.NewResourceGroupsClient(subscriptionID, cred, options)
Expand All @@ -363,3 +327,28 @@ func ListResourceGroup(ctx context.Context, cred azcore.TokenCredential, subscri
}
return resourceGroups, nil
}

// GetSubsctiptionFromResourceID - Get Subscription ID from Resource ID
func GetSubsctiptionFromResourceID(resourceID string) string {
parts := strings.Split(resourceID, "/")
return parts[2]
}

// GetResourceGroupFromResourceID - Get Resource Group from Resource ID
func GetResourceGroupFromResourceID(resourceID string) string {
parts := strings.Split(resourceID, "/")
if len(parts) < 5 {
return ""
}
return parts[4]
}

// GetResourceGroupIDFromResourceID - Get Resource Group from Resource ID
func GetResourceGroupIDFromResourceID(resourceID string) string {
parts := strings.Split(resourceID, "/")
if len(parts) < 5 {
return ""
}

return strings.Join(parts[:5], "/")
}
42 changes: 24 additions & 18 deletions internal/filters/filters.go → internal/azqr/filters.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package filters
package azqr

import (
"os"
"strings"
"gopkg.in/yaml.v3"

"github.com/rs/zerolog/log"
"gopkg.in/yaml.v3"
)

type (
Expand Down Expand Up @@ -44,28 +45,20 @@ func (e *Exclude) IsSubscriptionExcluded(subscriptionID string) bool {
return ok
}

func (e *Exclude) IsResourceGroupExcluded(resourceGroupID string) bool {
if e.resourceGroups == nil {
e.resourceGroups = make(map[string]bool)
for _, id := range e.ResourceGroups {
e.resourceGroups[strings.ToLower(id)] = true
}
}

_, ok := e.resourceGroups[strings.ToLower(resourceGroupID)]

return ok
}

func (e *Exclude) IsServiceExcluded(serviceID string) bool {
func (e *Exclude) IsServiceExcluded(resourceID string) bool {
if e.services == nil {
e.services = make(map[string]bool)
for _, id := range e.Services {
e.services[strings.ToLower(id)] = true
}
}

_, ok := e.services[strings.ToLower(serviceID)]
_, ok := e.services[strings.ToLower(resourceID)]

if !ok {
rgID := GetResourceGroupIDFromResourceID(resourceID)
ok = e.isResourceGroupExcluded(rgID)
}

return ok
}
Expand All @@ -83,7 +76,7 @@ func (e *Exclude) IsRecommendationExcluded(recommendationID string) bool {
return ok
}

func LoadFilters(filterFile string) (*Filters) {
func LoadFilters(filterFile string) *Filters {
filters := &Filters{
Azqr: &AzqrFilter{
Exclude: &Exclude{
Expand All @@ -108,3 +101,16 @@ func LoadFilters(filterFile string) (*Filters) {

return filters
}

func (e *Exclude) isResourceGroupExcluded(resourceGroupID string) bool {
if e.resourceGroups == nil {
e.resourceGroups = make(map[string]bool)
for _, id := range e.ResourceGroups {
e.resourceGroups[strings.ToLower(id)] = true
}
}

_, ok := e.resourceGroups[strings.ToLower(resourceGroupID)]

return ok
}
16 changes: 16 additions & 0 deletions internal/renderers/report_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,3 +301,19 @@ func (rd *ReportData) ResourceTypesTable() [][]string {
rows = append([][]string{headers}, rows...)
return rows
}

func NewReportData(outputFile string, mask bool) ReportData {
return ReportData{
OutputFileName: outputFile,
Mask: mask,
Recomendations: map[string]map[string]azqr.AprlRecommendation{},
AzqrData: []azqr.AzqrServiceResult{},
AprlData: []azqr.AprlResult{},
DefenderData: []scanners.DefenderResult{},
AdvisorData: []scanners.AdvisorResult{},
CostData: &scanners.CostResult{
Items: []*scanners.CostResultItem{},
},
ResourceTypeCount: []azqr.ResourceTypeCount{},
}
}
Loading

0 comments on commit 5afed26

Please sign in to comment.