-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementing Cognitive Service Account Scanner #102
- Loading branch information
Showing
9 changed files
with
323 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
} |