Skip to content

Commit

Permalink
feature: add azure analysis service scanner (#221)
Browse files Browse the repository at this point in the history
* feature: add azure analytics service scanner

Co-authored-by: Carlos Mendible <266546+cmendible@users.noreply.github.com>
  • Loading branch information
vanwinkelseppe and cmendible authored Apr 25, 2024
1 parent e0de39d commit 4b5718a
Show file tree
Hide file tree
Showing 9 changed files with 287 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ To learn more about the recommendations used by **Azure Quick Review (azqr)**, y

**Azure Quick Review (azqr)** currently supports the following Azure services:

* Azure Analysis Service
* Azure API Management
* Azure App Configuration
* Azure App Services
Expand Down
28 changes: 28 additions & 0 deletions cmd/azqr/as.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package azqr

import (
"github.com/Azure/azqr/internal/scanners"
"github.com/Azure/azqr/internal/scanners/as"
"github.com/spf13/cobra"
)

func init() {
scanCmd.AddCommand(asCmd)
}

var asCmd = &cobra.Command{
Use: "as",
Short: "Scan Azure Analysis Service",
Long: "Scan Azure Analysis Service",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
serviceScanners := []scanners.IAzureScanner{
&as.AnalysisServicesScanner{},
}

scan(cmd, serviceScanners)
},
}
1 change: 1 addition & 0 deletions docs/content/en/docs/Overview/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ To learn more about the recommendations used by **Azure Quick Review (azqr)**, y

**Azure Quick Review (azqr)** currently supports the following Azure services:

* Azure Analysis Service
* Azure API Management
* Azure App Configuration
* Azure App Services
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/advisor/armadvisor v1.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/analysisservices/armanalysisservices v1.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement v1.1.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappconfiguration v1.1.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v2 v2.1.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.6.0 h1:sUFnFjzDUie80h24I7mrKtw
github.com/Azure/azure-sdk-for-go/sdk/internal v1.6.0/go.mod h1:52JbnQTp15qg5mRkMBHwp0j0ZFwHJ42Sx3zVV5RE9p0=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/advisor/armadvisor v1.2.0 h1:3ddjPq/3A/oB2u7LdohEr900EGP5l1MnAiNc3EbY1E4=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/advisor/armadvisor v1.2.0/go.mod h1:oZ73p8dR7aZI+TJo5Ul92oCoVubMYPBo39eTsWa0AiQ=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/analysisservices/armanalysisservices v1.2.0 h1:iCQzouPqEJkjvgXooBv/AlUp16HHB+fR5S7CU1wDns8=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/analysisservices/armanalysisservices v1.2.0/go.mod h1:IDarmBQ4VwelvI5xEwBtDommOZYwb5xAsGS/ok1MYcg=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement v1.1.1 h1:jCkNVNpsEevyic4bmjgVjzVA4tMGSJpXNGirf+S+mDI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement v1.1.1/go.mod h1:a0Ug1l73Il7EhrCJEEt2dGjlNjvphppZq5KqJdgnwuw=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappconfiguration v1.1.1 h1:iRc20pGuVlc1HwRO2bg0m1tfP9rkPB0K88trl8Fei2w=
Expand Down
2 changes: 2 additions & 0 deletions internal/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/Azure/azqr/internal/scanners/apim"
"github.com/Azure/azqr/internal/scanners/appcs"
"github.com/Azure/azqr/internal/scanners/appi"
"github.com/Azure/azqr/internal/scanners/as"
"github.com/Azure/azqr/internal/scanners/asp"
"github.com/Azure/azqr/internal/scanners/ca"
"github.com/Azure/azqr/internal/scanners/cae"
Expand Down Expand Up @@ -451,6 +452,7 @@ func GetScanners() []scanners.IAzureScanner {
&apim.APIManagementScanner{},
&appcs.AppConfigurationScanner{},
&appi.AppInsightsScanner{},
&as.AnalysisServicesScanner{},
&cae.ContainerAppsEnvironmentScanner{},
&ca.ContainerAppsScanner{},
&ci.ContainerInstanceScanner{},
Expand Down
64 changes: 64 additions & 0 deletions internal/scanners/as/as.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package as

import (
"github.com/Azure/azqr/internal/scanners"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/analysisservices/armanalysisservices"
)

// AnalysisServicesScanner - Scanner for Analysis Services
type AnalysisServicesScanner struct {
config *scanners.ScannerConfig
client *armanalysisservices.ServersClient
}

// Init - Initializes the AnalysisServicesScanner
func (c *AnalysisServicesScanner) Init(config *scanners.ScannerConfig) error {
c.config = config
var err error
c.client, err = armanalysisservices.NewServersClient(config.SubscriptionID, config.Cred, config.ClientOptions)
return err
}

// Scan - Scans all Analysis Services in a Resource Group
func (c *AnalysisServicesScanner) Scan(resourceGroupName string, scanContext *scanners.ScanContext) ([]scanners.AzureServiceResult, error) {
scanners.LogResourceGroupScan(c.config.SubscriptionID, resourceGroupName, "Analysis Services")

workspaces, err := c.listWorkspaces(resourceGroupName)
if err != nil {
return nil, err
}
engine := scanners.RuleEngine{}
rules := c.GetRules()
results := []scanners.AzureServiceResult{}

for _, ws := range workspaces {
rr := engine.EvaluateRules(rules, ws, scanContext)

results = append(results, scanners.AzureServiceResult{
SubscriptionID: c.config.SubscriptionID,
ResourceGroup: resourceGroupName,
ServiceName: *ws.Name,
Type: *ws.Type,
Location: *ws.Location,
Rules: rr,
})
}
return results, nil
}

func (c *AnalysisServicesScanner) listWorkspaces(resourceGroupName string) ([]*armanalysisservices.Server, error) {
pager := c.client.NewListByResourceGroupPager(resourceGroupName, nil)

registries := make([]*armanalysisservices.Server, 0)
for pager.More() {
resp, err := pager.NextPage(c.config.Ctx)
if err != nil {
return nil, err
}
registries = append(registries, resp.Value...)
}
return registries, nil
}
79 changes: 79 additions & 0 deletions internal/scanners/as/rules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package as

import (
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/analysisservices/armanalysisservices"
"strings"

"github.com/Azure/azqr/internal/scanners"
)

// GetRules - Returns the rules for the AnalysisServicesScanner
func (a *AnalysisServicesScanner) GetRules() map[string]scanners.AzureRule {
return map[string]scanners.AzureRule{
"as-001": {
Id: "as-001",
Category: scanners.RulesCategoryMonitoringAndAlerting,
Recommendation: "Azure Analysis Service should have diagnostic settings enabled",
Impact: scanners.ImpactLow,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
service := target.(*armanalysisservices.Server)
_, ok := scanContext.DiagnosticsSettings[strings.ToLower(*service.ID)]
return !ok, ""
},
Url: "https://learn.microsoft.com/en-us/azure/analysis-services/analysis-services-logging",
},
"as-002": {
Id: "as-002",
Category: scanners.RulesCategoryHighAvailability,
Recommendation: "Azure Analysis Service should have a SLA",
Impact: scanners.ImpactHigh,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
i := target.(*armanalysisservices.Server)
sku := *i.SKU.Tier
sla := "None"
if sku != armanalysisservices.SKUTierBasic {
sla = "99.9%"
}
return sla == "None", sla
},
Url: "https://www.microsoft.com/licensing/docs/view/Service-Level-Agreements-SLA-for-Online-Services",
},
"as-003": {
Id: "as-003",
Category: scanners.RulesCategoryHighAvailability,
Recommendation: "Azure Analysis Service SKU",
Impact: scanners.ImpactHigh,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
i := target.(*armanalysisservices.Server)
return false, string(*i.SKU.Name)
},
Url: "https://azure.microsoft.com/en-us/pricing/details/analysis-services/",
},
"as-004": {
Id: "as-004",
Category: scanners.RulesCategoryGovernance,
Recommendation: "Azure Analysis Service Name should comply with naming conventions",
Impact: scanners.ImpactLow,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
c := target.(*armanalysisservices.Server)
caf := strings.HasPrefix(*c.Name, "as")
return !caf, ""
},
Url: "https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations",
},
"as-005": {
Id: "as-005",
Category: scanners.RulesCategoryGovernance,
Recommendation: "Azure Analysis Service should have tags",
Impact: scanners.ImpactLow,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
c := target.(*armanalysisservices.Server)
return len(c.Tags) == 0, ""
},
Url: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json",
},
}
}
109 changes: 109 additions & 0 deletions internal/scanners/as/rules_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package as

