Skip to content

Commit

Permalink
Implementing Cognitive Service Account Scanner #102
Browse files Browse the repository at this point in the history
  • Loading branch information
cmendible committed Jul 17, 2023
1 parent 17f58ff commit 32e1e9f
Show file tree
Hide file tree
Showing 9 changed files with 323 additions and 0 deletions.
28 changes: 28 additions & 0 deletions cmd/azqr/cog.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/cog"
"github.com/spf13/cobra"
)

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

var cogCmd = &cobra.Command{
Use: "cog",
Short: "Scan Azure Cognitive Service Accounts",
Long: "Scan Azure Cognitive Service Accounts",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
serviceScanners := []scanners.IAzureScanner{
&cog.CognitiveScanner{},
}

scan(cmd, serviceScanners)
},
}
2 changes: 2 additions & 0 deletions cmd/azqr/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/Azure/azqr/internal/scanners/appi"
"github.com/Azure/azqr/internal/scanners/cae"
"github.com/Azure/azqr/internal/scanners/ci"
"github.com/Azure/azqr/internal/scanners/cog"
"github.com/Azure/azqr/internal/scanners/cosmos"
"github.com/Azure/azqr/internal/scanners/cr"
"github.com/Azure/azqr/internal/scanners/evgd"
Expand Down Expand Up @@ -78,6 +79,7 @@ var rulesCmd = &cobra.Command{
&lb.LoadBalancerScanner{},
&vnet.VirtualNetworkScanner{},
&vm.VirtualMachineScanner{},
&cog.CognitiveScanner{},
}

fmt.Println("# | Id | Category | Subcategory | Name | Severity | More Info")
Expand Down
2 changes: 2 additions & 0 deletions cmd/azqr/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/Azure/azqr/internal/scanners/appi"
"github.com/Azure/azqr/internal/scanners/cae"
"github.com/Azure/azqr/internal/scanners/ci"
"github.com/Azure/azqr/internal/scanners/cog"
"github.com/Azure/azqr/internal/scanners/cosmos"
"github.com/Azure/azqr/internal/scanners/cr"
"github.com/Azure/azqr/internal/scanners/evgd"
Expand Down Expand Up @@ -98,6 +99,7 @@ var scanCmd = &cobra.Command{
&lb.LoadBalancerScanner{},
&vnet.VirtualNetworkScanner{},
&vm.VirtualMachineScanner{},
&cog.CognitiveScanner{},
}
scan(cmd, serviceScanners)
},
Expand Down
6 changes: 6 additions & 0 deletions docs/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,9 @@ Azure Quick Review uses the following rules to identify Azure resources that may
210 | vm-007 | Operational Excellence | Tags | Virtual Machine should have tags | Low | https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json
211 | vm-008 | Reliability | Reliability | Virtual Machine should use managed disks | High | https://learn.microsoft.com/en-us/azure/architecture/checklist/resiliency-per-service#virtual-machines
212 | vm-009 | Reliability | Reliability | Virtual Machine should host application or database data on a data disk | Low | https://learn.microsoft.com/azure/virtual-machines/managed-disks-overview#data-disk
213 | cog-001 | Reliability | Diagnostic Logs | Cognitive Service Account should have diagnostic settings enabled | Medium | https://learn.microsoft.com/en-us/azure/event-hubs/monitor-event-hubs#collection-and-routing
214 | cog-003 | Reliability | SLA | Cognitive Service Account should have a SLA | High | https://www.microsoft.com/licensing/docs/view/Service-Level-Agreements-SLA-for-Online-Services?lang=1
215 | cog-004 | Security | Private Endpoint | Cognitive Service Account should have private endpoints enabled | High | https://learn.microsoft.com/en-us/azure/cognitive-services/cognitive-services-virtual-networks
216 | cog-005 | Reliability | SKU | Cognitive Service Account SKU | High | https://learn.microsoft.com/en-us/azure/templates/microsoft.cognitiveservices/accounts?pivots=deployment-language-bicep#sku
217 | cog-006 | Operational Excellence | Naming Convention (CAF) | Cognitive Service Account Name should comply with naming conventions | Low | https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations
218 | cog-007 | Operational Excellence | Tags | Cognitive Service Account should have tags | Low | https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights v1.1.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v2 v2.0.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cdn/armcdn v1.0.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.4.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerinstance/armcontainerinstance v1.0.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry v0.6.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v2 v2.0.0/go.mod h1:q3eRORy1pK+iwccG8yKZfRjhdJXTUs5LF2j9hA/MNNs=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cdn/armcdn v1.0.0 h1:mZYozuxvzO83ZtKST8TYGqvj++a3ehiworh8zuxfxOo=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cdn/armcdn v1.0.0/go.mod h1:9HKN4ZiYtjIYAEsr76ecdVsMO7DdUzOGo6tgu3Olf5w=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.4.1 h1:ynIxbR7wH5nBEJzprbeBFVBtoYTYcQbN39vM7eNS3Xc=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.4.1/go.mod h1:nUhnLNlOtAVpn/PRwJKIf3ulXLvdMiWlGk8nufEUaKc=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1 h1:UPeCRD+XY7QlaGQte2EVI2iOcWvUYA2XY8w5T/8v0NQ=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1/go.mod h1:oGV6NlB0cvi1ZbYRR2UN44QHxWFyGk+iylgD0qaMXjA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerinstance/armcontainerinstance v1.0.0 h1:yKmuPI8w+5rXTMa4G5xrzwz9aGEkS6t4Gx/cRBnuh+M=
Expand Down
66 changes: 66 additions & 0 deletions internal/scanners/cog/cog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package cog

