diff --git a/avd_docs/azure/monitor/AVD-AZU-0031/docs.md b/avd_docs/azure/monitor/AVD-AZU-0031/docs.md index 2e4c39a7..5839c392 100644 --- a/avd_docs/azure/monitor/AVD-AZU-0031/docs.md +++ b/avd_docs/azure/monitor/AVD-AZU-0031/docs.md @@ -1,8 +1,9 @@ The average time to detect a breach is up to 210 days, to ensure that all the information required for an effective investigation is available, the retention period should allow for delayed starts to investigating. + ### Impact -Short life activity logs can lead to missing records when investigating a breach + {{ remediationActions }} diff --git a/avd_docs/azure/monitor/AVD-AZU-0032/docs.md b/avd_docs/azure/monitor/AVD-AZU-0032/docs.md index ec736a9b..47af42ff 100644 --- a/avd_docs/azure/monitor/AVD-AZU-0032/docs.md +++ b/avd_docs/azure/monitor/AVD-AZU-0032/docs.md @@ -1,8 +1,9 @@ Log profiles should capture all regions to ensure that all events are logged + ### Impact -Activity may be occurring in locations that aren't being monitored + {{ remediationActions }} diff --git a/avd_docs/azure/monitor/AVD-AZU-0033/docs.md b/avd_docs/azure/monitor/AVD-AZU-0033/docs.md index 16196826..38cee9ff 100644 --- a/avd_docs/azure/monitor/AVD-AZU-0033/docs.md +++ b/avd_docs/azure/monitor/AVD-AZU-0033/docs.md @@ -1,8 +1,9 @@ Log profiles should capture all categories to ensure that all events are logged + ### Impact -Log profile must capture all activity to be able to ensure that all relevant information possible is available for an investigation + {{ remediationActions }} diff --git a/avd_docs/azure/network/AVD-AZU-0049/docs.md b/avd_docs/azure/network/AVD-AZU-0049/docs.md index d5e28c3e..b758ec9c 100644 --- a/avd_docs/azure/network/AVD-AZU-0049/docs.md +++ b/avd_docs/azure/network/AVD-AZU-0049/docs.md @@ -1,11 +1,13 @@ -Flow logs are the source of truth for all network activity in your cloud environment. -To enable analysis in security event that was detected late, you need to have the logs available. - +Flow logs are the source of truth for all network activity in your cloud environment. + +To enable analysis in security event that was detected late, you need to have the logs available. + Setting an retention policy will help ensure as much information is available for review. + ### Impact -Not enabling retention or having short expiry on flow logs could lead to compromise being undetected limiting time for analysis + {{ remediationActions }} diff --git a/avd_docs/azure/securitycenter/AVD-AZU-0044/Terraform.md b/avd_docs/azure/securitycenter/AVD-AZU-0044/Terraform.md index ddd8c460..440ed302 100644 --- a/avd_docs/azure/securitycenter/AVD-AZU-0044/Terraform.md +++ b/avd_docs/azure/securitycenter/AVD-AZU-0044/Terraform.md @@ -1,5 +1,5 @@ - Set alert notifications to be on +Set alert notifications to be on ```hcl resource "azurerm_security_center_contact" "good_example" { diff --git a/avd_docs/azure/securitycenter/AVD-AZU-0044/docs.md b/avd_docs/azure/securitycenter/AVD-AZU-0044/docs.md index b1a5bf93..bf1edd70 100644 --- a/avd_docs/azure/securitycenter/AVD-AZU-0044/docs.md +++ b/avd_docs/azure/securitycenter/AVD-AZU-0044/docs.md @@ -1,9 +1,11 @@ -It is recommended that at least one valid contact is configured for the security center. +It is recommended that at least one valid contact is configured for the security center. + Microsoft will notify the security contact directly in the event of a security incident using email and require alerting to be turned on. + ### Impact -The ability to react to high severity notifications could be delayed + {{ remediationActions }} diff --git a/avd_docs/azure/securitycenter/AVD-AZU-0045/docs.md b/avd_docs/azure/securitycenter/AVD-AZU-0045/docs.md index 62c1ab4b..2f3a3e44 100644 --- a/avd_docs/azure/securitycenter/AVD-AZU-0045/docs.md +++ b/avd_docs/azure/securitycenter/AVD-AZU-0045/docs.md @@ -1,10 +1,11 @@ To benefit from Azure Defender you should use the Standard subscription tier. - - Enabling Azure Defender extends the capabilities of the free mode to workloads running in private and other public clouds, providing unified security management and threat protection across your hybrid cloud workloads. + +Enabling Azure Defender extends the capabilities of the free mode to workloads running in private and other public clouds, providing unified security management and threat protection across your hybrid cloud workloads. + ### Impact -Using free subscription does not enable Azure Defender for the resource type + {{ remediationActions }} diff --git a/avd_docs/azure/securitycenter/AVD-AZU-0046/docs.md b/avd_docs/azure/securitycenter/AVD-AZU-0046/docs.md index 12095552..4a9ca988 100644 --- a/avd_docs/azure/securitycenter/AVD-AZU-0046/docs.md +++ b/avd_docs/azure/securitycenter/AVD-AZU-0046/docs.md @@ -1,9 +1,11 @@ -It is recommended that at least one valid contact is configured for the security center. +It is recommended that at least one valid contact is configured for the security center. + Microsoft will notify the security contact directly in the event of a security incident and will look to use a telephone number in cases where a prompt response is required. + ### Impact -Without a telephone number set, Azure support can't contact + {{ remediationActions }} diff --git a/avd_docs/azure/synapse/AVD-AZU-0034/docs.md b/avd_docs/azure/synapse/AVD-AZU-0034/docs.md index 9e31b5d5..0fdf511e 100644 --- a/avd_docs/azure/synapse/AVD-AZU-0034/docs.md +++ b/avd_docs/azure/synapse/AVD-AZU-0034/docs.md @@ -2,10 +2,12 @@ Synapse Workspace does not have managed virtual network enabled by default. When you create your Azure Synapse workspace, you can choose to associate it to a Microsoft Azure Virtual Network. The Virtual Network associated with your workspace is managed by Azure Synapse. This Virtual Network is called a Managed workspace Virtual Network. + Managed private endpoints are private endpoints created in a Managed Virtual Network associated with your Azure Synapse workspace. Managed private endpoints establish a private link to Azure resources. You can only use private links in a workspace that has a Managed workspace Virtual Network. + ### Impact -Your Synapse workspace is not using the private endpoints + {{ remediationActions }} diff --git a/checks/cloud/azure/monitor/activity_log_retention_set.go b/checks/cloud/azure/monitor/activity_log_retention_set.go index 94f9a613..8df1de96 100755 --- a/checks/cloud/azure/monitor/activity_log_retention_set.go +++ b/checks/cloud/azure/monitor/activity_log_retention_set.go @@ -27,7 +27,8 @@ var CheckActivityLogRetentionSet = rules.Register( Links: terraformActivityLogRetentionSetLinks, RemediationMarkdown: terraformActivityLogRetentionSetRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, profile := range s.Azure.Monitor.LogProfiles { diff --git a/checks/cloud/azure/monitor/activity_log_retention_set.rego b/checks/cloud/azure/monitor/activity_log_retention_set.rego new file mode 100644 index 00000000..6b45478e --- /dev/null +++ b/checks/cloud/azure/monitor/activity_log_retention_set.rego @@ -0,0 +1,54 @@ +# METADATA +# title: Ensure the activity retention log is set to at least a year +# description: | +# The average time to detect a breach is up to 210 days, to ensure that all the information required for an effective investigation is available, the retention period should allow for delayed starts to investigating. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.microsoft.com/en-us/azure/azure-monitor/essentials/platform-logs-overview +# custom: +# id: AVD-AZU-0031 +# avd_id: AVD-AZU-0031 +# provider: azure +# service: monitor +# severity: MEDIUM +# short_code: activity-log-retention-set +# recommended_action: Set a retention period that will allow for delayed investigation +# input: +# selector: +# - type: cloud +# subtypes: +# - service: monitor +# provider: azure +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/monitor_log_profile#retention_policy +# good_examples: checks/cloud/azure/monitor/activity_log_retention_set.tf.go +# bad_examples: checks/cloud/azure/monitor/activity_log_retention_set.tf.go +package builtin.azure.monitor.azure0031 + +import rego.v1 + +deny contains res if { + some profile in input.azure.monitor.logprofiles + isManaged(profile) + not profile.retentionpolicy.enabled.value + res := result.new( + "Profile does not enable the log retention policy.", + object.get(profile, ["retentionpolicy", "enabled"], profile), + ) +} + +deny contains res if { + some profile in input.azure.monitor.logprofiles + isManaged(profile) + profile.retentionpolicy.enabled.value + not is_recommended_retention_policy(profile) + res := result.new( + "Profile has a log retention policy of less than 1 year.", + object.get(profile, ["retentionpolicy", "days"], profile), + ) +} + +is_recommended_retention_policy(profile) := profile.retentionpolicy.days.value >= 365 diff --git a/checks/cloud/azure/monitor/activity_log_retention_set_test.go b/checks/cloud/azure/monitor/activity_log_retention_set_test.go deleted file mode 100644 index a9499a9b..00000000 --- a/checks/cloud/azure/monitor/activity_log_retention_set_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package monitor - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/monitor" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckActivityLogRetentionSet(t *testing.T) { - tests := []struct { - name string - input monitor.Monitor - expected bool - }{ - { - name: "Log retention policy disabled", - input: monitor.Monitor{ - LogProfiles: []monitor.LogProfile{ - { - Metadata: trivyTypes.NewTestMetadata(), - RetentionPolicy: monitor.RetentionPolicy{ - Metadata: trivyTypes.NewTestMetadata(), - Enabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - Days: trivyTypes.Int(365, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "Log retention policy enabled for 90 days", - input: monitor.Monitor{ - LogProfiles: []monitor.LogProfile{ - { - Metadata: trivyTypes.NewTestMetadata(), - RetentionPolicy: monitor.RetentionPolicy{ - Metadata: trivyTypes.NewTestMetadata(), - Enabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - Days: trivyTypes.Int(90, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "Log retention policy enabled for 365 days", - input: monitor.Monitor{ - LogProfiles: []monitor.LogProfile{ - { - Metadata: trivyTypes.NewTestMetadata(), - RetentionPolicy: monitor.RetentionPolicy{ - Metadata: trivyTypes.NewTestMetadata(), - Enabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - Days: trivyTypes.Int(365, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Azure.Monitor = test.input - results := CheckActivityLogRetentionSet.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckActivityLogRetentionSet.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/azure/monitor/activity_log_retention_set_test.rego b/checks/cloud/azure/monitor/activity_log_retention_set_test.rego new file mode 100644 index 00000000..076efa59 --- /dev/null +++ b/checks/cloud/azure/monitor/activity_log_retention_set_test.rego @@ -0,0 +1,33 @@ +package builtin.azure.monitor.azure0031_test + +import rego.v1 + +import data.builtin.azure.monitor.azure0031 as check +import data.lib.test + +test_deny_retention_policy_disabled if { + inp := {"azure": {"monitor": {"logprofiles": [{"retentionpolicy": {"enabled": {"value": false}}}]}}} + + res := check.deny with input as inp + count(res) == 1 +} + +test_deny_retention_policy_enabled_but_days_lt_365 if { + inp := {"azure": {"monitor": {"logprofiles": [{"retentionpolicy": { + "enabled": {"value": true}, + "days": {"value": 30}, + }}]}}} + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_retention_policy_enabled_and_days_gt_365 if { + inp := {"azure": {"monitor": {"logprofiles": [{"retentionpolicy": { + "enabled": {"value": true}, + "days": {"value": 365}, + }}]}}} + + res := check.deny with input as inp + count(res) == 0 +} diff --git a/checks/cloud/azure/monitor/capture_all_activities.go b/checks/cloud/azure/monitor/capture_all_activities.go index 9ca8c7a1..28eb4616 100755 --- a/checks/cloud/azure/monitor/capture_all_activities.go +++ b/checks/cloud/azure/monitor/capture_all_activities.go @@ -35,7 +35,8 @@ var CheckCaptureAllActivities = rules.Register( Links: terraformCaptureAllActivitiesLinks, RemediationMarkdown: terraformCaptureAllActivitiesRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { required := []string{ diff --git a/checks/cloud/azure/monitor/capture_all_activities.rego b/checks/cloud/azure/monitor/capture_all_activities.rego new file mode 100644 index 00000000..823aa7d6 --- /dev/null +++ b/checks/cloud/azure/monitor/capture_all_activities.rego @@ -0,0 +1,55 @@ +# METADATA +# title: Ensure log profile captures all activities +# description: | +# Log profiles should capture all categories to ensure that all events are logged +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.microsoft.com/en-us/azure/azure-monitor/essentials/activity-log +# - https://docs.microsoft.com/en-us/cli/azure/monitor/log-profiles?view=azure-cli-latest#az_monitor_log_profiles_create-required-parameters +# custom: +# id: AVD-AZU-0033 +# avd_id: AVD-AZU-0033 +# provider: azure +# service: monitor +# severity: MEDIUM +# short_code: capture-all-activities +# recommended_action: Configure log profile to capture all activities +# input: +# selector: +# - type: cloud +# subtypes: +# - service: monitor +# provider: azure +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/monitor_log_profile#categories +# good_examples: checks/cloud/azure/monitor/capture_all_activities.tf.go +# bad_examples: checks/cloud/azure/monitor/capture_all_activities.tf.go +package builtin.azure.monitor.azure0033 + +import rego.v1 + +required_categories := {"Action", "Write", "Delete"} + +deny contains res if { + some profile in input.azure.monitor.logprofiles + isManaged(profile) + missing := missing_required_categories(profile) + count(missing) > 0 + res := result.new( + sprintf("Log profile does not require categories: %v", [missing]), + profile, + ) +} + +missing_required_categories(profile) := missing if { + categories := { + val | + some category in profile.categories + val := category.value + } + + missing := required_categories - categories +} else := {} diff --git a/checks/cloud/azure/monitor/capture_all_activities_test.go b/checks/cloud/azure/monitor/capture_all_activities_test.go deleted file mode 100644 index bc90c2b6..00000000 --- a/checks/cloud/azure/monitor/capture_all_activities_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package monitor - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/monitor" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckCaptureAllActivities(t *testing.T) { - tests := []struct { - name string - input monitor.Monitor - expected bool - }{ - { - name: "Log profile captures only write activities", - input: monitor.Monitor{ - LogProfiles: []monitor.LogProfile{ - { - Metadata: trivyTypes.NewTestMetadata(), - Categories: []trivyTypes.StringValue{ - trivyTypes.String("Write", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "Log profile captures action, write, delete activities", - input: monitor.Monitor{ - LogProfiles: []monitor.LogProfile{ - { - Metadata: trivyTypes.NewTestMetadata(), - Categories: []trivyTypes.StringValue{ - trivyTypes.String("Action", trivyTypes.NewTestMetadata()), - trivyTypes.String("Write", trivyTypes.NewTestMetadata()), - trivyTypes.String("Delete", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Azure.Monitor = test.input - results := CheckCaptureAllActivities.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckCaptureAllActivities.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/azure/monitor/capture_all_activities_test.rego b/checks/cloud/azure/monitor/capture_all_activities_test.rego new file mode 100644 index 00000000..0046b024 --- /dev/null +++ b/checks/cloud/azure/monitor/capture_all_activities_test.rego @@ -0,0 +1,31 @@ +package builtin.azure.monitor.azure0033_test + +import rego.v1 + +import data.builtin.azure.monitor.azure0033 as check +import data.lib.test + +test_deny_log_profile_captures_only_write_activities if { + inp := {"azure": {"monitor": {"logprofiles": [{"categories": [{"value": "Write"}]}]}}} + + res := check.deny with input as inp + count(res) == 1 +} + +test_deny_log_profile_without_categories if { + inp := {"azure": {"monitor": {"logprofiles": [{}]}}} + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_log_profile_with_all_required_categories if { + inp := {"azure": {"monitor": {"logprofiles": [{"categories": [ + {"value": "Write"}, + {"value": "Delete"}, + {"value": "Action"}, + ]}]}}} + + res := check.deny with input as inp + count(res) == 0 +} diff --git a/checks/cloud/azure/monitor/capture_all_regions.go b/checks/cloud/azure/monitor/capture_all_regions.go index 3b05031e..6208c55c 100755 --- a/checks/cloud/azure/monitor/capture_all_regions.go +++ b/checks/cloud/azure/monitor/capture_all_regions.go @@ -35,7 +35,8 @@ var CheckCaptureAllRegions = rules.Register( Links: terraformCaptureAllRegionsLinks, RemediationMarkdown: terraformCaptureAllRegionsRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, profile := range s.Azure.Monitor.LogProfiles { diff --git a/checks/cloud/azure/monitor/capture_all_regions.rego b/checks/cloud/azure/monitor/capture_all_regions.rego new file mode 100644 index 00000000..0da1c598 --- /dev/null +++ b/checks/cloud/azure/monitor/capture_all_regions.rego @@ -0,0 +1,128 @@ +# METADATA +# title: Ensure activitys are captured for all locations +# description: | +# Log profiles should capture all regions to ensure that all events are logged +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.microsoft.com/en-us/cli/azure/monitor/log-profiles?view=azure-cli-latest#az_monitor_log_profiles_create-required-parameters +# custom: +# id: AVD-AZU-0032 +# avd_id: AVD-AZU-0032 +# provider: azure +# service: monitor +# severity: MEDIUM +# short_code: capture-all-regions +# recommended_action: Enable capture for all locations +# input: +# selector: +# - type: cloud +# subtypes: +# - service: monitor +# provider: azure +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/monitor_log_profile#locations +# good_examples: checks/cloud/azure/monitor/capture_all_regions.tf.go +# bad_examples: checks/cloud/azure/monitor/capture_all_regions.tf.go +package builtin.azure.monitor.azure0032 + +import rego.v1 + +deny contains res if { + some profile in input.azure.monitor.logprofiles + missing := missing_regions(profile) + count(missing) > 0 + det := details(missing) + res := result.new( + sprintf("Log profile does not log to all regions (%s).", [det]), + profile, + ) +} + +details(missing) := msg if { + count(missing) < 10 + msg := sprintf("missing: %v", [missing]) +} else := sprintf("%d regions are missing", [count(missing)]) + +missing_regions(profile) := missing if { + regions := { + val | + some region in profile.locations + val := region.value + } + + missing := required_regions - regions +} + +required_regions := { + "eastus", + "eastus2", + "southcentralus", + "westus2", + "westus3", + "australiaeast", + "southeastasia", + "northeurope", + "swedencentral", + "uksouth", + "westeurope", + "centralus", + "northcentralus", + "westus", + "southafricanorth", + "centralindia", + "eastasia", + "japaneast", + "jioindiawest", + "koreacentral", + "canadacentral", + "francecentral", + "germanywestcentral", + "norwayeast", + "switzerlandnorth", + "uaenorth", + "brazilsouth", + "centralusstage", + "eastusstage", + "eastus2stage", + "northcentralusstage", + "southcentralusstage", + "westusstage", + "westus2stage", + "asia", + "asiapacific", + "australia", + "brazil", + "canada", + "europe", + "global", + "india", + "japan", + "uk", + "unitedstates", + "eastasiastage", + "southeastasiastage", + "centraluseuap", + "eastus2euap", + "westcentralus", + "southafricawest", + "australiacentral", + "australiacentral2", + "australiasoutheast", + "japanwest", + "jioindiacentral", + "koreasouth", + "southindia", + "westindia", + "canadaeast", + "francesouth", + "germanynorth", + "norwaywest", + "swedensouth", + "switzerlandwest", + "ukwest", + "uaecentral", + "brazilsoutheast", +} diff --git a/checks/cloud/azure/monitor/capture_all_regions_test.go b/checks/cloud/azure/monitor/capture_all_regions_test.go deleted file mode 100644 index 5a419da7..00000000 --- a/checks/cloud/azure/monitor/capture_all_regions_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package monitor - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/monitor" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckCaptureAllRegions(t *testing.T) { - tests := []struct { - name string - input monitor.Monitor - expected bool - }{ - { - name: "Log profile captures only eastern US region", - input: monitor.Monitor{ - LogProfiles: []monitor.LogProfile{ - { - Metadata: trivyTypes.NewTestMetadata(), - Locations: []trivyTypes.StringValue{ - trivyTypes.String("eastus", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "Log profile captures all regions", - input: monitor.Monitor{ - LogProfiles: []monitor.LogProfile{ - { - Metadata: trivyTypes.NewTestMetadata(), - Locations: []trivyTypes.StringValue{ - trivyTypes.String("eastus", trivyTypes.NewTestMetadata()), - trivyTypes.String("eastus2", trivyTypes.NewTestMetadata()), - trivyTypes.String("southcentralus", trivyTypes.NewTestMetadata()), - trivyTypes.String("westus2", trivyTypes.NewTestMetadata()), - trivyTypes.String("westus3", trivyTypes.NewTestMetadata()), - trivyTypes.String("australiaeast", trivyTypes.NewTestMetadata()), - trivyTypes.String("southeastasia", trivyTypes.NewTestMetadata()), - trivyTypes.String("northeurope", trivyTypes.NewTestMetadata()), - trivyTypes.String("swedencentral", trivyTypes.NewTestMetadata()), - trivyTypes.String("uksouth", trivyTypes.NewTestMetadata()), - trivyTypes.String("westeurope", trivyTypes.NewTestMetadata()), - trivyTypes.String("centralus", trivyTypes.NewTestMetadata()), - trivyTypes.String("northcentralus", trivyTypes.NewTestMetadata()), - trivyTypes.String("westus", trivyTypes.NewTestMetadata()), - trivyTypes.String("southafricanorth", trivyTypes.NewTestMetadata()), - trivyTypes.String("centralindia", trivyTypes.NewTestMetadata()), - trivyTypes.String("eastasia", trivyTypes.NewTestMetadata()), - trivyTypes.String("japaneast", trivyTypes.NewTestMetadata()), - trivyTypes.String("jioindiawest", trivyTypes.NewTestMetadata()), - trivyTypes.String("koreacentral", trivyTypes.NewTestMetadata()), - trivyTypes.String("canadacentral", trivyTypes.NewTestMetadata()), - trivyTypes.String("francecentral", trivyTypes.NewTestMetadata()), - trivyTypes.String("germanywestcentral", trivyTypes.NewTestMetadata()), - trivyTypes.String("norwayeast", trivyTypes.NewTestMetadata()), - trivyTypes.String("switzerlandnorth", trivyTypes.NewTestMetadata()), - trivyTypes.String("uaenorth", trivyTypes.NewTestMetadata()), - trivyTypes.String("brazilsouth", trivyTypes.NewTestMetadata()), - trivyTypes.String("centralusstage", trivyTypes.NewTestMetadata()), - trivyTypes.String("eastusstage", trivyTypes.NewTestMetadata()), - trivyTypes.String("eastus2stage", trivyTypes.NewTestMetadata()), - trivyTypes.String("northcentralusstage", trivyTypes.NewTestMetadata()), - trivyTypes.String("southcentralusstage", trivyTypes.NewTestMetadata()), - trivyTypes.String("westusstage", trivyTypes.NewTestMetadata()), - trivyTypes.String("westus2stage", trivyTypes.NewTestMetadata()), - trivyTypes.String("asia", trivyTypes.NewTestMetadata()), - trivyTypes.String("asiapacific", trivyTypes.NewTestMetadata()), - trivyTypes.String("australia", trivyTypes.NewTestMetadata()), - trivyTypes.String("brazil", trivyTypes.NewTestMetadata()), - trivyTypes.String("canada", trivyTypes.NewTestMetadata()), - trivyTypes.String("europe", trivyTypes.NewTestMetadata()), - trivyTypes.String("global", trivyTypes.NewTestMetadata()), - trivyTypes.String("india", trivyTypes.NewTestMetadata()), - trivyTypes.String("japan", trivyTypes.NewTestMetadata()), - trivyTypes.String("uk", trivyTypes.NewTestMetadata()), - trivyTypes.String("unitedstates", trivyTypes.NewTestMetadata()), - trivyTypes.String("eastasiastage", trivyTypes.NewTestMetadata()), - trivyTypes.String("southeastasiastage", trivyTypes.NewTestMetadata()), - trivyTypes.String("centraluseuap", trivyTypes.NewTestMetadata()), - trivyTypes.String("eastus2euap", trivyTypes.NewTestMetadata()), - trivyTypes.String("westcentralus", trivyTypes.NewTestMetadata()), - trivyTypes.String("southafricawest", trivyTypes.NewTestMetadata()), - trivyTypes.String("australiacentral", trivyTypes.NewTestMetadata()), - trivyTypes.String("australiacentral2", trivyTypes.NewTestMetadata()), - trivyTypes.String("australiasoutheast", trivyTypes.NewTestMetadata()), - trivyTypes.String("japanwest", trivyTypes.NewTestMetadata()), - trivyTypes.String("jioindiacentral", trivyTypes.NewTestMetadata()), - trivyTypes.String("koreasouth", trivyTypes.NewTestMetadata()), - trivyTypes.String("southindia", trivyTypes.NewTestMetadata()), - trivyTypes.String("westindia", trivyTypes.NewTestMetadata()), - trivyTypes.String("canadaeast", trivyTypes.NewTestMetadata()), - trivyTypes.String("francesouth", trivyTypes.NewTestMetadata()), - trivyTypes.String("germanynorth", trivyTypes.NewTestMetadata()), - trivyTypes.String("norwaywest", trivyTypes.NewTestMetadata()), - trivyTypes.String("swedensouth", trivyTypes.NewTestMetadata()), - trivyTypes.String("switzerlandwest", trivyTypes.NewTestMetadata()), - trivyTypes.String("ukwest", trivyTypes.NewTestMetadata()), - trivyTypes.String("uaecentral", trivyTypes.NewTestMetadata()), - trivyTypes.String("brazilsoutheast", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Azure.Monitor = test.input - results := CheckCaptureAllRegions.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckCaptureAllRegions.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/azure/monitor/capture_all_regions_test.rego b/checks/cloud/azure/monitor/capture_all_regions_test.rego new file mode 100644 index 00000000..e7600f1f --- /dev/null +++ b/checks/cloud/azure/monitor/capture_all_regions_test.rego @@ -0,0 +1,34 @@ +package builtin.azure.monitor.azure0032_test + +import rego.v1 + +import data.builtin.azure.monitor.azure0032 as check +import data.lib.test + +test_deny_profile_captures_only_one_region if { + inp := {"azure": {"monitor": {"logprofiles": [{"locations": [{"value": "eastus"}]}]}}} + + res := check.deny with input as inp + count(res) == 1 +} + +test_deny_profiles_without_locations if { + inp := {"azure": {"monitor": {"logprofiles": [{}]}}} + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_profiles_with_all_locations if { + inp := {"azure": {"monitor": {"logprofiles": [{"locations": all_locations}]}}} + + res := check.deny with input as inp + count(res) == 0 +} + +all_locations := locations if { + locations := [ + {"value": region} | + some region in check.required_regions + ] +} diff --git a/checks/cloud/azure/network/retention_policy_set.go b/checks/cloud/azure/network/retention_policy_set.go index 39025ea1..7b7b088a 100755 --- a/checks/cloud/azure/network/retention_policy_set.go +++ b/checks/cloud/azure/network/retention_policy_set.go @@ -30,7 +30,8 @@ Setting an retention policy will help ensure as much information is available fo Links: terraformRetentionPolicySetLinks, RemediationMarkdown: terraformRetentionPolicySetRemediationMarkdown, }, - Severity: severity.Low, + Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, flowLog := range s.Azure.Network.NetworkWatcherFlowLogs { diff --git a/checks/cloud/azure/network/retention_policy_set.rego b/checks/cloud/azure/network/retention_policy_set.rego new file mode 100644 index 00000000..d6d586e5 --- /dev/null +++ b/checks/cloud/azure/network/retention_policy_set.rego @@ -0,0 +1,60 @@ +# METADATA +# title: Retention policy for flow logs should be enabled and set to greater than 90 days +# description: | +# Flow logs are the source of truth for all network activity in your cloud environment. +# +# To enable analysis in security event that was detected late, you need to have the logs available. +# +# Setting an retention policy will help ensure as much information is available for review. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.microsoft.com/en-us/azure/network-watcher/network-watcher-monitoring-overview +# custom: +# id: AVD-AZU-0049 +# avd_id: AVD-AZU-0049 +# provider: azure +# service: network +# severity: LOW +# short_code: retention-policy-set +# recommended_action: Ensure flow log retention is turned on with an expiry of >90 days +# input: +# selector: +# - type: cloud +# subtypes: +# - service: network +# provider: azure +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_watcher_flow_log#retention_policy +# good_examples: checks/cloud/azure/network/retention_policy_set.tf.go +# bad_examples: checks/cloud/azure/network/retention_policy_set.tf.go +package builtin.azure.network.azure0049 + +import rego.v1 + +flowlogs := input.azure.network.networkwatcherflowlogs + +deny contains res if { + some flowlog in flowlogs + isManaged(flowlog) + + not flowlog.retentionpolicy.enabled.value + res := result.new( + "Flow log does not enable the log retention policy.", + object.get(flowlog, ["retentionpolicy", "enabled"], flowlog), + ) +} + +deny contains res if { + some flowlog in flowlogs + isManaged(flowlog) + + flowlog.retentionpolicy.enabled.value + flowlog.retentionpolicy.days.value < 90 + res := result.new( + "Flow log has a log retention policy of less than 90 days.", + object.get(flowlog, ["retentionpolicy", "days"], flowlog), + ) +} \ No newline at end of file diff --git a/checks/cloud/azure/network/retention_policy_set_test.go b/checks/cloud/azure/network/retention_policy_set_test.go deleted file mode 100644 index 32b765ed..00000000 --- a/checks/cloud/azure/network/retention_policy_set_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package network - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/network" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckRetentionPolicySet(t *testing.T) { - tests := []struct { - name string - input network.Network - expected bool - }{ - { - name: "Network watcher flow log retention policy disabled", - input: network.Network{ - NetworkWatcherFlowLogs: []network.NetworkWatcherFlowLog{ - { - Metadata: trivyTypes.NewTestMetadata(), - RetentionPolicy: network.RetentionPolicy{ - Metadata: trivyTypes.NewTestMetadata(), - Enabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - Days: trivyTypes.Int(100, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "Network watcher flow log retention policy enabled for 30 days", - input: network.Network{ - NetworkWatcherFlowLogs: []network.NetworkWatcherFlowLog{ - { - Metadata: trivyTypes.NewTestMetadata(), - RetentionPolicy: network.RetentionPolicy{ - Metadata: trivyTypes.NewTestMetadata(), - Enabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - Days: trivyTypes.Int(30, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "Network watcher flow log retention policy enabled for 100 days", - input: network.Network{ - NetworkWatcherFlowLogs: []network.NetworkWatcherFlowLog{ - { - Metadata: trivyTypes.NewTestMetadata(), - RetentionPolicy: network.RetentionPolicy{ - Metadata: trivyTypes.NewTestMetadata(), - Enabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - Days: trivyTypes.Int(100, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Azure.Network = test.input - results := CheckRetentionPolicySet.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckRetentionPolicySet.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/azure/network/retention_policy_set_test.rego b/checks/cloud/azure/network/retention_policy_set_test.rego new file mode 100644 index 00000000..6e6cb4b1 --- /dev/null +++ b/checks/cloud/azure/network/retention_policy_set_test.rego @@ -0,0 +1,69 @@ +package builtin.azure.network.azure0049_test + +import rego.v1 + +import data.builtin.azure.network.azure0049 as check +import data.lib.test + +test_deny_flow_log_retention_policy_disabled if { + inp := { + "azure": { + "network": { + "networkwatcherflowlogs": [{ + "retentionpolicy": { + "enabled": { + "value": false + } + } + }] + } + } + } + + res := check.deny with input as inp + count(res) == 1 +} + +test_deny_flow_log_retention_policy_less_than_90 if { + inp := { + "azure": { + "network": { + "networkwatcherflowlogs": [{ + "retentionpolicy": { + "enabled": { + "value": true + }, + "days": { + "value": 89 + } + } + }] + } + } + } + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_flow_log_retention_policy_greater_than_90 if { + inp := { + "azure": { + "network": { + "networkwatcherflowlogs": [{ + "retentionpolicy": { + "enabled": { + "value": true + }, + "days": { + "value": 90 + } + } + }] + } + } + } + + res := check.deny with input as inp + count(res) == 0 +} \ No newline at end of file diff --git a/checks/cloud/azure/securitycenter/alert_on_severe_notifications.go b/checks/cloud/azure/securitycenter/alert_on_severe_notifications.go index e914c147..84dbabca 100755 --- a/checks/cloud/azure/securitycenter/alert_on_severe_notifications.go +++ b/checks/cloud/azure/securitycenter/alert_on_severe_notifications.go @@ -28,7 +28,8 @@ Microsoft will notify the security contact directly in the event of a security i Links: terraformAlertOnSevereNotificationsLinks, RemediationMarkdown: terraformAlertOnSevereNotificationsRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, contact := range s.Azure.SecurityCenter.Contacts { diff --git a/checks/cloud/azure/securitycenter/alert_on_severe_notifications.rego b/checks/cloud/azure/securitycenter/alert_on_severe_notifications.rego new file mode 100644 index 00000000..75381e4c --- /dev/null +++ b/checks/cloud/azure/securitycenter/alert_on_severe_notifications.rego @@ -0,0 +1,44 @@ +# METADATA +# title: Send notification emails for high severity alerts +# description: | +# It is recommended that at least one valid contact is configured for the security center. +# +# Microsoft will notify the security contact directly in the event of a security incident using email and require alerting to be turned on. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://azure.microsoft.com/en-us/services/security-center/ +# custom: +# id: AVD-AZU-0044 +# avd_id: AVD-AZU-0044 +# provider: azure +# service: security-center +# severity: MEDIUM +# short_code: alert-on-severe-notifications +# recommended_action: Set alert notifications to be on +# input: +# selector: +# - type: cloud +# subtypes: +# - service: securitycenter +# provider: azure +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/security_center_contact#alert_notifications +# good_examples: checks/cloud/azure/securitycenter/alert_on_severe_notifications.tf.go +# bad_examples: checks/cloud/azure/securitycenter/alert_on_severe_notifications.tf.go +package builtin.azure.securitycenter.azure0044 + +import rego.v1 + +deny contains res if { + some contact in input.azure.securitycenter.contacts + isManaged(contact) + + not contact.enablealertnotifications.value + res := result.new( + "Security contact has alert notifications disabled.", + object.get(contact, "enablealertnotifications", contact), + ) +} diff --git a/checks/cloud/azure/securitycenter/alert_on_severe_notifications_test.go b/checks/cloud/azure/securitycenter/alert_on_severe_notifications_test.go deleted file mode 100644 index b869c052..00000000 --- a/checks/cloud/azure/securitycenter/alert_on_severe_notifications_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package securitycenter - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/securitycenter" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckAlertOnSevereNotifications(t *testing.T) { - tests := []struct { - name string - input securitycenter.SecurityCenter - expected bool - }{ - { - name: "Security center alert nofifications disabled", - input: securitycenter.SecurityCenter{ - Contacts: []securitycenter.Contact{ - { - Metadata: trivyTypes.NewTestMetadata(), - EnableAlertNotifications: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "Security center alert nofifications enabled", - input: securitycenter.SecurityCenter{ - Contacts: []securitycenter.Contact{ - { - Metadata: trivyTypes.NewTestMetadata(), - EnableAlertNotifications: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Azure.SecurityCenter = test.input - results := CheckAlertOnSevereNotifications.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckAlertOnSevereNotifications.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/azure/securitycenter/alert_on_severe_notifications_test.rego b/checks/cloud/azure/securitycenter/alert_on_severe_notifications_test.rego new file mode 100644 index 00000000..2a301705 --- /dev/null +++ b/checks/cloud/azure/securitycenter/alert_on_severe_notifications_test.rego @@ -0,0 +1,18 @@ +package builtin.azure.securitycenter.azure0044_test + +import rego.v1 + +import data.builtin.azure.securitycenter.azure0044 as check +import data.lib.test + +test_deny_security_center_alert_notifications_disabled if { + res := check.deny with input as build_input(false) + count(res) == 1 +} + +test_allow_security_center_alert_notifications_enabled if { + res := check.deny with input as build_input(true) + count(res) == 0 +} + +build_input(enabled) := {"azure": {"securitycenter": {"contacts": [{"enablealertnotifications": {"value": enabled}}]}}} diff --git a/checks/cloud/azure/securitycenter/enable_standard_subscription.go b/checks/cloud/azure/securitycenter/enable_standard_subscription.go index 8167e3e0..147f5940 100755 --- a/checks/cloud/azure/securitycenter/enable_standard_subscription.go +++ b/checks/cloud/azure/securitycenter/enable_standard_subscription.go @@ -30,7 +30,8 @@ var CheckEnableStandardSubscription = rules.Register( Links: terraformEnableStandardSubscriptionLinks, RemediationMarkdown: terraformEnableStandardSubscriptionRemediationMarkdown, }, - Severity: severity.Low, + Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, sub := range s.Azure.SecurityCenter.Subscriptions { diff --git a/checks/cloud/azure/securitycenter/enable_standard_subscription.rego b/checks/cloud/azure/securitycenter/enable_standard_subscription.rego new file mode 100644 index 00000000..19067bbe --- /dev/null +++ b/checks/cloud/azure/securitycenter/enable_standard_subscription.rego @@ -0,0 +1,46 @@ +# METADATA +# title: Enable the standard security center subscription tier +# description: | +# To benefit from Azure Defender you should use the Standard subscription tier. +# +# Enabling Azure Defender extends the capabilities of the free mode to workloads running in private and other public clouds, providing unified security management and threat protection across your hybrid cloud workloads. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.microsoft.com/en-us/azure/security-center/security-center-pricing +# custom: +# id: AVD-AZU-0045 +# avd_id: AVD-AZU-0045 +# provider: azure +# service: security-center +# severity: LOW +# short_code: enable-standard-subscription +# recommended_action: Enable standard subscription tier to benefit from Azure Defender +# input: +# selector: +# - type: cloud +# subtypes: +# - service: securitycenter +# provider: azure +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/security_center_subscription_pricing#tier +# good_examples: checks/cloud/azure/securitycenter/enable_standard_subscription.tf.go +# bad_examples: checks/cloud/azure/securitycenter/enable_standard_subscription.tf.go +package builtin.azure.securitycenter.azure0045 + +import rego.v1 + +free_tier := "Free" + +deny contains res if { + some sub in input.azure.securitycenter.subscriptions + isManaged(sub) + + sub.tier.value == free_tier + res := result.new( + "Security center subscription uses the free tier.", + sub.tier, + ) +} \ No newline at end of file diff --git a/checks/cloud/azure/securitycenter/enable_standard_subscription_test.go b/checks/cloud/azure/securitycenter/enable_standard_subscription_test.go deleted file mode 100644 index 95a68c74..00000000 --- a/checks/cloud/azure/securitycenter/enable_standard_subscription_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package securitycenter - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/securitycenter" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableStandardSubscription(t *testing.T) { - tests := []struct { - name string - input securitycenter.SecurityCenter - expected bool - }{ - { - name: "Security center set with free subscription", - input: securitycenter.SecurityCenter{ - Subscriptions: []securitycenter.SubscriptionPricing{ - { - Metadata: trivyTypes.NewTestMetadata(), - Tier: trivyTypes.String(securitycenter.TierFree, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "Security center set with standard subscription", - input: securitycenter.SecurityCenter{ - Subscriptions: []securitycenter.SubscriptionPricing{ - { - Metadata: trivyTypes.NewTestMetadata(), - Tier: trivyTypes.String(securitycenter.TierStandard, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Azure.SecurityCenter = test.input - results := CheckEnableStandardSubscription.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableStandardSubscription.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/azure/securitycenter/enable_standard_subscription_test.rego b/checks/cloud/azure/securitycenter/enable_standard_subscription_test.rego new file mode 100644 index 00000000..6c8b6126 --- /dev/null +++ b/checks/cloud/azure/securitycenter/enable_standard_subscription_test.rego @@ -0,0 +1,40 @@ +package builtin.azure.securitycenter.azure0045_test + +import rego.v1 + +import data.builtin.azure.securitycenter.azure0045 as check +import data.lib.test + +test_deny_subscription_use_free_tier if { + inp := { + "azure": { + "securitycenter": { + "subscriptions": [{ + "tier": { + "value": check.free_tier + } + }] + } + } + } + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_subscription_use_standard_tier if { + inp := { + "azure": { + "securitycenter": { + "subscriptions": [{ + "tier": { + "value": "Standard" + } + }] + } + } + } + + res := check.deny with input as inp + count(res) == 0 +} \ No newline at end of file diff --git a/checks/cloud/azure/securitycenter/set_required_contact_details.go b/checks/cloud/azure/securitycenter/set_required_contact_details.go index 3ec21ee5..a605a3cd 100755 --- a/checks/cloud/azure/securitycenter/set_required_contact_details.go +++ b/checks/cloud/azure/securitycenter/set_required_contact_details.go @@ -28,7 +28,8 @@ Microsoft will notify the security contact directly in the event of a security i Links: terraformSetRequiredContactDetailsLinks, RemediationMarkdown: terraformSetRequiredContactDetailsRemediationMarkdown, }, - Severity: severity.Low, + Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, contact := range s.Azure.SecurityCenter.Contacts { diff --git a/checks/cloud/azure/securitycenter/set_required_contact_details.rego b/checks/cloud/azure/securitycenter/set_required_contact_details.rego new file mode 100644 index 00000000..effa4476 --- /dev/null +++ b/checks/cloud/azure/securitycenter/set_required_contact_details.rego @@ -0,0 +1,46 @@ +# METADATA +# title: The required contact details should be set for security center +# description: | +# It is recommended that at least one valid contact is configured for the security center. +# +# Microsoft will notify the security contact directly in the event of a security incident and will look to use a telephone number in cases where a prompt response is required. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://azure.microsoft.com/en-us/services/security-center/ +# custom: +# id: AVD-AZU-0046 +# avd_id: AVD-AZU-0046 +# provider: azure +# service: security-center +# severity: LOW +# short_code: set-required-contact-details +# recommended_action: Set a telephone number for security center contact +# input: +# selector: +# - type: cloud +# subtypes: +# - service: securitycenter +# provider: azure +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/security_center_contact#phone +# good_examples: checks/cloud/azure/securitycenter/set_required_contact_details.tf.go +# bad_examples: checks/cloud/azure/securitycenter/set_required_contact_details.tf.go +package builtin.azure.securitycenter.azure0046 + +import rego.v1 + +deny contains res if { + some contact in input.azure.securitycenter.contacts + isManaged(contact) + + not contact_has_phone(contact) + res := result.new( + "Security contact does not have a phone number listed.", + object.get(contact, "phone", contact), + ) +} + +contact_has_phone(contact) := contact.phone.value != "" \ No newline at end of file diff --git a/checks/cloud/azure/securitycenter/set_required_contact_details_test.go b/checks/cloud/azure/securitycenter/set_required_contact_details_test.go deleted file mode 100644 index 6b5f15ac..00000000 --- a/checks/cloud/azure/securitycenter/set_required_contact_details_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package securitycenter - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/securitycenter" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckSetRequiredContactDetails(t *testing.T) { - tests := []struct { - name string - input securitycenter.SecurityCenter - expected bool - }{ - { - name: "Contact's phone number missing", - input: securitycenter.SecurityCenter{ - Contacts: []securitycenter.Contact{ - { - Metadata: trivyTypes.NewTestMetadata(), - Phone: trivyTypes.String("", trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "Contact's phone number provided", - input: securitycenter.SecurityCenter{ - Contacts: []securitycenter.Contact{ - { - Metadata: trivyTypes.NewTestMetadata(), - Phone: trivyTypes.String("+1-555-555-5555", trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Azure.SecurityCenter = test.input - results := CheckSetRequiredContactDetails.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckSetRequiredContactDetails.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/azure/securitycenter/set_required_contact_details_test.rego b/checks/cloud/azure/securitycenter/set_required_contact_details_test.rego new file mode 100644 index 00000000..096f35b2 --- /dev/null +++ b/checks/cloud/azure/securitycenter/set_required_contact_details_test.rego @@ -0,0 +1,44 @@ +package builtin.azure.securitycenter.azure0046_test + +import rego.v1 + +import data.builtin.azure.securitycenter.azure0046 as check +import data.lib.test + +test_deny_contact_without_phone if { + inp := { + "azure": { + "securitycenter": { + "contacts": [ + { + "phone": { + "value": "" + } + } + ] + } + } + } + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_contact_with_phone if { + inp := { + "azure": { + "securitycenter": { + "contacts": [ + { + "phone": { + "value": "555-555-5555" + } + } + ] + } + } + } + + res := check.deny with input as inp + count(res) == 0 +} diff --git a/checks/cloud/azure/synapse/virtual_network_enabled.go b/checks/cloud/azure/synapse/virtual_network_enabled.go index 54dbf741..40e917cf 100755 --- a/checks/cloud/azure/synapse/virtual_network_enabled.go +++ b/checks/cloud/azure/synapse/virtual_network_enabled.go @@ -31,7 +31,8 @@ Managed private endpoints are private endpoints created in a Managed Virtual Net Links: terraformVirtualNetworkEnabledLinks, RemediationMarkdown: terraformVirtualNetworkEnabledRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, workspace := range s.Azure.Synapse.Workspaces { diff --git a/checks/cloud/azure/synapse/virtual_network_enabled.rego b/checks/cloud/azure/synapse/virtual_network_enabled.rego new file mode 100644 index 00000000..c5d2ce29 --- /dev/null +++ b/checks/cloud/azure/synapse/virtual_network_enabled.rego @@ -0,0 +1,47 @@ +# METADATA +# title: Synapse Workspace should have managed virtual network enabled, the default is disabled. +# description: | +# Synapse Workspace does not have managed virtual network enabled by default. +# +# When you create your Azure Synapse workspace, you can choose to associate it to a Microsoft Azure Virtual Network. The Virtual Network associated with your workspace is managed by Azure Synapse. This Virtual Network is called a Managed workspace Virtual Network. +# +# Managed private endpoints are private endpoints created in a Managed Virtual Network associated with your Azure Synapse workspace. Managed private endpoints establish a private link to Azure resources. You can only use private links in a workspace that has a Managed workspace Virtual Network. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.microsoft.com/en-us/azure/synapse-analytics/security/synapse-workspace-managed-private-endpoints +# - https://docs.microsoft.com/en-us/azure/synapse-analytics/security/synapse-workspace-managed-vnet +# custom: +# id: AVD-AZU-0034 +# avd_id: AVD-AZU-0034 +# provider: azure +# service: synapse +# severity: MEDIUM +# short_code: virtual-network-enabled +# recommended_action: Set manage virtual network to enabled +# input: +# selector: +# - type: cloud +# subtypes: +# - service: synapse +# provider: azure +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/synapse_workspace#managed_virtual_network_enabled +# good_examples: checks/cloud/azure/synapse/virtual_network_enabled.tf.go +# bad_examples: checks/cloud/azure/synapse/virtual_network_enabled.tf.go +package builtin.azure.synapse.azure0034 + +import rego.v1 + +deny contains res if { + some workspace in input.azure.synapse.workspaces + isManaged(workspace) + + not workspace.enablemanagedvirtualnetwork.value + res := result.new( + "Workspace does not have a managed virtual network enabled.", + object.get(workspace, "enablemanagedvirtualnetwork", workspace), + ) +} diff --git a/checks/cloud/azure/synapse/virtual_network_enabled_test.go b/checks/cloud/azure/synapse/virtual_network_enabled_test.go deleted file mode 100644 index 3dd6daf2..00000000 --- a/checks/cloud/azure/synapse/virtual_network_enabled_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package synapse - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/azure/synapse" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckVirtualNetworkEnabled(t *testing.T) { - tests := []struct { - name string - input synapse.Synapse - expected bool - }{ - { - name: "Synapse workspace managed VN disabled", - input: synapse.Synapse{ - Workspaces: []synapse.Workspace{ - { - Metadata: trivyTypes.NewTestMetadata(), - EnableManagedVirtualNetwork: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "Synapse workspace managed VN enabled", - input: synapse.Synapse{ - Workspaces: []synapse.Workspace{ - { - Metadata: trivyTypes.NewTestMetadata(), - EnableManagedVirtualNetwork: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Azure.Synapse = test.input - results := CheckVirtualNetworkEnabled.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckVirtualNetworkEnabled.LongID() { - found = true - } - } - if test.expected { - assert.True(t, found, "Rule should have been found") - } else { - assert.False(t, found, "Rule should not have been found") - } - }) - } -} diff --git a/checks/cloud/azure/synapse/virtual_network_enabled_test.rego b/checks/cloud/azure/synapse/virtual_network_enabled_test.rego new file mode 100644 index 00000000..fd4b2b67 --- /dev/null +++ b/checks/cloud/azure/synapse/virtual_network_enabled_test.rego @@ -0,0 +1,20 @@ +package builtin.azure.synapse.azure0034_test + +import rego.v1 + +import data.builtin.azure.synapse.azure0034 as check +import data.lib.test + +test_deny_managed_virtual_network_disabled if { + inp := {"azure": {"synapse": {"workspaces": [{"enablemanagedvirtualnetwork": {"value": false}}]}}} + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_managed_virtual_network_enabled if { + inp := {"azure": {"synapse": {"workspaces": [{"enablemanagedvirtualnetwork": {"value": true}}]}}} + + res := check.deny with input as inp + count(res) == 0 +}