internal/models/models.go (303 lines of code) (raw):

// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. package models import ( "context" "errors" "fmt" "sort" "strings" "time" "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" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage" "github.com/rs/zerolog/log" ) type ( // ScannerConfig - Struct for Scanner Config ScannerConfig struct { Ctx context.Context Cred azcore.TokenCredential ClientOptions *arm.ClientOptions SubscriptionID string SubscriptionName string } // ScanContext - Struct for Scanner Context ScanContext struct { Filters *Filters PrivateEndpoints map[string]bool DiagnosticsSettings map[string]bool PublicIPs map[string]*armnetwork.PublicIPAddress SiteConfig *armappservice.WebAppsClientGetConfigurationResponse BlobServiceProperties *armstorage.BlobServicesClientGetServicePropertiesResponse } // IAzureScanner - Interface for all Azure Scanners IAzureScanner interface { Init(config *ScannerConfig) error GetRecommendations() map[string]AzqrRecommendation Scan(scanContext *ScanContext) ([]AzqrServiceResult, error) ResourceTypes() []string } // AzqrServiceResult - Struct for all Azure Service Results AzqrServiceResult struct { SubscriptionID string SubscriptionName string ResourceGroup string Location string Type string ServiceName string Recommendations map[string]AzqrResult } AzqrRecommendation struct { RecommendationID string ResourceType string Recommendation string Category RecommendationCategory Impact RecommendationImpact RecommendationType RecommendationType LearnMoreUrl string Eval func(target interface{}, scanContext *ScanContext) (bool, string) } AzqrResult struct { RecommendationID string ResourceType string Recommendation string Category RecommendationCategory Impact RecommendationImpact RecommendationType RecommendationType LearnMoreUrl string NotCompliant bool Result string } Resource struct { ID string SubscriptionID string ResourceGroup string Type string Location string Name string SkuName string SkuTier string Kind string SLA string } ResourceTypeCount struct { Subscription string `json:"Subscription"` ResourceType string `json:"Resource Type"` Count float64 `json:"Number of Resources"` AvailableInAPRL string `json:"Available In APRL?"` Custom1 string `json:"Custom1"` Custom2 string `json:"Custom2"` Custom3 string `json:"Custom3"` } AprlRecommendation struct { RecommendationID string `yaml:"aprlGuid"` Recommendation string `yaml:"description"` Category string `yaml:"recommendationControl"` Impact string `yaml:"recommendationImpact"` ResourceType string `yaml:"recommendationResourceType"` MetadataState string `yaml:"recommendationMetadataState"` LongDescription string `yaml:"longDescription"` PotentialBenefits string `yaml:"potentialBenefits"` PgVerified bool `yaml:"pgVerified"` AutomationAvailable string `yaml:"automationAvailable"` Tags []string `yaml:"tags,omitempty"` GraphQuery string `yaml:"graphQuery,omitempty"` LearnMoreLink []struct { Name string `yaml:"name"` Url string `yaml:"url"` } `yaml:"learnMoreLink,flow"` Source string } AprlResult struct { RecommendationID string ResourceType string Recommendation string LongDescription string PotentialBenefits string ResourceID string SubscriptionID string SubscriptionName string ResourceGroup string Name string Tags string Category RecommendationCategory Impact RecommendationImpact Learn string Param1 string Param2 string Param3 string Param4 string Param5 string AutomationAvailable string Source string } DefenderRecommendation struct { SubscriptionId string SubscriptionName string ResourceGroupName string ResourceType string ResourceName string Category string RecommendationSeverity string RecommendationName string ActionDescription string RemediationDescription string AzPortalLink string ResourceId string } // DefenderResult - Defender result DefenderResult struct { SubscriptionID, SubscriptionName, Name, Tier string } // CostResult - Cost result CostResult struct { From, To time.Time Items []*CostResultItem } // CostResultItem - Cost result ite, CostResultItem struct { SubscriptionID, SubscriptionName, ServiceName, Value, Currency string } // AdvisorResult - Advisor result AdvisorResult struct { RecommendationID, SubscriptionID, SubscriptionName, Type, Name, ResourceID, Category, Impact, Description string } RecommendationEngine struct{} RecommendationImpact string RecommendationCategory string RecommendationType string ) const ( ImpactHigh RecommendationImpact = "High" ImpactMedium RecommendationImpact = "Medium" ImpactLow RecommendationImpact = "Low" CategoryBusinessContinuity RecommendationCategory = "BusinessContinuity" CategoryDisasterRecovery RecommendationCategory = "DisasterRecovery" CategoryGovernance RecommendationCategory = "Governance" CategoryHighAvailability RecommendationCategory = "HighAvailability" CategoryMonitoringAndAlerting RecommendationCategory = "MonitoringAndAlerting" CategoryOtherBestPractices RecommendationCategory = "OtherBestPractices" CategoryScalability RecommendationCategory = "Scalability" CategorySecurity RecommendationCategory = "Security" CategoryServiceUpgradeAndRetirement RecommendationCategory = "ServiceUpgradeAndRetirement" TypeRecommendation RecommendationType = "" TypeSLA RecommendationType = "SLA" ) func (r *AzqrRecommendation) ToAzureAprlRecommendation() AprlRecommendation { return AprlRecommendation{ RecommendationID: r.RecommendationID, Recommendation: r.Recommendation, Category: string(r.Category), Impact: string(r.Impact), ResourceType: r.ResourceType, MetadataState: "", LongDescription: r.Recommendation, PotentialBenefits: "", PgVerified: false, AutomationAvailable: "", Tags: nil, GraphQuery: "", LearnMoreLink: []struct { Name string "yaml:\"name\"" Url string "yaml:\"url\"" }{{Name: "Learn More", Url: r.LearnMoreUrl}}, Source: "AZQR", } } func (e *RecommendationEngine) EvaluateRecommendations(rules map[string]AzqrRecommendation, target interface{}, scanContext *ScanContext) map[string]AzqrResult { results := map[string]AzqrResult{} for k, rule := range rules { if scanContext.Filters.Azqr.IsRecommendationExcluded(rule.RecommendationID) { continue } results[k] = e.evaluateRecommendation(rule, target, scanContext) } return results } func (e *RecommendationEngine) evaluateRecommendation(rule AzqrRecommendation, target interface{}, scanContext *ScanContext) AzqrResult { broken, result := rule.Eval(target, scanContext) return AzqrResult{ RecommendationID: rule.RecommendationID, Category: rule.Category, Recommendation: rule.Recommendation, RecommendationType: rule.RecommendationType, Impact: rule.Impact, LearnMoreUrl: rule.LearnMoreUrl, Result: result, NotCompliant: broken, } } func (r *AzqrServiceResult) ResourceID() string { return strings.ToLower(fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/%s", r.SubscriptionID, r.ResourceGroup, r.Type, r.ServiceName)) } func LogResourceGroupScan(subscriptionID string, resourceGroupName string, serviceTypeOrName string) { log.Info().Msgf("Scanning subscriptions/...%s/resourceGroups/%s for %s", subscriptionID[29:], resourceGroupName, serviceTypeOrName) } func LogSubscriptionScan(subscriptionID string, serviceTypeOrName string) { log.Info().Msgf("Scanning subscriptions/...%s for %s", subscriptionID[29:], serviceTypeOrName) } func LogResourceTypeScan(serviceType string) { log.Info().Msgf("Scanning subscriptions for %s", serviceType) } func ShouldSkipError(err error) bool { var respErr *azcore.ResponseError if errors.As(err, &respErr) { switch respErr.ErrorCode { case "MissingRegistrationForResourceProvider", "MissingSubscriptionRegistration", "DisallowedOperation": log.Warn().Msgf("Subscription failed with code: %s. Skipping Scan...", respErr.ErrorCode) return true } } return false } func ListResourceGroup(ctx context.Context, cred azcore.TokenCredential, subscriptionID string, options *arm.ClientOptions) ([]*armresources.ResourceGroup, error) { resourceGroupClient, err := armresources.NewResourceGroupsClient(subscriptionID, cred, options) if err != nil { return nil, err } resultPager := resourceGroupClient.NewListPager(nil) resourceGroups := make([]*armresources.ResourceGroup, 0) for resultPager.More() { pageResp, err := resultPager.NextPage(ctx) if err != nil { return nil, err } resourceGroups = append(resourceGroups, pageResp.Value...) } return resourceGroups, nil } // GetSubscriptionFromResourceID - Get Subscription ID from Resource ID func GetSubscriptionFromResourceID(resourceID string) string { parts := strings.Split(resourceID, "/") if len(parts) < 3 { return "" } 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], "/") } // GetResourceNameFromResourceID - Get Resource Type from Resource ID func GetResourceTypeFromResourceID(resourceID string) string { parts := strings.Split(resourceID, "/") if len(parts) < 8 { return "" } return fmt.Sprintf("%s/%s", parts[6], parts[7]) } // ScannerList is a map of service abbreviation to scanner var ScannerList = map[string][]IAzureScanner{} // GetScanners returns a list of all scanners in ScannerList func GetScanners() ([]string, []IAzureScanner) { var scanners []IAzureScanner keys := make([]string, 0, len(ScannerList)) for key := range ScannerList { keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { scanners = append(scanners, ScannerList[key]...) } return keys, scanners }