import (
"github.com/rs/zerolog/log"

"github.com/Azure/azqr/internal/scanners"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices"
)

// CognitiveScanner - Scanner for Cognitive Services Accounts
type CognitiveScanner struct {
config *scanners.ScannerConfig
client *armcognitiveservices.AccountsClient
}

// Init - Initializes the CognitiveScanner
func (a *CognitiveScanner) Init(config *scanners.ScannerConfig) error {
a.config = config
var err error
a.client, err = armcognitiveservices.NewAccountsClient(config.SubscriptionID, config.Cred, config.ClientOptions)
return err
}

// Scan - Scans all Cognitive Services Accounts in a Resource Group
func (c *CognitiveScanner) Scan(resourceGroupName string, scanContext *scanners.ScanContext) ([]scanners.AzureServiceResult, error) {
log.Info().Msgf("Scanning Cognitive Services Accounts in Resource Group %s", resourceGroupName)

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

for _, eventHub := range eventHubs {
rr := engine.EvaluateRules(rules, eventHub, scanContext)

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

func (c *CognitiveScanner) listEventHubs(resourceGroupName string) ([]*armcognitiveservices.Account, error) {
pager := c.client.NewListByResourceGroupPager(resourceGroupName, nil)

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

package cog

import (
"strings"

"github.com/Azure/azqr/internal/scanners"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices"
)

// GetRules - Returns the rules for the CognitiveScanner
func (a *CognitiveScanner) GetRules() map[string]scanners.AzureRule {
return map[string]scanners.AzureRule{
"DiagnosticSettings": {
Id: "cog-001",
Category: scanners.RulesCategoryReliability,
Subcategory: scanners.RulesSubcategoryReliabilityDiagnosticLogs,
Description: "Cognitive Service Account should have diagnostic settings enabled",
Severity: scanners.SeverityMedium,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
service := target.(*armcognitiveservices.Account)
_, ok := scanContext.DiagnosticsSettings[strings.ToLower(*service.ID)]
return !ok, ""
},
Url: "https://learn.microsoft.com/en-us/azure/event-hubs/monitor-event-hubs#collection-and-routing",
},
"SLA": {
Id: "cog-003",
Category: scanners.RulesCategoryReliability,
Subcategory: scanners.RulesSubcategoryReliabilitySLA,
Description: "Cognitive Service Account should have a SLA",
Severity: scanners.SeverityHigh,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
return false, "99.9%"
},
Url: "https://www.microsoft.com/licensing/docs/view/Service-Level-Agreements-SLA-for-Online-Services?lang=1",
},
"Private": {
Id: "cog-004",
Category: scanners.RulesCategorySecurity,
Subcategory: scanners.RulesSubcategorySecurityPrivateEndpoint,
Description: "Cognitive Service Account should have private endpoints enabled",
Severity: scanners.SeverityHigh,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
i := target.(*armcognitiveservices.Account)
pe := len(i.Properties.PrivateEndpointConnections) > 0
return !pe, ""
},
Url: "https://learn.microsoft.com/en-us/azure/cognitive-services/cognitive-services-virtual-networks",
},
"SKU": {
Id: "cog-005",
Category: scanners.RulesCategoryReliability,
Subcategory: scanners.RulesSubcategoryReliabilitySKU,
Description: "Cognitive Service Account SKU",
Severity: scanners.SeverityHigh,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
i := target.(*armcognitiveservices.Account)
return false, string(*i.SKU.Name)
},
Url: "https://learn.microsoft.com/en-us/azure/templates/microsoft.cognitiveservices/accounts?pivots=deployment-language-bicep#sku",
},
"CAF": {
Id: "cog-006",
Category: scanners.RulesCategoryOperationalExcellence,
Subcategory: scanners.RulesSubcategoryOperationalExcellenceCAF,
Description: "Cognitive Service Account Name should comply with naming conventions",
Severity: scanners.SeverityLow,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
c := target.(*armcognitiveservices.Account)
caf := strings.HasPrefix(*c.Name, "cog")
return !caf, ""
},
Url: "https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations",
},
"cog-007": {
Id: "cog-007",
Category: scanners.RulesCategoryOperationalExcellence,
Subcategory: scanners.RulesSubcategoryOperationalExcellenceTags,
Description: "Cognitive Service Account should have tags",
Severity: scanners.SeverityLow,
Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) {
c := target.(*armcognitiveservices.Account)
return c.Tags == nil || len(c.Tags) == 0, ""
},
Url: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json",
},
}
}
125 changes: 125 additions & 0 deletions internal/scanners/cog/rules_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package cog

