From 18549a8db783e5daa691d0eec5b2526b5bd5782f Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Fri, 19 Jul 2024 17:16:24 +0700 Subject: [PATCH 1/7] refactor(checks): migrate Azure monitor, network, synapse, securitycenter to Rego Signed-off-by: Nikita Pivkin --- avd_docs/azure/monitor/AVD-AZU-0031/docs.md | 3 +- avd_docs/azure/monitor/AVD-AZU-0032/docs.md | 3 +- avd_docs/azure/monitor/AVD-AZU-0033/docs.md | 3 +- avd_docs/azure/network/AVD-AZU-0049/docs.md | 10 +- .../securitycenter/AVD-AZU-0044/Terraform.md | 2 +- .../azure/securitycenter/AVD-AZU-0044/docs.md | 6 +- .../azure/securitycenter/AVD-AZU-0045/docs.md | 7 +- .../azure/securitycenter/AVD-AZU-0046/docs.md | 6 +- avd_docs/azure/synapse/AVD-AZU-0034/docs.md | 4 +- .../monitor/activity_log_retention_set.go | 3 +- .../monitor/activity_log_retention_set.rego | 54 +++++++ .../activity_log_retention_set_test.go | 89 ------------ .../activity_log_retention_set_test.rego | 33 +++++ .../azure/monitor/capture_all_activities.go | 3 +- .../azure/monitor/capture_all_activities.rego | 55 +++++++ .../monitor/capture_all_activities_test.go | 71 --------- .../monitor/capture_all_activities_test.rego | 31 ++++ .../azure/monitor/capture_all_regions.go | 3 +- .../azure/monitor/capture_all_regions.rego | 128 +++++++++++++++++ .../azure/monitor/capture_all_regions_test.go | 136 ------------------ .../monitor/capture_all_regions_test.rego | 34 +++++ .../azure/network/retention_policy_set.go | 3 +- .../azure/network/retention_policy_set.rego | 60 ++++++++ .../network/retention_policy_set_test.go | 89 ------------ .../network/retention_policy_set_test.rego | 33 +++++ .../alert_on_severe_notifications.go | 3 +- .../alert_on_severe_notifications.rego | 44 ++++++ .../alert_on_severe_notifications_test.go | 65 --------- .../alert_on_severe_notifications_test.rego | 18 +++ .../enable_standard_subscription.go | 3 +- .../enable_standard_subscription.rego | 46 ++++++ .../enable_standard_subscription_test.go | 65 --------- .../enable_standard_subscription_test.rego | 20 +++ .../set_required_contact_details.go | 3 +- .../set_required_contact_details.rego | 46 ++++++ .../set_required_contact_details_test.go | 65 --------- .../set_required_contact_details_test.rego | 20 +++ .../azure/synapse/virtual_network_enabled.go | 3 +- .../synapse/virtual_network_enabled.rego | 47 ++++++ .../synapse/virtual_network_enabled_test.go | 65 --------- .../synapse/virtual_network_enabled_test.rego | 20 +++ 41 files changed, 733 insertions(+), 669 deletions(-) create mode 100644 checks/cloud/azure/monitor/activity_log_retention_set.rego delete mode 100644 checks/cloud/azure/monitor/activity_log_retention_set_test.go create mode 100644 checks/cloud/azure/monitor/activity_log_retention_set_test.rego create mode 100644 checks/cloud/azure/monitor/capture_all_activities.rego delete mode 100644 checks/cloud/azure/monitor/capture_all_activities_test.go create mode 100644 checks/cloud/azure/monitor/capture_all_activities_test.rego create mode 100644 checks/cloud/azure/monitor/capture_all_regions.rego delete mode 100644 checks/cloud/azure/monitor/capture_all_regions_test.go create mode 100644 checks/cloud/azure/monitor/capture_all_regions_test.rego create mode 100644 checks/cloud/azure/network/retention_policy_set.rego delete mode 100644 checks/cloud/azure/network/retention_policy_set_test.go create mode 100644 checks/cloud/azure/network/retention_policy_set_test.rego create mode 100644 checks/cloud/azure/securitycenter/alert_on_severe_notifications.rego delete mode 100644 checks/cloud/azure/securitycenter/alert_on_severe_notifications_test.go create mode 100644 checks/cloud/azure/securitycenter/alert_on_severe_notifications_test.rego create mode 100644 checks/cloud/azure/securitycenter/enable_standard_subscription.rego delete mode 100644 checks/cloud/azure/securitycenter/enable_standard_subscription_test.go create mode 100644 checks/cloud/azure/securitycenter/enable_standard_subscription_test.rego create mode 100644 checks/cloud/azure/securitycenter/set_required_contact_details.rego delete mode 100644 checks/cloud/azure/securitycenter/set_required_contact_details_test.go create mode 100644 checks/cloud/azure/securitycenter/set_required_contact_details_test.rego create mode 100644 checks/cloud/azure/synapse/virtual_network_enabled.rego delete mode 100644 checks/cloud/azure/synapse/virtual_network_enabled_test.go create mode 100644 checks/cloud/azure/synapse/virtual_network_enabled_test.rego 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..6d1658e7 --- /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), + ) +} 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..f49646ea --- /dev/null +++ b/checks/cloud/azure/network/retention_policy_set_test.rego @@ -0,0 +1,33 @@ +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 +} 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..61300edf --- /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, + ) +} 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..14574f96 --- /dev/null +++ b/checks/cloud/azure/securitycenter/enable_standard_subscription_test.rego @@ -0,0 +1,20 @@ +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 +} 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..e54e593c --- /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 != "" 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..ed966866 --- /dev/null +++ b/checks/cloud/azure/securitycenter/set_required_contact_details_test.rego @@ -0,0 +1,20 @@ +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 +} From 8c1b76340c75226343daf1f5ecb6e6dc603c1e2f Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Mon, 19 Aug 2024 19:29:37 +0600 Subject: [PATCH 2/7] migrate some checks Signed-off-by: Nikita Pivkin --- .../network/disable_rdp_from_internet.go | 3 +- .../network/disable_rdp_from_internet.rego | 52 +++++++ .../network/disable_rdp_from_internet_test.go | 132 ------------------ .../disable_rdp_from_internet_test.rego | 54 +++++++ .../cloud/azure/network/no_public_egress.go | 3 +- .../cloud/azure/network/no_public_egress.rego | 42 ++++++ .../azure/network/no_public_egress_test.go | 83 ----------- .../azure/network/no_public_egress_test.rego | 28 ++++ .../cloud/azure/network/no_public_ingress.go | 3 +- .../azure/network/no_public_ingress.rego | 43 ++++++ .../azure/network/no_public_ingress_test.go | 125 ----------------- .../azure/network/no_public_ingress_test.rego | 28 ++++ .../network/ssh_blocked_from_internet.rego | 50 +++++++ .../network/ssh_blocked_from_internet_test.go | 128 ----------------- .../ssh_blocked_from_internet_test.rego | 54 +++++++ 15 files changed, 357 insertions(+), 471 deletions(-) create mode 100644 checks/cloud/azure/network/disable_rdp_from_internet.rego delete mode 100644 checks/cloud/azure/network/disable_rdp_from_internet_test.go create mode 100644 checks/cloud/azure/network/disable_rdp_from_internet_test.rego create mode 100644 checks/cloud/azure/network/no_public_egress.rego delete mode 100644 checks/cloud/azure/network/no_public_egress_test.go create mode 100644 checks/cloud/azure/network/no_public_egress_test.rego create mode 100644 checks/cloud/azure/network/no_public_ingress.rego delete mode 100644 checks/cloud/azure/network/no_public_ingress_test.go create mode 100644 checks/cloud/azure/network/no_public_ingress_test.rego create mode 100644 checks/cloud/azure/network/ssh_blocked_from_internet.rego delete mode 100644 checks/cloud/azure/network/ssh_blocked_from_internet_test.go create mode 100644 checks/cloud/azure/network/ssh_blocked_from_internet_test.rego diff --git a/checks/cloud/azure/network/disable_rdp_from_internet.go b/checks/cloud/azure/network/disable_rdp_from_internet.go index b8ecdede..6ac26642 100755 --- a/checks/cloud/azure/network/disable_rdp_from_internet.go +++ b/checks/cloud/azure/network/disable_rdp_from_internet.go @@ -31,7 +31,8 @@ RDP access should not be permitted from the internet (*, 0.0.0.0, /0, internet, Links: terraformDisableRdpFromInternetLinks, RemediationMarkdown: terraformDisableRdpFromInternetRemediationMarkdown, }, - Severity: severity.Critical, + Severity: severity.Critical, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, group := range s.Azure.Network.SecurityGroups { diff --git a/checks/cloud/azure/network/disable_rdp_from_internet.rego b/checks/cloud/azure/network/disable_rdp_from_internet.rego new file mode 100644 index 00000000..7e01ea8a --- /dev/null +++ b/checks/cloud/azure/network/disable_rdp_from_internet.rego @@ -0,0 +1,52 @@ +# METADATA +# title: RDP access should not be accessible from the Internet, should be blocked on port 3389 +# description: | +# RDP access can be configured on either the network security group or in the network security group rule. +# RDP access should not be permitted from the internet (*, 0.0.0.0, /0, internet, any). Consider using the Azure Bastion Service. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.microsoft.com/en-us/azure/bastion/tutorial-create-host-portal +# custom: +# id: AVD-AZU-0048 +# avd_id: AVD-AZU-0048 +# provider: azure +# service: network +# severity: CRITICAL +# short_code: disable-rdp-from-internet +# recommended_action: Block RDP port from internet +# input: +# selector: +# - type: cloud +# subtypes: +# - service: network +# provider: azure +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/network_security_group#security_rule +# - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule#source_port_ranges +# good_examples: checks/cloud/azure/network/disable_rdp_from_internet.tf.go +# bad_examples: checks/cloud/azure/network/disable_rdp_from_internet.tf.go +package builtin.azure.network.azure0048 + +import rego.v1 + +deny contains res if { + some group in input.azure.network.securitygroups + some rule in group.rules + rule.allow.value + not rule.outbound.value + lower(rule.protocol.value) != "icmp" + some ports in rule.destinationports + port_range_includes(ports.start, ports.end, 3389) + some ip in rule.sourceaddresses + cidr.is_public(ip.value) + cidr.count_addresses(ip.value) > 1 + res := result.new( + "Security group rule allows ingress to RDP port from multiple public internet addresses.", + ip, + ) +} + +port_range_includes(from, to, port) if from <= port <= to diff --git a/checks/cloud/azure/network/disable_rdp_from_internet_test.go b/checks/cloud/azure/network/disable_rdp_from_internet_test.go deleted file mode 100644 index c3ef9db8..00000000 --- a/checks/cloud/azure/network/disable_rdp_from_internet_test.go +++ /dev/null @@ -1,132 +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 TestCheckDisableRdpFromInternet(t *testing.T) { - tests := []struct { - name string - input network.Network - expected bool - }{ - { - name: "Security group inbound rule allowing RDP access from the Internet", - input: network.Network{ - SecurityGroups: []network.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []network.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - SourceAddresses: []trivyTypes.StringValue{ - trivyTypes.String("*", trivyTypes.NewTestMetadata()), - }, - SourcePorts: nil, - DestinationAddresses: nil, - DestinationPorts: []network.PortRange{ - { - Metadata: trivyTypes.NewTestMetadata(), - Start: trivyTypes.IntTest(3310), - End: trivyTypes.IntTest(3390), - }, - }, - Protocol: trivyTypes.String("Tcp", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Security group inbound rule allowing RDP access from a specific address", - input: network.Network{ - SecurityGroups: []network.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []network.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - DestinationPorts: []network.PortRange{ - { - Metadata: trivyTypes.NewTestMetadata(), - Start: trivyTypes.IntTest(3310), - End: trivyTypes.IntTest(3390), - }, - }, - SourceAddresses: []trivyTypes.StringValue{ - trivyTypes.String("4.53.160.75", trivyTypes.NewTestMetadata()), - }, - Protocol: trivyTypes.String("Tcp", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Security group inbound rule allowing only ICMP", - input: network.Network{ - SecurityGroups: []network.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []network.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - SourceAddresses: []trivyTypes.StringValue{ - trivyTypes.String("*", trivyTypes.NewTestMetadata()), - }, - SourcePorts: nil, - DestinationAddresses: nil, - DestinationPorts: []network.PortRange{ - { - Metadata: trivyTypes.NewTestMetadata(), - Start: trivyTypes.IntTest(3310), - End: trivyTypes.IntTest(3390), - }, - }, - Protocol: trivyTypes.String("Icmp", 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 := CheckDisableRdpFromInternet.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckDisableRdpFromInternet.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/disable_rdp_from_internet_test.rego b/checks/cloud/azure/network/disable_rdp_from_internet_test.rego new file mode 100644 index 00000000..862a4d51 --- /dev/null +++ b/checks/cloud/azure/network/disable_rdp_from_internet_test.rego @@ -0,0 +1,54 @@ +package builtin.azure.network.azure0048_test + +import rego.v1 + +import data.builtin.azure.network.azure0048 as check +import data.lib.test + +test_deny_inbound_rule_allows_rdp_access_from_internet if { + inp := {"azure": {"network": {"securitygroups": [{"rules": [{ + "outbound": {"value": false}, + "allow": {"value": true}, + "protocol": {"value": "Tcp"}, + "sourceaddresses": [{"value": "*"}], + "destinationports": [{ + "start": 3310, + "end": 3390, + }], + }]}]}}} + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_inbound_rule_allow_rdp_access_from_specific_address if { + inp := {"azure": {"network": {"securitygroups": [{"rules": [{ + "outbound": {"value": false}, + "allow": {"value": true}, + "protocol": {"value": "Tcp"}, + "sourceaddresses": [{"value": "237.84.2.178"}], + "destinationports": [{ + "start": 3310, + "end": 3390, + }], + }]}]}}} + + res := check.deny with input as inp + res == set() +} + +test_allow_inbound_rule_allow_access_for_icmp if { + inp := {"azure": {"network": {"securitygroups": [{"rules": [{ + "outbound": {"value": false}, + "allow": {"value": true}, + "protocol": {"value": "Icmp"}, + "sourceaddresses": [{"value": "*"}], + "destinationports": [{ + "start": 3310, + "end": 3390, + }], + }]}]}}} + + res := check.deny with input as inp + res == set() +} diff --git a/checks/cloud/azure/network/no_public_egress.go b/checks/cloud/azure/network/no_public_egress.go index d598f44f..d526138e 100755 --- a/checks/cloud/azure/network/no_public_egress.go +++ b/checks/cloud/azure/network/no_public_egress.go @@ -30,7 +30,8 @@ Where possible, segments should be broken into smaller subnets.`, Links: terraformNoPublicEgressLinks, RemediationMarkdown: terraformNoPublicEgressRemediationMarkdown, }, - Severity: severity.Critical, + Severity: severity.Critical, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, group := range s.Azure.Network.SecurityGroups { diff --git a/checks/cloud/azure/network/no_public_egress.rego b/checks/cloud/azure/network/no_public_egress.rego new file mode 100644 index 00000000..470bce51 --- /dev/null +++ b/checks/cloud/azure/network/no_public_egress.rego @@ -0,0 +1,42 @@ +# METADATA +# title: An outbound network security rule allows traffic to /0. +# description: | +# Network security rules should not use very broad subnets. +# Where possible, segments should be broken into smaller subnets. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.microsoft.com/en-us/azure/security/fundamentals/network-best-practices +# custom: +# id: AVD-AZU-0051 +# avd_id: AVD-AZU-0051 +# provider: azure +# service: network +# severity: CRITICAL +# short_code: no-public-egress +# recommended_action: Set a more restrictive cidr range +# input: +# selector: +# - type: cloud +# subtypes: +# - service: network +# provider: azure +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule +# good_examples: checks/cloud/azure/network/no_public_egress.tf.go +# bad_examples: checks/cloud/azure/network/no_public_egress.tf.go +package builtin.azure.network.azure0051 + +import rego.v1 + +deny contains res if { + some group in input.azure.network.securitygroups + some rule in group.rules + rule.outbound.value + rule.allow.value + some addr in rule.destinationaddresses + cidr.is_public(addr.value) + res := result.new("Security group rule allows egress to public internet.", addr) +} diff --git a/checks/cloud/azure/network/no_public_egress_test.go b/checks/cloud/azure/network/no_public_egress_test.go deleted file mode 100644 index 3abd1357..00000000 --- a/checks/cloud/azure/network/no_public_egress_test.go +++ /dev/null @@ -1,83 +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 TestCheckNoPublicEgress(t *testing.T) { - tests := []struct { - name string - input network.Network - expected bool - }{ - { - name: "Security group outbound rule with wildcard destination address", - input: network.Network{ - SecurityGroups: []network.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []network.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - Outbound: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - DestinationAddresses: []trivyTypes.StringValue{ - trivyTypes.String("*", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Security group outbound rule with private destination address", - input: network.Network{ - SecurityGroups: []network.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []network.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - Outbound: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - DestinationAddresses: []trivyTypes.StringValue{ - trivyTypes.String("10.0.0.0/16", 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 := CheckNoPublicEgress.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckNoPublicEgress.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/no_public_egress_test.rego b/checks/cloud/azure/network/no_public_egress_test.rego new file mode 100644 index 00000000..cb61d036 --- /dev/null +++ b/checks/cloud/azure/network/no_public_egress_test.rego @@ -0,0 +1,28 @@ +package builtin.azure.network.azure0051_test + +import rego.v1 + +import data.builtin.azure.network.azure0051 as check +import data.lib.test + +test_deny_outbound_rule_with_wildcard_destination_address if { + inp := {"azure": {"network": {"securitygroups": [{"rules": [{ + "allow": {"value": true}, + "outbound": {"value": true}, + "destinationaddresses": [{"value": "*"}], + }]}]}}} + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_outbound_rule_with_private_destination_address if { + inp := {"azure": {"network": {"securitygroups": [{"rules": [{ + "allow": {"value": true}, + "outbound": {"value": true}, + "destinationaddresses": [{"value": "10.0.0.0/16"}], + }]}]}}} + + res := check.deny with input as inp + count(res) == 0 +} diff --git a/checks/cloud/azure/network/no_public_ingress.go b/checks/cloud/azure/network/no_public_ingress.go index efa59ff0..c99a0a43 100755 --- a/checks/cloud/azure/network/no_public_ingress.go +++ b/checks/cloud/azure/network/no_public_ingress.go @@ -30,7 +30,8 @@ Where possible, segments should be broken into smaller subnets.`, Links: terraformNoPublicIngressLinks, RemediationMarkdown: terraformNoPublicIngressRemediationMarkdown, }, - Severity: severity.Critical, + Severity: severity.Critical, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, group := range s.Azure.Network.SecurityGroups { diff --git a/checks/cloud/azure/network/no_public_ingress.rego b/checks/cloud/azure/network/no_public_ingress.rego new file mode 100644 index 00000000..4a632f66 --- /dev/null +++ b/checks/cloud/azure/network/no_public_ingress.rego @@ -0,0 +1,43 @@ +# METADATA +# title: An inbound network security rule allows traffic from /0. +# description: | +# Network security rules should not use very broad subnets. +# Where possible, segments should be broken into smaller subnets. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.microsoft.com/en-us/azure/security/fundamentals/network-best-practices +# custom: +# id: AVD-AZU-0047 +# avd_id: AVD-AZU-0047 +# provider: azure +# service: network +# severity: CRITICAL +# short_code: no-public-ingress +# recommended_action: Set a more restrictive cidr range +# input: +# selector: +# - type: cloud +# subtypes: +# - service: network +# provider: azure +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule +# good_examples: checks/cloud/azure/network/no_public_ingress.tf.go +# bad_examples: checks/cloud/azure/network/no_public_ingress.tf.go +package builtin.azure.network.azure0047 + +import rego.v1 + +deny contains res if { + some group in input.azure.network.securitygroups + some rule in group.rules + not rule.outbound.value + rule.allow.value + some addr in rule.sourceaddresses + cidr.is_public(addr.value) + cidr.count_addresses(addr.value) > 1 + res := result.new("Security group rule allows ingress from public internet.", addr) +} diff --git a/checks/cloud/azure/network/no_public_ingress_test.go b/checks/cloud/azure/network/no_public_ingress_test.go deleted file mode 100644 index dbbe0e0c..00000000 --- a/checks/cloud/azure/network/no_public_ingress_test.go +++ /dev/null @@ -1,125 +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 TestCheckNoPublicIngress(t *testing.T) { - tests := []struct { - name string - input network.Network - expected bool - }{ - { - name: "Security group inbound rule with asterisk wildcard source address", - input: network.Network{ - SecurityGroups: []network.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []network.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - SourceAddresses: []trivyTypes.StringValue{ - trivyTypes.String("*", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Security group inbound rule with lower case internet wildcard source address", - input: network.Network{ - SecurityGroups: []network.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []network.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - SourceAddresses: []trivyTypes.StringValue{ - trivyTypes.String("internet", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Security group inbound rule with upper case internet wildcard source address", - input: network.Network{ - SecurityGroups: []network.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []network.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - SourceAddresses: []trivyTypes.StringValue{ - trivyTypes.String("Internet", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Security group inbound rule with private source address", - input: network.Network{ - SecurityGroups: []network.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []network.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - SourceAddresses: []trivyTypes.StringValue{ - trivyTypes.String("10.0.0.0/16", 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 := CheckNoPublicIngress.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckNoPublicIngress.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/no_public_ingress_test.rego b/checks/cloud/azure/network/no_public_ingress_test.rego new file mode 100644 index 00000000..958c5ac0 --- /dev/null +++ b/checks/cloud/azure/network/no_public_ingress_test.rego @@ -0,0 +1,28 @@ +package builtin.azure.network.azure0047_test + +import rego.v1 + +import data.builtin.azure.network.azure0047 as check +import data.lib.test + +test_deny_inbound_rule_with_wildcard_source_address if { + inp := {"azure": {"network": {"securitygroups": [{"rules": [{ + "allow": {"value": true}, + "outbound": {"value": false}, + "sourceaddresses": [{"value": "*"}], + }]}]}}} + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_inbound_rule_with_private_source_address if { + inp := {"azure": {"network": {"securitygroups": [{"rules": [{ + "allow": {"value": true}, + "outbound": {"value": false}, + "sourceaddresses": [{"value": "10.0.0.0/16"}], + }]}]}}} + + res := check.deny with input as inp + count(res) == 0 +} diff --git a/checks/cloud/azure/network/ssh_blocked_from_internet.rego b/checks/cloud/azure/network/ssh_blocked_from_internet.rego new file mode 100644 index 00000000..6b9c8ae3 --- /dev/null +++ b/checks/cloud/azure/network/ssh_blocked_from_internet.rego @@ -0,0 +1,50 @@ +# METADATA +# title: SSH access should not be accessible from the Internet, should be blocked on port 22 +# description: | +# SSH access can be configured on either the network security group or in the network security group rule. +# SSH access should not be permitted from the internet (*, 0.0.0.0, /0, internet, any) +# scope: package +# schemas: +# - input: schema["cloud"] +# custom: +# id: AVD-AZU-0050 +# avd_id: AVD-AZU-0050 +# provider: azure +# service: network +# severity: CRITICAL +# short_code: ssh-blocked-from-internet +# recommended_action: Block port 22 access from the internet +# input: +# selector: +# - type: cloud +# subtypes: +# - service: network +# provider: azure +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/network_security_group#security_rule +# - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule#source_port_ranges +# good_examples: checks/cloud/azure/network/ssh_blocked_from_internet.tf.go +# bad_examples: checks/cloud/azure/network/ssh_blocked_from_internet.tf.go +package builtin.azure.network.azure0050 + +import rego.v1 + +deny contains res if { + some group in input.azure.network.securitygroups + some rule in group.rules + rule.allow.value + not rule.outbound.value + lower(rule.protocol.value) != "icmp" + some ports in rule.destinationports + port_range_includes(ports.start, ports.end, 22) + some ip in rule.sourceaddresses + cidr.is_public(ip.value) + cidr.count_addresses(ip.value) > 1 + res := result.new( + "Security group rule allows ingress to SSH port from multiple public internet addresses.", + ip, + ) +} + +port_range_includes(from, to, port) if from <= port <= to \ No newline at end of file diff --git a/checks/cloud/azure/network/ssh_blocked_from_internet_test.go b/checks/cloud/azure/network/ssh_blocked_from_internet_test.go deleted file mode 100644 index 65ee23ad..00000000 --- a/checks/cloud/azure/network/ssh_blocked_from_internet_test.go +++ /dev/null @@ -1,128 +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 TestCheckSshBlockedFromInternet(t *testing.T) { - tests := []struct { - name string - input network.Network - expected bool - }{ - { - name: "Security group rule allowing SSH access from the public internet", - input: network.Network{ - SecurityGroups: []network.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []network.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - DestinationPorts: []network.PortRange{ - { - Metadata: trivyTypes.NewTestMetadata(), - Start: trivyTypes.IntTest(22), - End: trivyTypes.IntTest(22), - }, - }, - SourceAddresses: []trivyTypes.StringValue{ - trivyTypes.String("*", trivyTypes.NewTestMetadata()), - }, - Protocol: trivyTypes.String("Tcp", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Security group rule allowing SSH only ICMP", - input: network.Network{ - SecurityGroups: []network.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []network.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - DestinationPorts: []network.PortRange{ - { - Metadata: trivyTypes.NewTestMetadata(), - Start: trivyTypes.IntTest(22), - End: trivyTypes.IntTest(22), - }, - }, - SourceAddresses: []trivyTypes.StringValue{ - trivyTypes.String("*", trivyTypes.NewTestMetadata()), - }, - Protocol: trivyTypes.String("Icmp", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Security group rule allowing SSH access from a specific address", - input: network.Network{ - SecurityGroups: []network.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []network.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - DestinationPorts: []network.PortRange{ - { - Metadata: trivyTypes.NewTestMetadata(), - Start: trivyTypes.IntTest(22), - End: trivyTypes.IntTest(22), - }, - }, - SourceAddresses: []trivyTypes.StringValue{ - trivyTypes.String("82.102.23.23", trivyTypes.NewTestMetadata()), - }, - Protocol: trivyTypes.String("Tcp", 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 := CheckSshBlockedFromInternet.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckSshBlockedFromInternet.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/ssh_blocked_from_internet_test.rego b/checks/cloud/azure/network/ssh_blocked_from_internet_test.rego new file mode 100644 index 00000000..3cb3b1ab --- /dev/null +++ b/checks/cloud/azure/network/ssh_blocked_from_internet_test.rego @@ -0,0 +1,54 @@ +package builtin.azure.network.azure0050_test + +import rego.v1 + +import data.builtin.azure.network.azure0050 as check +import data.lib.test + +test_deny_inbound_rule_allows_rdp_access_from_internet if { + inp := {"azure": {"network": {"securitygroups": [{"rules": [{ + "outbound": {"value": false}, + "allow": {"value": true}, + "protocol": {"value": "Tcp"}, + "sourceaddresses": [{"value": "*"}], + "destinationports": [{ + "start": 22, + "end": 22, + }], + }]}]}}} + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_inbound_rule_allow_rdp_access_from_specific_address if { + inp := {"azure": {"network": {"securitygroups": [{"rules": [{ + "outbound": {"value": false}, + "allow": {"value": true}, + "protocol": {"value": "Tcp"}, + "sourceaddresses": [{"value": "237.84.2.178"}], + "destinationports": [{ + "start": 22, + "end": 22, + }], + }]}]}}} + + res := check.deny with input as inp + res == set() +} + +test_allow_inbound_rule_allow_access_for_icmp if { + inp := {"azure": {"network": {"securitygroups": [{"rules": [{ + "outbound": {"value": false}, + "allow": {"value": true}, + "protocol": {"value": "Icmp"}, + "sourceaddresses": [{"value": "*"}], + "destinationports": [{ + "start": 22, + "end": 22, + }], + }]}]}}} + + res := check.deny with input as inp + res == set() +} \ No newline at end of file From 6163b2b11e73ac0da22fbefa6cda16d38a3f7e25 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Mon, 19 Aug 2024 20:22:21 +0600 Subject: [PATCH 3/7] test: add functional tests Signed-off-by: Nikita Pivkin --- test/rego/azure_monitor_test.go | 190 ++++++++++++++ test/rego/azure_network_test.go | 331 +++++++++++++++++++++++++ test/rego/azure_securitycenter_test.go | 89 +++++++ test/rego/azure_synapse_test.go | 37 +++ test/rego/rego_checks_test.go | 6 +- 5 files changed, 652 insertions(+), 1 deletion(-) create mode 100644 test/rego/azure_monitor_test.go create mode 100644 test/rego/azure_network_test.go create mode 100644 test/rego/azure_securitycenter_test.go create mode 100644 test/rego/azure_synapse_test.go diff --git a/test/rego/azure_monitor_test.go b/test/rego/azure_monitor_test.go new file mode 100644 index 00000000..f650a49b --- /dev/null +++ b/test/rego/azure_monitor_test.go @@ -0,0 +1,190 @@ +package test + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/monitor" + "github.com/aquasecurity/trivy/pkg/iac/state" + trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var azureMonitorTestCases = testCases{ + "AVD-AZU-0031": { + { + name: "Log retention policy disabled", + input: state.State{Azure: azure.Azure{Monitor: 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: state.State{Azure: azure.Azure{Monitor: 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: state.State{Azure: azure.Azure{Monitor: 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, + }, + }, + "AVD-AZU-0033": { + { + name: "Log profile captures only write activities", + input: state.State{Azure: azure.Azure{Monitor: 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: state.State{Azure: azure.Azure{Monitor: 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, + }, + }, + "AVD-AZU-0032": { + { + name: "Log profile captures only eastern US region", + input: state.State{Azure: azure.Azure{Monitor: 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: state.State{Azure: azure.Azure{Monitor: 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, + }, + }, +} diff --git a/test/rego/azure_network_test.go b/test/rego/azure_network_test.go new file mode 100644 index 00000000..85bb6aef --- /dev/null +++ b/test/rego/azure_network_test.go @@ -0,0 +1,331 @@ +package test + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/network" + "github.com/aquasecurity/trivy/pkg/iac/state" + trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var azureNetworkTestCases = testCases{ + "AVD-AZU-0048": { + { + name: "Security group inbound rule allowing RDP access from the Internet", + input: state.State{Azure: azure.Azure{Network: network.Network{ + SecurityGroups: []network.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Rules: []network.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + SourceAddresses: []trivyTypes.StringValue{ + trivyTypes.String("*", trivyTypes.NewTestMetadata()), + }, + SourcePorts: nil, + DestinationAddresses: nil, + DestinationPorts: []network.PortRange{ + { + Metadata: trivyTypes.NewTestMetadata(), + Start: 3310, + End: 3390, + }, + }, + Protocol: trivyTypes.String("Tcp", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Security group inbound rule allowing RDP access from a specific address", + input: state.State{Azure: azure.Azure{Network: network.Network{ + SecurityGroups: []network.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Rules: []network.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + DestinationPorts: []network.PortRange{ + { + Metadata: trivyTypes.NewTestMetadata(), + Start: 3310, + End: 3390, + }, + }, + SourceAddresses: []trivyTypes.StringValue{ + trivyTypes.String("4.53.160.75", trivyTypes.NewTestMetadata()), + }, + Protocol: trivyTypes.String("Tcp", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: false, + }, + { + name: "Security group inbound rule allowing only ICMP", + input: state.State{Azure: azure.Azure{Network: network.Network{ + SecurityGroups: []network.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Rules: []network.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + SourceAddresses: []trivyTypes.StringValue{ + trivyTypes.String("*", trivyTypes.NewTestMetadata()), + }, + SourcePorts: nil, + DestinationAddresses: nil, + DestinationPorts: []network.PortRange{ + { + Metadata: trivyTypes.NewTestMetadata(), + Start: 3310, + End: 3390, + }, + }, + Protocol: trivyTypes.String("Icmp", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AZU-0051": { + { + name: "Security group outbound rule with wildcard destination address", + input: state.State{Azure: azure.Azure{Network: network.Network{ + SecurityGroups: []network.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Rules: []network.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + Outbound: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + DestinationAddresses: []trivyTypes.StringValue{ + trivyTypes.String("*", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Security group outbound rule with private destination address", + input: state.State{Azure: azure.Azure{Network: network.Network{ + SecurityGroups: []network.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Rules: []network.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + Outbound: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + DestinationAddresses: []trivyTypes.StringValue{ + trivyTypes.String("10.0.0.0/16", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AZU-0047": { + { + name: "Security group inbound rule with wildcard source address", + input: state.State{Azure: azure.Azure{Network: network.Network{ + SecurityGroups: []network.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Rules: []network.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + SourceAddresses: []trivyTypes.StringValue{ + trivyTypes.String("*", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Security group inbound rule with private source address", + input: state.State{Azure: azure.Azure{Network: network.Network{ + SecurityGroups: []network.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Rules: []network.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + SourceAddresses: []trivyTypes.StringValue{ + trivyTypes.String("10.0.0.0/16", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AZU-0049": { + { + name: "Network watcher flow log retention policy disabled", + input: state.State{Azure: azure.Azure{Network: 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: state.State{Azure: azure.Azure{Network: 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: state.State{Azure: azure.Azure{Network: 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, + }, + }, + "AVD-AZU-0050": { + { + name: "Security group rule allowing SSH access from the public internet", + input: state.State{Azure: azure.Azure{Network: network.Network{ + SecurityGroups: []network.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Rules: []network.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + DestinationPorts: []network.PortRange{ + { + Metadata: trivyTypes.NewTestMetadata(), + Start: 22, + End: 22, + }, + }, + SourceAddresses: []trivyTypes.StringValue{ + trivyTypes.String("*", trivyTypes.NewTestMetadata()), + }, + Protocol: trivyTypes.String("Tcp", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Security group rule allowing SSH only ICMP", + input: state.State{Azure: azure.Azure{Network: network.Network{ + SecurityGroups: []network.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Rules: []network.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + DestinationPorts: []network.PortRange{ + { + Metadata: trivyTypes.NewTestMetadata(), + Start: 22, + End: 22, + }, + }, + SourceAddresses: []trivyTypes.StringValue{ + trivyTypes.String("*", trivyTypes.NewTestMetadata()), + }, + Protocol: trivyTypes.String("Icmp", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: false, + }, + { + name: "Security group rule allowing SSH access from a specific address", + input: state.State{Azure: azure.Azure{Network: network.Network{ + SecurityGroups: []network.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Rules: []network.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Allow: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + Outbound: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + DestinationPorts: []network.PortRange{ + { + Metadata: trivyTypes.NewTestMetadata(), + Start: 22, + End: 22, + }, + }, + SourceAddresses: []trivyTypes.StringValue{ + trivyTypes.String("82.102.23.23", trivyTypes.NewTestMetadata()), + }, + Protocol: trivyTypes.String("Tcp", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: false, + }, + }, +} diff --git a/test/rego/azure_securitycenter_test.go b/test/rego/azure_securitycenter_test.go new file mode 100644 index 00000000..277fb22a --- /dev/null +++ b/test/rego/azure_securitycenter_test.go @@ -0,0 +1,89 @@ +package test + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/securitycenter" + "github.com/aquasecurity/trivy/pkg/iac/state" + trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var azureSecurityCenterTestCases = testCases{ + "AVD-AZU-0044": { + { + name: "Security center alert nofifications disabled", + input: state.State{Azure: azure.Azure{SecurityCenter: securitycenter.SecurityCenter{ + Contacts: []securitycenter.Contact{ + { + Metadata: trivyTypes.NewTestMetadata(), + EnableAlertNotifications: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "Security center alert nofifications enabled", + input: state.State{Azure: azure.Azure{SecurityCenter: securitycenter.SecurityCenter{ + Contacts: []securitycenter.Contact{ + { + Metadata: trivyTypes.NewTestMetadata(), + EnableAlertNotifications: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AZU-0045": { + { + name: "Security center set with free subscription", + input: state.State{Azure: azure.Azure{SecurityCenter: 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: state.State{Azure: azure.Azure{SecurityCenter: securitycenter.SecurityCenter{ + Subscriptions: []securitycenter.SubscriptionPricing{ + { + Metadata: trivyTypes.NewTestMetadata(), + Tier: trivyTypes.String(securitycenter.TierStandard, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AZU-0046": { + { + name: "Contact's phone number missing", + input: state.State{Azure: azure.Azure{SecurityCenter: securitycenter.SecurityCenter{ + Contacts: []securitycenter.Contact{ + { + Metadata: trivyTypes.NewTestMetadata(), + Phone: trivyTypes.String("", trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "Contact's phone number provided", + input: state.State{Azure: azure.Azure{SecurityCenter: securitycenter.SecurityCenter{ + Contacts: []securitycenter.Contact{ + { + Metadata: trivyTypes.NewTestMetadata(), + Phone: trivyTypes.String("+1-555-555-5555", trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + }, +} diff --git a/test/rego/azure_synapse_test.go b/test/rego/azure_synapse_test.go new file mode 100644 index 00000000..9e50650b --- /dev/null +++ b/test/rego/azure_synapse_test.go @@ -0,0 +1,37 @@ +package test + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/azure" + "github.com/aquasecurity/trivy/pkg/iac/providers/azure/synapse" + "github.com/aquasecurity/trivy/pkg/iac/state" + trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +var azureSynapseTestCases = testCases{ + "AVD-AZU-0034": { + { + name: "Synapse workspace managed VN disabled", + input: state.State{Azure: azure.Azure{Synapse: synapse.Synapse{ + Workspaces: []synapse.Workspace{ + { + Metadata: trivyTypes.NewTestMetadata(), + EnableManagedVirtualNetwork: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "Synapse workspace managed VN enabled", + input: state.State{Azure: azure.Azure{Synapse: synapse.Synapse{ + Workspaces: []synapse.Workspace{ + { + Metadata: trivyTypes.NewTestMetadata(), + EnableManagedVirtualNetwork: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + }, +} diff --git a/test/rego/rego_checks_test.go b/test/rego/rego_checks_test.go index 3b5f9d34..1869b311 100644 --- a/test/rego/rego_checks_test.go +++ b/test/rego/rego_checks_test.go @@ -54,7 +54,11 @@ func TestRegoChecks(t *testing.T) { awsDocumentDBTestCases, awsDynamodbTestCases, awsS3TestCases, - + + azureMonitorTestCases, + azureNetworkTestCases, + azureSynapseTestCases, + azureSecurityCenterTestCases, azureDataFactoryTestCases, azureDataLakeTestCases, azureKeyVaultTestCases, From fcab3d7dcdf586ef0254b09d81e28e9570a90641 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 22 Aug 2024 12:27:01 +0600 Subject: [PATCH 4/7] chore: update docs Signed-off-by: Nikita Pivkin --- avd_docs/azure/network/AVD-AZU-0047/docs.md | 4 ++-- avd_docs/azure/network/AVD-AZU-0048/docs.md | 4 ++-- avd_docs/azure/network/AVD-AZU-0050/docs.md | 6 +++--- avd_docs/azure/network/AVD-AZU-0051/docs.md | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/avd_docs/azure/network/AVD-AZU-0047/docs.md b/avd_docs/azure/network/AVD-AZU-0047/docs.md index 41376117..420dcd15 100644 --- a/avd_docs/azure/network/AVD-AZU-0047/docs.md +++ b/avd_docs/azure/network/AVD-AZU-0047/docs.md @@ -1,10 +1,10 @@ Network security rules should not use very broad subnets. - Where possible, segments should be broken into smaller subnets. + ### Impact -The port is exposed for ingress from the internet + {{ remediationActions }} diff --git a/avd_docs/azure/network/AVD-AZU-0048/docs.md b/avd_docs/azure/network/AVD-AZU-0048/docs.md index eef047ce..d1268a1b 100644 --- a/avd_docs/azure/network/AVD-AZU-0048/docs.md +++ b/avd_docs/azure/network/AVD-AZU-0048/docs.md @@ -1,10 +1,10 @@ RDP access can be configured on either the network security group or in the network security group rule. - RDP access should not be permitted from the internet (*, 0.0.0.0, /0, internet, any). Consider using the Azure Bastion Service. + ### Impact -Anyone from the internet can potentially RDP onto an instance + {{ remediationActions }} diff --git a/avd_docs/azure/network/AVD-AZU-0050/docs.md b/avd_docs/azure/network/AVD-AZU-0050/docs.md index e42590c0..31fccd28 100644 --- a/avd_docs/azure/network/AVD-AZU-0050/docs.md +++ b/avd_docs/azure/network/AVD-AZU-0050/docs.md @@ -1,10 +1,10 @@ -SSH access can be configured on either the network security group or in the network security group rule. - +SSH access can be configured on either the network security group or in the network security group rule. SSH access should not be permitted from the internet (*, 0.0.0.0, /0, internet, any) + ### Impact -Its dangerous to allow SSH access from the internet + {{ remediationActions }} diff --git a/avd_docs/azure/network/AVD-AZU-0051/docs.md b/avd_docs/azure/network/AVD-AZU-0051/docs.md index b5834eb0..420dcd15 100644 --- a/avd_docs/azure/network/AVD-AZU-0051/docs.md +++ b/avd_docs/azure/network/AVD-AZU-0051/docs.md @@ -1,10 +1,10 @@ Network security rules should not use very broad subnets. - Where possible, segments should be broken into smaller subnets. + ### Impact -The port is exposed for egress to the internet + {{ remediationActions }} From a85591caeecbd893ee13ce35df999749000c6d06 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 22 Aug 2024 12:27:36 +0600 Subject: [PATCH 5/7] chore: fmt rego Signed-off-by: Nikita Pivkin --- checks/cloud/azure/network/ssh_blocked_from_internet.rego | 2 +- checks/cloud/azure/network/ssh_blocked_from_internet_test.rego | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/checks/cloud/azure/network/ssh_blocked_from_internet.rego b/checks/cloud/azure/network/ssh_blocked_from_internet.rego index 6b9c8ae3..a7d6fb4f 100644 --- a/checks/cloud/azure/network/ssh_blocked_from_internet.rego +++ b/checks/cloud/azure/network/ssh_blocked_from_internet.rego @@ -47,4 +47,4 @@ deny contains res if { ) } -port_range_includes(from, to, port) if from <= port <= to \ No newline at end of file +port_range_includes(from, to, port) if from <= port <= to diff --git a/checks/cloud/azure/network/ssh_blocked_from_internet_test.rego b/checks/cloud/azure/network/ssh_blocked_from_internet_test.rego index 3cb3b1ab..b5fa396c 100644 --- a/checks/cloud/azure/network/ssh_blocked_from_internet_test.rego +++ b/checks/cloud/azure/network/ssh_blocked_from_internet_test.rego @@ -51,4 +51,4 @@ test_allow_inbound_rule_allow_access_for_icmp if { res := check.deny with input as inp res == set() -} \ No newline at end of file +} From 794b51c7b9687d833b9011136965937882d55b4e Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 22 Aug 2024 12:33:24 +0600 Subject: [PATCH 6/7] chore: mark Go AVD-AZU-0050 as deprecated Signed-off-by: Nikita Pivkin --- checks/cloud/azure/network/ssh_blocked_from_internet.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/checks/cloud/azure/network/ssh_blocked_from_internet.go b/checks/cloud/azure/network/ssh_blocked_from_internet.go index 1f930009..377d9b29 100755 --- a/checks/cloud/azure/network/ssh_blocked_from_internet.go +++ b/checks/cloud/azure/network/ssh_blocked_from_internet.go @@ -29,7 +29,8 @@ SSH access should not be permitted from the internet (*, 0.0.0.0, /0, internet, Links: terraformSshBlockedFromInternetLinks, RemediationMarkdown: terraformSshBlockedFromInternetRemediationMarkdown, }, - Severity: severity.Critical, + Severity: severity.Critical, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, group := range s.Azure.Network.SecurityGroups { From 6a9fb512db65bf0428c6ffedfcdef2846cdd6d48 Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 22 Aug 2024 12:50:16 +0600 Subject: [PATCH 7/7] test: update tests Signed-off-by: Nikita Pivkin --- test/rego/azure_network_test.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/rego/azure_network_test.go b/test/rego/azure_network_test.go index 85bb6aef..3318f9f8 100644 --- a/test/rego/azure_network_test.go +++ b/test/rego/azure_network_test.go @@ -28,8 +28,8 @@ var azureNetworkTestCases = testCases{ DestinationPorts: []network.PortRange{ { Metadata: trivyTypes.NewTestMetadata(), - Start: 3310, - End: 3390, + Start: trivyTypes.IntTest(3310), + End: trivyTypes.IntTest(3390), }, }, Protocol: trivyTypes.String("Tcp", trivyTypes.NewTestMetadata()), @@ -54,8 +54,8 @@ var azureNetworkTestCases = testCases{ DestinationPorts: []network.PortRange{ { Metadata: trivyTypes.NewTestMetadata(), - Start: 3310, - End: 3390, + Start: trivyTypes.IntTest(3310), + End: trivyTypes.IntTest(3390), }, }, SourceAddresses: []trivyTypes.StringValue{ @@ -88,8 +88,8 @@ var azureNetworkTestCases = testCases{ DestinationPorts: []network.PortRange{ { Metadata: trivyTypes.NewTestMetadata(), - Start: 3310, - End: 3390, + Start: trivyTypes.IntTest(3310), + End: trivyTypes.IntTest(3390), }, }, Protocol: trivyTypes.String("Icmp", trivyTypes.NewTestMetadata()), @@ -254,8 +254,8 @@ var azureNetworkTestCases = testCases{ DestinationPorts: []network.PortRange{ { Metadata: trivyTypes.NewTestMetadata(), - Start: 22, - End: 22, + Start: trivyTypes.IntTest(22), + End: trivyTypes.IntTest(22), }, }, SourceAddresses: []trivyTypes.StringValue{ @@ -283,8 +283,8 @@ var azureNetworkTestCases = testCases{ DestinationPorts: []network.PortRange{ { Metadata: trivyTypes.NewTestMetadata(), - Start: 22, - End: 22, + Start: trivyTypes.IntTest(22), + End: trivyTypes.IntTest(22), }, }, SourceAddresses: []trivyTypes.StringValue{ @@ -312,8 +312,8 @@ var azureNetworkTestCases = testCases{ DestinationPorts: []network.PortRange{ { Metadata: trivyTypes.NewTestMetadata(), - Start: 22, - End: 22, + Start: trivyTypes.IntTest(22), + End: trivyTypes.IntTest(22), }, }, SourceAddresses: []trivyTypes.StringValue{