import (
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/analysisservices/armanalysisservices"
"reflect"
"testing"

"github.com/Azure/azqr/internal/scanners"
"github.com/Azure/azqr/internal/to"
)

func TestAnalysisServicesScanner_Rules(t *testing.T) {
type fields struct {
rule string
target interface{}
scanContext *scanners.ScanContext
}
type want struct {
broken bool
result string
}
tests := []struct {
name string
fields fields
want want
}{
{
name: "AnalysisServicesScanner DiagnosticSettings",
fields: fields{
rule: "as-001",
target: &armanalysisservices.Server{
ID: to.Ptr("test"),
},
scanContext: &scanners.ScanContext{
DiagnosticsSettings: map[string]bool{
"test": true,
},
},
},
want: want{
broken: false,
result: "",
},
},
{
name: "AnalysisServicesScanner SLA",
fields: fields{
rule: "as-002",
target: &armanalysisservices.Server{
SKU: &armanalysisservices.ResourceSKU{
Tier: to.Ptr(armanalysisservices.SKUTierBasic),
},
},
scanContext: &scanners.ScanContext{},
},
want: want{
broken: true,
result: "None",
},
},
{
name: "AnalysisServicesScanner SLA",
fields: fields{
rule: "as-002",
target: &armanalysisservices.Server{
SKU: &armanalysisservices.ResourceSKU{
Tier: to.Ptr(armanalysisservices.SKUTierStandard),
},
},
scanContext: &scanners.ScanContext{},
},
want: want{
broken: false,
result: "99.9%",
},
},
{
name: "AnalysisServicesScanner CAF",
fields: fields{
rule: "as-004",
target: &armanalysisservices.Server{
Name: to.Ptr("as-test"),
},
scanContext: &scanners.ScanContext{},
},
want: want{
broken: false,
result: "",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &AnalysisServicesScanner{}
rules := s.GetRules()
b, w := rules[tt.fields.rule].Eval(tt.fields.target, tt.fields.scanContext)
got := want{
broken: b,
result: w,
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("AnalysisServicesScanner Rule.Eval() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit 4b5718a

Please sign in to comment.