import (
"reflect"
"testing"

"github.com/Azure/azqr/internal/scanners"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices"
"github.com/Azure/go-autorest/autorest/to"
)

func TestCognitiveScanner_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: "CognitiveScanner DiagnosticSettings",
fields: fields{
rule: "DiagnosticSettings",
target: &armcognitiveservices.Account{
ID: to.StringPtr("test"),
},
scanContext: &scanners.ScanContext{
DiagnosticsSettings: map[string]bool{
"test": true,
},
},
},
want: want{
broken: false,
result: "",
},
},
{
name: "CognitiveScanner SLA 99.99%",
fields: fields{
rule: "SLA",
target: &armcognitiveservices.Account{},
scanContext: &scanners.ScanContext{},
},
want: want{
broken: false,
result: "99.9%",
},
},
{
name: "CognitiveScanner Private Endpoint",
fields: fields{
rule: "Private",
target: &armcognitiveservices.Account{
Properties: &armcognitiveservices.AccountProperties{
PrivateEndpointConnections: []*armcognitiveservices.PrivateEndpointConnection{
{
ID: to.StringPtr("test"),
},
},
},
},
scanContext: &scanners.ScanContext{},
},
want: want{
broken: false,
result: "",
},
},
{
name: "CognitiveScanner SKU",
fields: fields{
rule: "SKU",
target: &armcognitiveservices.Account{
SKU: &armcognitiveservices.SKU{
Name: to.StringPtr("test"),
},
},
scanContext: &scanners.ScanContext{},
},
want: want{
broken: false,
result: "test",
},
},
{
name: "CognitiveScanner CAF",
fields: fields{
rule: "CAF",
target: &armcognitiveservices.Account{
Name: to.StringPtr("cog-test"),
},
scanContext: &scanners.ScanContext{},
},
want: want{
broken: false,
result: "",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &CognitiveScanner{}
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("CognitiveScanner Rule.Eval() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit 32e1e9f

Please sign in to comment.