diff --git a/README.md b/README.md index 0fe41b09..f7623402 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,9 @@ To learn more about the recommendations used by **Azure Quick Review (azqr)**, y * Azure SignalR Service * Azure SQL Database * Azure Storage Account +* Azure Synapse Analytics Workspace +* Azure Synapse Spark Pool +* Azure Synapse Dedicated SQL Pool * Azure Traffic Manager * Azure Virtual Machine * Azure Virtual Network diff --git a/cmd/azqr/synw.go b/cmd/azqr/synw.go new file mode 100644 index 00000000..cdb4d625 --- /dev/null +++ b/cmd/azqr/synw.go @@ -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/synw" + "github.com/spf13/cobra" +) + +func init() { + scanCmd.AddCommand(synwCmd) +} + +var synwCmd = &cobra.Command{ + Use: "synw", + Short: "Scan Azure Synapse Workspace", + Long: "Scan Azure Synapse Workspace", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + serviceScanners := []scanners.IAzureScanner{ + &synw.SynapseWorkspaceScanner{}, + } + + scan(cmd, serviceScanners) + }, +} diff --git a/docs/content/en/docs/Overview/_index.md b/docs/content/en/docs/Overview/_index.md index 53d6387f..6b221a50 100644 --- a/docs/content/en/docs/Overview/_index.md +++ b/docs/content/en/docs/Overview/_index.md @@ -91,6 +91,9 @@ To learn more about the recommendations used by **Azure Quick Review (azqr)**, y * Azure SignalR Service * Azure SQL Database * Azure Storage Account +* Azure Synapse Analytics Workspace +* Azure Synapse Spark Pool +* Azure Synapse Dedicated SQL Pool * Azure Traffic Manager * Azure Virtual Machine * Azure Virtual Network diff --git a/docs/content/en/docs/Recommendations/_index.md b/docs/content/en/docs/Recommendations/_index.md index 079f2c63..42f58e5d 100644 --- a/docs/content/en/docs/Recommendations/_index.md +++ b/docs/content/en/docs/Recommendations/_index.md @@ -316,3 +316,16 @@ Azure Quick Review checks the following recommendations for Azure resources. The 306 | High Availability | High | Web Pub Sub SKU | [Learn](https://azure.microsoft.com/en-us/pricing/details/web-pubsub/) 307 | Governance | Low | Web Pub Sub Name should comply with naming conventions | [Learn](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations) 308 | Governance | Low | Web Pub Sub should have tags | [Learn](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json) +309 | Monitoring and Alerting | Low | Azure Synapse Workspace should have diagnostic settings enabled | [Learn](https://learn.microsoft.com/en-us/azure/synapse-analytics/monitor-synapse-analytics) +310 | Security | High | Azure Synapse Workspace should have private endpoints enabled | [Learn](https://learn.microsoft.com/en-us/azure/synapse-analytics/security/synapse-workspace-managed-private-endpoints) +311 | High Availability | High | Azure Synapse Workspace SLA | [Learn](https://www.microsoft.com/licensing/docs/view/Service-Level-Agreements-SLA-for-Online-Services) +312 | Governance | Low | Azure Synapse Workspace Name should comply with naming conventions | [Learn](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations) +313 | Governance | Low | Azure Synapse Workspace should have tags | [Learn](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json) +312 | Security | High | Azure Synapse Workspace should establish network segmentation boundaries | [Learn](https://learn.microsoft.com/en-us/security/benchmark/azure/baselines/azure-synapse-analytics-security-baseline?toc=%2Fazure%2Fsynapse-analytics%2Ftoc.json) +313 | Security | High | Azure Synapse Workspace should disable public network access | [Learn](https://learn.microsoft.com/en-us/security/benchmark/azure/baselines/azure-synapse-analytics-security-baseline?toc=%2Fazure%2Fsynapse-analytics%2Ftoc.json) +314 | High Availability | High | Azure Synapse Spark Pool SLA | [Learn](https://www.microsoft.com/licensing/docs/view/Service-Level-Agreements-SLA-for-Online-Services) +315 | Governance | Low | Azure Synapse Spark Pool Name should comply with naming conventions | [Learn](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations) +316 | Governance | Low | Azure Synapse Spark Pool should have tags | [Learn](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json) +317 | High Availability | High | Azure Synapse Dedicated SQL Pool SLA | [Learn](https://www.microsoft.com/licensing/docs/view/Service-Level-Agreements-SLA-for-Online-Services) +318 | Governance | Low | Azure Synapse Dedicated SQL Pool Name should comply with naming conventions | [Learn](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations) +319 | Governance | Low | Azure Synapse Dedicated SQL Pool should have tags | [Learn](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json) \ No newline at end of file diff --git a/go.mod b/go.mod index 9b039477..ccf11c73 100644 --- a/go.mod +++ b/go.mod @@ -42,6 +42,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription v1.2.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/synapse/armsynapse v0.8.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/trafficmanager/armtrafficmanager v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/webpubsub/armwebpubsub v1.2.0 github.com/rs/zerolog v1.31.0 diff --git a/go.sum b/go.sum index fe601e6f..c1144389 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0/go.mod h1:T5RfihdXtBDxt1Ch2wobif3TvzTdumDy29kahv6AV9A= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription v1.2.0 h1:UrGzkHueDwAWDdjQxC+QaXHd4tVCkISYE9j7fSSXF8k= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription v1.2.0/go.mod h1:qskvSQeW+cxEE2bcKYyKimB1/KiQ9xpJ99bcHY0BX6c= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/synapse/armsynapse v0.8.0 h1:IKCilT2DdxjeCXhiCIZb5hywpA1KDGKwpdA1WL20wT0= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/synapse/armsynapse v0.8.0/go.mod h1:IzuvA34YNVnlifc1+KhCouAKEf1VYzV439FOpyfTHzA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/trafficmanager/armtrafficmanager v1.3.0 h1:e3kTG23M5ps+DjvPolK4dcgohDY8sHsXU7zrdHj1WzY= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/trafficmanager/armtrafficmanager v1.3.0/go.mod h1:Os5dq8Cvvz97rJauZhZJAfKHN+OEvF/0nVmHzF4aVys= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/webpubsub/armwebpubsub v1.2.0 h1:U+zDy6lU9scW8b58JpcQAlI+lsitiVSjz/RzBqbS5gM= diff --git a/internal/scan.go b/internal/scan.go index 941beefb..16692299 100644 --- a/internal/scan.go +++ b/internal/scan.go @@ -56,6 +56,7 @@ import ( "github.com/Azure/azqr/internal/scanners/sigr" "github.com/Azure/azqr/internal/scanners/sql" "github.com/Azure/azqr/internal/scanners/st" + "github.com/Azure/azqr/internal/scanners/synw" "github.com/Azure/azqr/internal/scanners/traf" "github.com/Azure/azqr/internal/scanners/vm" "github.com/Azure/azqr/internal/scanners/vnet" @@ -471,6 +472,7 @@ func GetScanners() []scanners.IAzureScanner { &sb.ServiceBusScanner{}, &sigr.SignalRScanner{}, &sql.SQLScanner{}, + &synw.SynapseWorkspaceScanner{}, &traf.TrafficManagerScanner{}, &st.StorageScanner{}, &vm.VirtualMachineScanner{}, diff --git a/internal/scanners/synw/rules.go b/internal/scanners/synw/rules.go new file mode 100644 index 00000000..835765b0 --- /dev/null +++ b/internal/scanners/synw/rules.go @@ -0,0 +1,182 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package synw + +import ( + "strings" + + "github.com/Azure/azqr/internal/scanners" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/synapse/armsynapse" +) + +// GetRules - Returns the rules for the SynapseWorkspaceScanner +func (a *SynapseWorkspaceScanner) GetRules() map[string]scanners.AzureRule { + result := a.getWorkspaceRules() + for k, v := range a.getSparkPoolRules() { + result[k] = v + } + for k, v := range a.getSqlPoolRules() { + result[k] = v + } + return result +} +func (a *SynapseWorkspaceScanner) getWorkspaceRules() map[string]scanners.AzureRule { + return map[string]scanners.AzureRule{ + "synw-001": { + Id: "synw-001", + Category: scanners.RulesCategoryMonitoringAndAlerting, + Recommendation: "Azure Synapse Workspace should have diagnostic settings enabled", + Impact: scanners.ImpactLow, + Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) { + service := target.(*armsynapse.Workspace) + _, ok := scanContext.DiagnosticsSettings[strings.ToLower(*service.ID)] + return !ok, "" + }, + Url: "https://learn.microsoft.com/en-us/azure/data-factory/monitor-configure-diagnostics", + }, + "synw-002": { + Id: "synw-002", + Category: scanners.RulesCategorySecurity, + Recommendation: "Azure Synapse Workspace should have private endpoints enabled", + Impact: scanners.ImpactHigh, + Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) { + i := target.(*armsynapse.Workspace) + pe := len(i.Properties.PrivateEndpointConnections) > 0 + return !pe, "" + }, + Url: "https://learn.microsoft.com/en-us/azure/synapse-analytics/security/synapse-workspace-managed-private-endpoints", + }, + "synw-003": { + Id: "synw-003", + Category: scanners.RulesCategoryHighAvailability, + Recommendation: "Azure Synapse Workspace SLA", + Impact: scanners.ImpactHigh, + 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", + }, + "synw-004": { + Id: "synw-004", + Category: scanners.RulesCategoryGovernance, + Recommendation: "Azure Synapse Workspace Name should comply with naming conventions", + Impact: scanners.ImpactLow, + Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) { + c := target.(*armsynapse.Workspace) + caf := strings.HasPrefix(*c.Name, "synw") + return !caf, "" + }, + Url: "https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations", + }, + "synw-005": { + Id: "synw-005", + Category: scanners.RulesCategoryGovernance, + Recommendation: "Azure Synapse Workspace should have tags", + Impact: scanners.ImpactLow, + Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) { + c := target.(*armsynapse.Workspace) + return len(c.Tags) == 0, "" + }, + Url: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json", + }, + "synw-006": { + Id: "synw-006", + Category: scanners.RulesCategorySecurity, + Recommendation: "Azure Synapse Workspace should establish network segmentation boundaries", + Impact: scanners.ImpactHigh, + Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) { + c := target.(*armsynapse.Workspace) + return *c.Properties.ManagedVirtualNetwork != "default", "" + }, + Url: "https://learn.microsoft.com/en-us/security/benchmark/azure/baselines/azure-synapse-analytics-security-baseline?toc=%2Fazure%2Fsynapse-analytics%2Ftoc.json", + }, + "synw-007": { + Id: "synw-007", + Category: scanners.RulesCategorySecurity, + Recommendation: "Azure Synapse Workspace should disable public network access", + Impact: scanners.ImpactHigh, + Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) { + c := target.(*armsynapse.Workspace) + return string(*c.Properties.PublicNetworkAccess) == "Enabled", "" + }, + Url: "https://learn.microsoft.com/en-us/security/benchmark/azure/baselines/azure-synapse-analytics-security-baseline?toc=%2Fazure%2Fsynapse-analytics%2Ftoc.json", + }, + } +} + +func (a *SynapseWorkspaceScanner) getSparkPoolRules() map[string]scanners.AzureRule { + return map[string]scanners.AzureRule{ + "synsp-001": { + Id: "synsp-001", + Category: scanners.RulesCategoryGovernance, + Recommendation: "Azure Synapse Spark Pool Name should comply with naming conventions", + Impact: scanners.ImpactLow, + Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) { + c := target.(*armsynapse.BigDataPoolResourceInfo) + caf := strings.HasPrefix(*c.Name, "synsp") + return !caf, "" + }, + Url: "https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations", + }, + "synsp-002": { + Id: "synsp-002", + Category: scanners.RulesCategoryHighAvailability, + Recommendation: "Azure Synapse Spark Pool SLA", + Impact: scanners.ImpactHigh, + 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", + }, + "synsp-003": { + Id: "synsp-003", + Category: scanners.RulesCategoryGovernance, + Recommendation: "Azure Synapse Spark Pool should have tags", + Impact: scanners.ImpactLow, + Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) { + c := target.(*armsynapse.BigDataPoolResourceInfo) + return len(c.Tags) == 0, "" + }, + Url: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json", + }, + } +} + +func (a *SynapseWorkspaceScanner) getSqlPoolRules() map[string]scanners.AzureRule { + return map[string]scanners.AzureRule{ + "syndp-001": { + Id: "syndp-001", + Category: scanners.RulesCategoryGovernance, + Recommendation: "Azure Synapse Dedicated SQL Pool Name should comply with naming conventions", + Impact: scanners.ImpactLow, + Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) { + c := target.(*armsynapse.SQLPool) + caf := strings.HasPrefix(*c.Name, "syndp") + return !caf, "" + }, + Url: "https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations", + }, + "syndp-002": { + Id: "syndp-002", + Category: scanners.RulesCategoryHighAvailability, + Recommendation: "Azure Synapse Dedicated SQL Pool SLA", + Impact: scanners.ImpactHigh, + 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", + }, + "syndp-003": { + Id: "syndp-003", + Category: scanners.RulesCategoryGovernance, + Recommendation: "Azure Synapse Dedicated SQL Pool should have tags", + Impact: scanners.ImpactLow, + Eval: func(target interface{}, scanContext *scanners.ScanContext) (bool, string) { + c := target.(*armsynapse.SQLPool) + return len(c.Tags) == 0, "" + }, + Url: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources?tabs=json", + }, + } +} diff --git a/internal/scanners/synw/rules_test.go b/internal/scanners/synw/rules_test.go new file mode 100644 index 00000000..a7b38e28 --- /dev/null +++ b/internal/scanners/synw/rules_test.go @@ -0,0 +1,259 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package synw + +import ( + "reflect" + "testing" + + "github.com/Azure/azqr/internal/scanners" + "github.com/Azure/azqr/internal/to" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/synapse/armsynapse" +) + +func TestSynapseWorkspaceScanner_WorkspaceRules(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: "SynapseWorkspaceScanner DiagnosticSettings", + fields: fields{ + rule: "synw-001", + target: &armsynapse.Workspace{ + ID: to.Ptr("test"), + }, + scanContext: &scanners.ScanContext{ + DiagnosticsSettings: map[string]bool{ + "test": true, + }, + }, + }, + want: want{ + broken: false, + result: "", + }, + }, + { + name: "SynapseWorkspaceScanner Private Endpoint", + fields: fields{ + rule: "synw-002", + target: &armsynapse.Workspace{ + Properties: &armsynapse.WorkspaceProperties{ + PrivateEndpointConnections: []*armsynapse.PrivateEndpointConnection{ + { + ID: to.Ptr("test"), + }, + }, + }, + }, + scanContext: &scanners.ScanContext{}, + }, + want: want{ + broken: false, + result: "", + }, + }, + { + name: "SynapseWorkspaceScanner SLA", + fields: fields{ + rule: "synw-003", + target: &armsynapse.Workspace{}, + scanContext: &scanners.ScanContext{}, + }, + want: want{ + broken: false, + result: "99.9%", + }, + }, + { + name: "SynapseWorkspaceScanner CAF", + fields: fields{ + rule: "synw-004", + target: &armsynapse.Workspace{ + Name: to.Ptr("synw-test"), + }, + scanContext: &scanners.ScanContext{}, + }, + want: want{ + broken: false, + result: "", + }, + }, + { + name: "SynapseWorkspaceScanner Security Profile", + fields: fields{ + rule: "synw-006", + target: &armsynapse.Workspace{ + Name: to.Ptr("synw-test"), + Properties: &armsynapse.WorkspaceProperties{ + ManagedVirtualNetwork: to.Ptr("default"), + }, + }, + scanContext: &scanners.ScanContext{}, + }, + want: want{ + broken: false, + result: "", + }, + }, + { + name: "SynapseWorkspaceScanner Security Profile", + fields: fields{ + rule: "synw-007", + target: &armsynapse.Workspace{ + Name: to.Ptr("synw-test"), + Properties: &armsynapse.WorkspaceProperties{ + PublicNetworkAccess: &armsynapse.PossibleWorkspacePublicNetworkAccessValues()[0], + }, + }, + scanContext: &scanners.ScanContext{}, + }, + want: want{ + broken: false, + result: "", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &SynapseWorkspaceScanner{} + rules := s.getWorkspaceRules() + 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("SynapseWorkspaceScanner Rule.Eval() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSynapseWorkspaceScanner_SparkPoolRules(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: "SynapseSparkPoolScanner SLA", + fields: fields{ + rule: "synsp-002", + target: &armsynapse.Workspace{}, + scanContext: &scanners.ScanContext{}, + }, + want: want{ + broken: false, + result: "99.9%", + }, + }, + { + name: "SynapseSparkPoolScanner CAF", + fields: fields{ + rule: "synsp-001", + target: &armsynapse.BigDataPoolResourceInfo{ + Name: to.Ptr("synsp-test"), + }, + scanContext: &scanners.ScanContext{}, + }, + want: want{ + broken: false, + result: "", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &SynapseWorkspaceScanner{} + rules := s.getSparkPoolRules() + 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("SynapseSparkPoolScanner Rule.Eval() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSynapseWorkspaceScanner_SqlPoolRules(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: "SynapseSqlPoolScanner SLA", + fields: fields{ + rule: "syndp-002", + target: &armsynapse.Workspace{}, + scanContext: &scanners.ScanContext{}, + }, + want: want{ + broken: false, + result: "99.9%", + }, + }, + { + name: "SynapseSqlPoolScanner CAF", + fields: fields{ + rule: "syndp-001", + target: &armsynapse.SQLPool{ + Name: to.Ptr("syndp-test"), + }, + scanContext: &scanners.ScanContext{}, + }, + want: want{ + broken: false, + result: "", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &SynapseWorkspaceScanner{} + rules := s.getSqlPoolRules() + 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("SynapseSqlPoolScanner Rule.Eval() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/scanners/synw/synw.go b/internal/scanners/synw/synw.go new file mode 100644 index 00000000..71b0f5f5 --- /dev/null +++ b/internal/scanners/synw/synw.go @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package synw + +import ( + "github.com/Azure/azqr/internal/scanners" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/synapse/armsynapse" +) + +// SynapseWorkspaceScanner - Scanner for Synapse Analytics Workspace +type SynapseWorkspaceScanner struct { + config *scanners.ScannerConfig + workspacesClient *armsynapse.WorkspacesClient + sparkPoolClient *armsynapse.BigDataPoolsClient + sqlPoolClient *armsynapse.SQLPoolsClient +} + +// Init - Initializes the SynapseWorkspaceScanner Scanner +func (a *SynapseWorkspaceScanner) Init(config *scanners.ScannerConfig) error { + a.config = config + var err error + a.workspacesClient, err = armsynapse.NewWorkspacesClient(config.SubscriptionID, a.config.Cred, a.config.ClientOptions) + if err != nil { + return err + } + a.sparkPoolClient, err = armsynapse.NewBigDataPoolsClient(config.SubscriptionID, config.Cred, config.ClientOptions) + if err != nil { + return err + } + a.sqlPoolClient, err = armsynapse.NewSQLPoolsClient(config.SubscriptionID, config.Cred, config.ClientOptions) + if err != nil { + return err + } + return nil +} + +// Scan - Scans all Synapse Workspaces in a Resource Group +func (a *SynapseWorkspaceScanner) Scan(resourceGroupName string, scanContext *scanners.ScanContext) ([]scanners.AzureServiceResult, error) { + scanners.LogResourceGroupScan(a.config.SubscriptionID, resourceGroupName, "Synapse Analytics Workspace") + + workspaces, err := a.listWorkspaces(resourceGroupName) + if err != nil { + + return nil, err + } + engine := scanners.RuleEngine{} + rules := a.getWorkspaceRules() + sqlPoolRules := a.getSqlPoolRules() + sparkPoolRules := a.getSparkPoolRules() + results := []scanners.AzureServiceResult{} + + for _, w := range workspaces { + rr := engine.EvaluateRules(rules, w, scanContext) + + results = append(results, scanners.AzureServiceResult{ + SubscriptionID: a.config.SubscriptionID, + ResourceGroup: resourceGroupName, + Location: *w.Location, + Type: *w.Type, + ServiceName: *w.Name, + Rules: rr, + }) + + sqlPools, err := a.listSqlPools(resourceGroupName, *w.Name) + if err != nil { + return nil, err + } + + for _, s := range sqlPools { + var result scanners.AzureServiceResult + rr := engine.EvaluateRules(sqlPoolRules, s, scanContext) + + result = scanners.AzureServiceResult{ + SubscriptionID: a.config.SubscriptionID, + ResourceGroup: resourceGroupName, + ServiceName: *s.Name, + Type: *s.Type, + Location: *w.Location, + Rules: rr, + } + results = append(results, result) + } + + sparkPools, err := a.listSparkPools(resourceGroupName, *w.Name) + if err != nil { + return nil, err + } + + for _, s := range sparkPools { + var result scanners.AzureServiceResult + rr := engine.EvaluateRules(sparkPoolRules, s, scanContext) + + result = scanners.AzureServiceResult{ + SubscriptionID: a.config.SubscriptionID, + ResourceGroup: resourceGroupName, + ServiceName: *s.Name, + Type: *s.Type, + Location: *w.Location, + Rules: rr, + } + results = append(results, result) + } + } + return results, nil +} + +func (a *SynapseWorkspaceScanner) listWorkspaces(resourceGroupName string) ([]*armsynapse.Workspace, error) { + pager := a.workspacesClient.NewListByResourceGroupPager(resourceGroupName, nil) + + workspaces := make([]*armsynapse.Workspace, 0) + for pager.More() { + resp, err := pager.NextPage(a.config.Ctx) + if err != nil { + return nil, err + } + workspaces = append(workspaces, resp.Value...) + } + return workspaces, nil +} + +func (a *SynapseWorkspaceScanner) listSqlPools(resourceGroupName string, workspace string) ([]*armsynapse.SQLPool, error) { + pager := a.sqlPoolClient.NewListByWorkspacePager(resourceGroupName, workspace, nil) + results := make([]*armsynapse.SQLPool, 0) + for pager.More() { + resp, err := pager.NextPage(a.config.Ctx) + if err != nil { + return nil, err + } + results = append(results, resp.Value...) + } + return results, nil +} + +func (a *SynapseWorkspaceScanner) listSparkPools(resourceGroupName string, workspace string) ([]*armsynapse.BigDataPoolResourceInfo, error) { + pager := a.sparkPoolClient.NewListByWorkspacePager(resourceGroupName, workspace, nil) + results := make([]*armsynapse.BigDataPoolResourceInfo, 0) + for pager.More() { + resp, err := pager.NextPage(a.config.Ctx) + if err != nil { + return nil, err + } + results = append(results, resp.Value...) + } + return results, nil +}