diff --git a/avd_docs/google/sql/AVD-GCP-0014/docs.md b/avd_docs/google/sql/AVD-GCP-0014/docs.md index 028480fb..a9687bf6 100644 --- a/avd_docs/google/sql/AVD-GCP-0014/docs.md +++ b/avd_docs/google/sql/AVD-GCP-0014/docs.md @@ -1,8 +1,9 @@ Temporary files are not logged by default. To log all temporary files, a value of `0` should set in the `log_temp_files` flag - as all files greater in size than the number of bytes set in this flag will be logged. + ### Impact -Use of temporary files will not be logged + {{ remediationActions }} diff --git a/avd_docs/google/sql/AVD-GCP-0015/docs.md b/avd_docs/google/sql/AVD-GCP-0015/docs.md index 1917b5ab..c593b3bb 100644 --- a/avd_docs/google/sql/AVD-GCP-0015/docs.md +++ b/avd_docs/google/sql/AVD-GCP-0015/docs.md @@ -1,8 +1,9 @@ In-transit data should be encrypted so that if traffic is intercepted data will not be exposed in plaintext to attackers. + ### Impact -Intercepted data can be read in transit + {{ remediationActions }} diff --git a/avd_docs/google/sql/AVD-GCP-0016/docs.md b/avd_docs/google/sql/AVD-GCP-0016/docs.md index 2ac6b745..5c51f7be 100644 --- a/avd_docs/google/sql/AVD-GCP-0016/docs.md +++ b/avd_docs/google/sql/AVD-GCP-0016/docs.md @@ -1,8 +1,9 @@ Logging connections provides useful diagnostic data such as session length, which can identify performance issues in an application and potential DoS vectors. + ### Impact -Insufficient diagnostic data. + {{ remediationActions }} diff --git a/avd_docs/google/sql/AVD-GCP-0017/docs.md b/avd_docs/google/sql/AVD-GCP-0017/docs.md index 48ed19d3..3cb1c2be 100644 --- a/avd_docs/google/sql/AVD-GCP-0017/docs.md +++ b/avd_docs/google/sql/AVD-GCP-0017/docs.md @@ -1,8 +1,9 @@ Database instances should be configured so that they are not available over the public internet, but to internal compute resources which access them. + ### Impact -Public exposure of sensitive data + {{ remediationActions }} diff --git a/avd_docs/google/sql/AVD-GCP-0018/docs.md b/avd_docs/google/sql/AVD-GCP-0018/docs.md index d1e21e31..c0799dcf 100644 --- a/avd_docs/google/sql/AVD-GCP-0018/docs.md +++ b/avd_docs/google/sql/AVD-GCP-0018/docs.md @@ -1,8 +1,9 @@ Setting the minimum log severity too high will cause errors not to be logged + ### Impact -Loss of error logging + {{ remediationActions }} diff --git a/avd_docs/google/sql/AVD-GCP-0019/docs.md b/avd_docs/google/sql/AVD-GCP-0019/docs.md index 4daa9a97..1ec126fe 100644 --- a/avd_docs/google/sql/AVD-GCP-0019/docs.md +++ b/avd_docs/google/sql/AVD-GCP-0019/docs.md @@ -1,8 +1,9 @@ Cross-database ownership chaining, also known as cross-database chaining, is a security feature of SQL Server that allows users of databases access to other databases besides the one they are currently using. + ### Impact -Unintended access to sensitive data + {{ remediationActions }} diff --git a/avd_docs/google/sql/AVD-GCP-0020/docs.md b/avd_docs/google/sql/AVD-GCP-0020/docs.md index 3ae6dd40..b26915df 100644 --- a/avd_docs/google/sql/AVD-GCP-0020/docs.md +++ b/avd_docs/google/sql/AVD-GCP-0020/docs.md @@ -1,8 +1,9 @@ Lock waits are often an indication of poor performance and often an indicator of a potential denial of service vulnerability, therefore occurrences should be logged for analysis. + ### Impact -Issues leading to denial of service may not be identified. + {{ remediationActions }} diff --git a/avd_docs/google/sql/AVD-GCP-0021/docs.md b/avd_docs/google/sql/AVD-GCP-0021/docs.md index 649e695e..395fc4b5 100644 --- a/avd_docs/google/sql/AVD-GCP-0021/docs.md +++ b/avd_docs/google/sql/AVD-GCP-0021/docs.md @@ -1,8 +1,9 @@ Logging of statements which could contain sensitive data is not advised, therefore this setting should preclude all statements from being logged. + ### Impact -Sensitive data could be exposed in the database logs. + {{ remediationActions }} diff --git a/avd_docs/google/sql/AVD-GCP-0022/docs.md b/avd_docs/google/sql/AVD-GCP-0022/docs.md index bf8acdea..ceb59bd1 100644 --- a/avd_docs/google/sql/AVD-GCP-0022/docs.md +++ b/avd_docs/google/sql/AVD-GCP-0022/docs.md @@ -1,8 +1,9 @@ Logging disconnections provides useful diagnostic data such as session length, which can identify performance issues in an application and potential DoS vectors. + ### Impact -Insufficient diagnostic data. + {{ remediationActions }} diff --git a/avd_docs/google/sql/AVD-GCP-0023/docs.md b/avd_docs/google/sql/AVD-GCP-0023/docs.md index 386e8587..6e89ba21 100644 --- a/avd_docs/google/sql/AVD-GCP-0023/docs.md +++ b/avd_docs/google/sql/AVD-GCP-0023/docs.md @@ -1,8 +1,9 @@ Users with ALTER permissions on users can grant access to a contained database without the knowledge of an administrator + ### Impact -Access can be granted without knowledge of the database administrator + {{ remediationActions }} diff --git a/avd_docs/google/sql/AVD-GCP-0024/docs.md b/avd_docs/google/sql/AVD-GCP-0024/docs.md index 22329e83..7a567db8 100644 --- a/avd_docs/google/sql/AVD-GCP-0024/docs.md +++ b/avd_docs/google/sql/AVD-GCP-0024/docs.md @@ -1,8 +1,9 @@ Automated backups are not enabled by default. Backups are an easy way to restore data in a corruption or data-loss scenario. + ### Impact -No recovery of lost or corrupted data + {{ remediationActions }} diff --git a/avd_docs/google/sql/AVD-GCP-0025/docs.md b/avd_docs/google/sql/AVD-GCP-0025/docs.md index 09ee3812..3e04ace8 100644 --- a/avd_docs/google/sql/AVD-GCP-0025/docs.md +++ b/avd_docs/google/sql/AVD-GCP-0025/docs.md @@ -1,8 +1,9 @@ Logging checkpoints provides useful diagnostic data, which can identify performance issues in an application and potential DoS vectors. + ### Impact -Insufficient diagnostic data. + {{ remediationActions }} diff --git a/avd_docs/google/sql/AVD-GCP-0026/docs.md b/avd_docs/google/sql/AVD-GCP-0026/docs.md index f3674d27..f525c240 100644 --- a/avd_docs/google/sql/AVD-GCP-0026/docs.md +++ b/avd_docs/google/sql/AVD-GCP-0026/docs.md @@ -1,8 +1,9 @@ Arbitrary files can be read from the system using LOAD_DATA unless this setting is disabled. + ### Impact -Arbitrary files read by attackers when combined with a SQL injection vulnerability. + {{ remediationActions }} diff --git a/avd_docs/google/storage/AVD-GCP-0001/docs.md b/avd_docs/google/storage/AVD-GCP-0001/docs.md index c1726932..14cdcb24 100644 --- a/avd_docs/google/storage/AVD-GCP-0001/docs.md +++ b/avd_docs/google/storage/AVD-GCP-0001/docs.md @@ -1,8 +1,9 @@ Using 'allUsers' or 'allAuthenticatedUsers' as members in an IAM member/binding causes data to be exposed outside of the organisation. + ### Impact -Public exposure of sensitive data. + {{ remediationActions }} diff --git a/avd_docs/google/storage/AVD-GCP-0002/docs.md b/avd_docs/google/storage/AVD-GCP-0002/docs.md index 6b9dfe86..ad15b9e5 100644 --- a/avd_docs/google/storage/AVD-GCP-0002/docs.md +++ b/avd_docs/google/storage/AVD-GCP-0002/docs.md @@ -1,8 +1,9 @@ When you enable uniform bucket-level access on a bucket, Access Control Lists (ACLs) are disabled, and only bucket-level Identity and Access Management (IAM) permissions grant access to that bucket and the objects it contains. You revoke all access granted by object ACLs and the ability to administrate permissions using bucket ACLs. + ### Impact -ACLs are difficult to manage and often lead to incorrect/unintended configurations. + {{ remediationActions }} diff --git a/avd_docs/google/storage/AVD-GCP-0066/docs.md b/avd_docs/google/storage/AVD-GCP-0066/docs.md index e6b79d1b..0c4a6df1 100644 --- a/avd_docs/google/storage/AVD-GCP-0066/docs.md +++ b/avd_docs/google/storage/AVD-GCP-0066/docs.md @@ -1,8 +1,9 @@ Using unmanaged keys makes rotation and general management difficult. + ### Impact -Using unmanaged keys does not allow for proper key management. + {{ remediationActions }} diff --git a/checks/cloud/google/sql/enable_backup.go b/checks/cloud/google/sql/enable_backup.go index 31ebb7df..663fca00 100755 --- a/checks/cloud/google/sql/enable_backup.go +++ b/checks/cloud/google/sql/enable_backup.go @@ -27,7 +27,8 @@ var CheckEnableBackup = rules.Register( Links: terraformEnableBackupLinks, RemediationMarkdown: terraformEnableBackupRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.Google.SQL.Instances { diff --git a/checks/cloud/google/sql/enable_backup.rego b/checks/cloud/google/sql/enable_backup.rego new file mode 100644 index 00000000..0990a113 --- /dev/null +++ b/checks/cloud/google/sql/enable_backup.rego @@ -0,0 +1,38 @@ +# METADATA +# title: Enable automated backups to recover from data-loss +# description: | +# Automated backups are not enabled by default. Backups are an easy way to restore data in a corruption or data-loss scenario. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://cloud.google.com/sql/docs/mysql/backup-recovery/backups +# custom: +# id: AVD-GCP-0024 +# avd_id: AVD-GCP-0024 +# provider: google +# service: sql +# severity: MEDIUM +# short_code: enable-backup +# recommended_action: Enable automated backups +# input: +# selector: +# - type: cloud +# subtypes: +# - service: sql +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance#settings.backup_configuration.enabled=true +# good_examples: checks/cloud/google/sql/enable_backup.tf.go +# bad_examples: checks/cloud/google/sql/enable_backup.tf.go +package builtin.google.sql.google0024 + +import rego.v1 + +deny contains res if { + some instance in input.google.sql.instances + instance.isreplica.value == false + instance.settings.backups.enabled.value == false + res := result.new("Database instance does not have backups enabled.", instance.settings.backups.enabled) +} diff --git a/checks/cloud/google/sql/enable_backup_test.go b/checks/cloud/google/sql/enable_backup_test.go deleted file mode 100644 index 62480548..00000000 --- a/checks/cloud/google/sql/enable_backup_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package sql - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableBackup(t *testing.T) { - tests := []struct { - name string - input sql.SQL - expected bool - }{ - { - name: "Database instance backups disabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - IsReplica: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Backups: sql.Backups{ - Metadata: trivyTypes.NewTestMetadata(), - Enabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Database instance backups enabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - IsReplica: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Backups: sql.Backups{ - Metadata: trivyTypes.NewTestMetadata(), - Enabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Read replica does not require backups", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - IsReplica: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Backups: sql.Backups{ - Metadata: trivyTypes.NewTestMetadata(), - Enabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Google.SQL = test.input - results := CheckEnableBackup.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableBackup.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/google/sql/enable_backup_test.rego b/checks/cloud/google/sql/enable_backup_test.rego new file mode 100644 index 00000000..037793ff --- /dev/null +++ b/checks/cloud/google/sql/enable_backup_test.rego @@ -0,0 +1,38 @@ +package builtin.google.sql.google0024_test + +import rego.v1 + +import data.builtin.google.sql.google0024 as check +import data.lib.test + +test_allow_backups_enabled if { + inp := build_input({ + "isreplica": {"value": false}, + "settings": {"backups": {"enabled": {"value": true}}}, + }) + + res := check.deny with input as inp + res == set() +} + +test_deny_backups_disabled if { + inp := build_input({ + "isreplica": {"value": false}, + "settings": {"backups": {"enabled": {"value": false}}}, + }) + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_backups_disabled_for_replica if { + inp := build_input({ + "isreplica": {"value": true}, + "settings": {"backups": {"enabled": {"value": false}}}, + }) + + res := check.deny with input as inp + res == set() +} + +build_input(instance) := {"google": {"sql": {"instances": [instance]}}} diff --git a/checks/cloud/google/sql/enable_pg_temp_file_logging.go b/checks/cloud/google/sql/enable_pg_temp_file_logging.go index 340d1365..a8bafa4d 100755 --- a/checks/cloud/google/sql/enable_pg_temp_file_logging.go +++ b/checks/cloud/google/sql/enable_pg_temp_file_logging.go @@ -28,7 +28,8 @@ var CheckEnablePgTempFileLogging = rules.Register( Links: terraformEnablePgTempFileLoggingLinks, RemediationMarkdown: terraformEnablePgTempFileLoggingRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.Google.SQL.Instances { diff --git a/checks/cloud/google/sql/enable_pg_temp_file_logging.rego b/checks/cloud/google/sql/enable_pg_temp_file_logging.rego new file mode 100644 index 00000000..749898ad --- /dev/null +++ b/checks/cloud/google/sql/enable_pg_temp_file_logging.rego @@ -0,0 +1,51 @@ +# METADATA +# title: Temporary file logging should be enabled for all temporary files. +# description: | +# Temporary files are not logged by default. To log all temporary files, a value of `0` should set in the `log_temp_files` flag - as all files greater in size than the number of bytes set in this flag will be logged. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://postgresqlco.nf/doc/en/param/log_temp_files/ +# custom: +# id: AVD-GCP-0014 +# avd_id: AVD-GCP-0014 +# provider: google +# service: sql +# severity: MEDIUM +# short_code: enable-pg-temp-file-logging +# recommended_action: Enable temporary file logging for all files +# input: +# selector: +# - type: cloud +# subtypes: +# - service: sql +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance +# good_examples: checks/cloud/google/sql/enable_pg_temp_file_logging.tf.go +# bad_examples: checks/cloud/google/sql/enable_pg_temp_file_logging.tf.go +package builtin.google.sql.google0014 + +import rego.v1 + +import data.lib.google + +deny contains res if { + some instance in input.google.sql.instances + google.is_postgres(instance) + msg := check_log_temp_file_size(instance.settings.flags.logtempfilesize) + msg != "" + res := result.new(msg, instance.settings.flags.logtempfilesize) +} + +check_log_temp_file_size(logtempfilesize) := msg if { + logtempfilesize.value < 0 + msg := "Database instance has temporary file logging disabled." +} + +check_log_temp_file_size(logtempfilesize) := msg if { + logtempfilesize.value > 0 + msg := "Database instance has temporary file logging disabled for files of certain sizes." +} diff --git a/checks/cloud/google/sql/enable_pg_temp_file_logging_test.go b/checks/cloud/google/sql/enable_pg_temp_file_logging_test.go deleted file mode 100644 index bdfa8d31..00000000 --- a/checks/cloud/google/sql/enable_pg_temp_file_logging_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package sql - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnablePgTempFileLogging(t *testing.T) { - tests := []struct { - name string - input sql.SQL - expected bool - }{ - { - name: "Instance temp files logging disabled for all files", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("POSTGRES_12", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LogTempFileSize: trivyTypes.Int(-1, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Instance temp files logging disabled for files smaller than 100KB", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("POSTGRES_12", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LogTempFileSize: trivyTypes.Int(100, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Instance temp files logging enabled for all files", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("POSTGRES_12", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LogTempFileSize: trivyTypes.Int(0, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Google.SQL = test.input - results := CheckEnablePgTempFileLogging.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnablePgTempFileLogging.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/google/sql/enable_pg_temp_file_logging_test.rego b/checks/cloud/google/sql/enable_pg_temp_file_logging_test.rego new file mode 100644 index 00000000..36e6fa94 --- /dev/null +++ b/checks/cloud/google/sql/enable_pg_temp_file_logging_test.rego @@ -0,0 +1,48 @@ +package builtin.google.sql.google0014_test + +import rego.v1 + +import data.builtin.google.sql.google0014 as check +import data.lib.test + +test_deny_temp_files_logging_disabled_for_all_files if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_12"}, + "settings": {"flags": {"logtempfilesize": {"value": -1}}}, + }) + + res := check.deny with input as inp + count(res) == 1 +} + +test_deny_temp_files_logging_disabled_for_files_smaller_than_100kb if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_12"}, + "settings": {"flags": {"logtempfilesize": {"value": 100}}}, + }) + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_temp_files_logging_enabled_for_all_files if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_12"}, + "settings": {"flags": {"logtempfilesize": {"value": 0}}}, + }) + + res := check.deny with input as inp + res == set() +} + +test_allow_temp_files_logging_disabled_for_all_files_for_non_postgres if { + inp := build_input({ + "databaseversion": {"value": "MYSQL_5_7"}, + "settings": {"flags": {"logtempfilesize": {"value": -1}}}, + }) + + res := check.deny with input as inp + res == set() +} + +build_input(instance) := {"google": {"sql": {"instances": [instance]}}} diff --git a/checks/cloud/google/sql/encrypt_in_transit_data.go b/checks/cloud/google/sql/encrypt_in_transit_data.go index 9be80b67..690554cc 100755 --- a/checks/cloud/google/sql/encrypt_in_transit_data.go +++ b/checks/cloud/google/sql/encrypt_in_transit_data.go @@ -27,7 +27,8 @@ var CheckEncryptInTransitData = rules.Register( Links: terraformEncryptInTransitDataLinks, RemediationMarkdown: terraformEncryptInTransitDataRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.Google.SQL.Instances { diff --git a/checks/cloud/google/sql/encrypt_in_transit_data.rego b/checks/cloud/google/sql/encrypt_in_transit_data.rego new file mode 100644 index 00000000..b68476a5 --- /dev/null +++ b/checks/cloud/google/sql/encrypt_in_transit_data.rego @@ -0,0 +1,40 @@ +# METADATA +# title: SSL connections to a SQL database instance should be enforced. +# description: | +# In-transit data should be encrypted so that if traffic is intercepted data will not be exposed in plaintext to attackers. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://cloud.google.com/sql/docs/mysql/configure-ssl-instance +# custom: +# id: AVD-GCP-0015 +# avd_id: AVD-GCP-0015 +# provider: google +# service: sql +# severity: HIGH +# short_code: encrypt-in-transit-data +# recommended_action: Enforce SSL for all connections +# input: +# selector: +# - type: cloud +# subtypes: +# - service: sql +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance +# good_examples: checks/cloud/google/sql/encrypt_in_transit_data.tf.go +# bad_examples: checks/cloud/google/sql/encrypt_in_transit_data.tf.go +package builtin.google.sql.google0015 + +import rego.v1 + +deny contains res if { + some instance in input.google.sql.instances + instance.settings.ipconfiguration.requiretls.value == false + res := result.new( + "Database instance does not require TLS for all connections.", + instance.settings.ipconfiguration.requiretls, + ) +} diff --git a/checks/cloud/google/sql/encrypt_in_transit_data_test.go b/checks/cloud/google/sql/encrypt_in_transit_data_test.go deleted file mode 100644 index 19705bdf..00000000 --- a/checks/cloud/google/sql/encrypt_in_transit_data_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package sql - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEncryptInTransitData(t *testing.T) { - tests := []struct { - name string - input sql.SQL - expected bool - }{ - { - name: "DB instance TLS not required", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - IPConfiguration: sql.IPConfiguration{ - Metadata: trivyTypes.NewTestMetadata(), - RequireTLS: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "DB instance TLS required", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - IPConfiguration: sql.IPConfiguration{ - Metadata: trivyTypes.NewTestMetadata(), - RequireTLS: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Google.SQL = test.input - results := CheckEncryptInTransitData.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEncryptInTransitData.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/google/sql/encrypt_in_transit_data_test.rego b/checks/cloud/google/sql/encrypt_in_transit_data_test.rego new file mode 100644 index 00000000..08a8c361 --- /dev/null +++ b/checks/cloud/google/sql/encrypt_in_transit_data_test.rego @@ -0,0 +1,21 @@ +package builtin.google.sql.google0015_test + +import rego.v1 + +import data.builtin.google.sql.google0015 as check +import data.lib.test + +test_allow_tls_required if { + inp := build_input({"requiretls": {"value": true}}) + res := check.deny with input as inp + res == set() +} + +test_deny_tls_not_required if { + inp := build_input({"requiretls": {"value": false}}) + + res := check.deny with input as inp + count(res) == 1 +} + +build_input(ipconfig) := {"google": {"sql": {"instances": [{"settings": {"ipconfiguration": ipconfig}}]}}} diff --git a/checks/cloud/google/sql/mysql_no_local_infile.go b/checks/cloud/google/sql/mysql_no_local_infile.go index cf455978..0a308214 100755 --- a/checks/cloud/google/sql/mysql_no_local_infile.go +++ b/checks/cloud/google/sql/mysql_no_local_infile.go @@ -28,7 +28,8 @@ var CheckMysqlNoLocalInfile = rules.Register( Links: terraformMysqlNoLocalInfileLinks, RemediationMarkdown: terraformMysqlNoLocalInfileRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.Google.SQL.Instances { diff --git a/checks/cloud/google/sql/mysql_no_local_infile.rego b/checks/cloud/google/sql/mysql_no_local_infile.rego new file mode 100644 index 00000000..7577ac47 --- /dev/null +++ b/checks/cloud/google/sql/mysql_no_local_infile.rego @@ -0,0 +1,41 @@ +# METADATA +# title: Disable local_infile setting in MySQL +# description: | +# Arbitrary files can be read from the system using LOAD_DATA unless this setting is disabled. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://dev.mysql.com/doc/refman/8.0/en/load-data-local-security.html +# custom: +# id: AVD-GCP-0026 +# avd_id: AVD-GCP-0026 +# provider: google +# service: sql +# severity: HIGH +# short_code: mysql-no-local-infile +# recommended_action: Disable the local infile setting +# input: +# selector: +# - type: cloud +# subtypes: +# - service: sql +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance +# - https://dev.mysql.com/doc/refman/8.0/en/load-data-local-security.html +# good_examples: checks/cloud/google/sql/mysql_no_local_infile.tf.go +# bad_examples: checks/cloud/google/sql/mysql_no_local_infile.tf.go +package builtin.google.sql.google0026 + +import rego.v1 + +import data.lib.google + +deny contains res if { + some instance in input.google.sql.instances + google.is_mysql(instance) + instance.settings.flags.localinfile.value == true + res := result.new("Database instance has local file read access enabled.", instance.settings.flags.localinfile) +} diff --git a/checks/cloud/google/sql/mysql_no_local_infile_test.go b/checks/cloud/google/sql/mysql_no_local_infile_test.go deleted file mode 100644 index b276f21c..00000000 --- a/checks/cloud/google/sql/mysql_no_local_infile_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package sql - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckMysqlNoLocalInfile(t *testing.T) { - tests := []struct { - name string - input sql.SQL - expected bool - }{ - { - name: "DB instance local file read access enabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("MYSQL_5_6", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LocalInFile: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "DB instance local file read access disabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("MYSQL_5_6", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LocalInFile: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Google.SQL = test.input - results := CheckMysqlNoLocalInfile.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckMysqlNoLocalInfile.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/google/sql/mysql_no_local_infile_test.rego b/checks/cloud/google/sql/mysql_no_local_infile_test.rego new file mode 100644 index 00000000..db710e9f --- /dev/null +++ b/checks/cloud/google/sql/mysql_no_local_infile_test.rego @@ -0,0 +1,38 @@ +package builtin.google.sql.google0026_test + +import rego.v1 + +import data.builtin.google.sql.google0026 as check +import data.lib.test + +test_deny_local_file_read_access_enabled if { + inp := build_input({ + "databaseversion": {"value": "MYSQL_5_7"}, + "settings": {"flags": {"localinfile": {"value": true}}}, + }) + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_local_file_read_access_disabled if { + inp := build_input({ + "databaseversion": {"value": "MYSQL_5_7"}, + "settings": {"flags": {"localinfile": {"value": false}}}, + }) + + res := check.deny with input as inp + res == set() +} + +test_allow_local_file_read_access_enabled_for_non_mysql if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_9_6"}, + "settings": {"flags": {"localinfile": {"value": true}}}, + }) + + res := check.deny with input as inp + res == set() +} + +build_input(instance) := {"google": {"sql": {"instances": [instance]}}} diff --git a/checks/cloud/google/sql/no_contained_db_auth.go b/checks/cloud/google/sql/no_contained_db_auth.go index 19f46201..d2fe9015 100755 --- a/checks/cloud/google/sql/no_contained_db_auth.go +++ b/checks/cloud/google/sql/no_contained_db_auth.go @@ -28,7 +28,8 @@ var CheckNoContainedDbAuth = rules.Register( Links: terraformNoContainedDbAuthLinks, RemediationMarkdown: terraformNoContainedDbAuthRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.Google.SQL.Instances { diff --git a/checks/cloud/google/sql/no_contained_db_auth.rego b/checks/cloud/google/sql/no_contained_db_auth.rego new file mode 100644 index 00000000..9cf538a4 --- /dev/null +++ b/checks/cloud/google/sql/no_contained_db_auth.rego @@ -0,0 +1,43 @@ +# METADATA +# title: Contained database authentication should be disabled +# description: | +# Users with ALTER permissions on users can grant access to a contained database without the knowledge of an administrator +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/contained-database-authentication-server-configuration-option?view=sql-server-ver15 +# custom: +# id: AVD-GCP-0023 +# avd_id: AVD-GCP-0023 +# provider: google +# service: sql +# severity: MEDIUM +# short_code: no-contained-db-auth +# recommended_action: Disable contained database authentication +# input: +# selector: +# - type: cloud +# subtypes: +# - service: sql +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance +# good_examples: checks/cloud/google/sql/no_contained_db_auth.tf.go +# bad_examples: checks/cloud/google/sql/no_contained_db_auth.tf.go +package builtin.google.sql.google0023 + +import rego.v1 + +import data.lib.google + +deny contains res if { + some instance in input.google.sql.instances + google.is_sql_server(instance) + instance.settings.flags.containeddatabaseauthentication.value == true + res := result.new( + "Database instance has contained database authentication enabled.", + instance.settings.flags.containeddatabaseauthentication, + ) +} diff --git a/checks/cloud/google/sql/no_contained_db_auth_test.go b/checks/cloud/google/sql/no_contained_db_auth_test.go deleted file mode 100644 index 0b6ca869..00000000 --- a/checks/cloud/google/sql/no_contained_db_auth_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package sql - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckNoContainedDbAuth(t *testing.T) { - tests := []struct { - name string - input sql.SQL - expected bool - }{ - { - name: "Instance contained database authentication enabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("SQLSERVER_2017_STANDARD", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - ContainedDatabaseAuthentication: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Instance contained database authentication disabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("SQLSERVER_2017_STANDARD", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - ContainedDatabaseAuthentication: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Google.SQL = test.input - results := CheckNoContainedDbAuth.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckNoContainedDbAuth.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/google/sql/no_contained_db_auth_test.rego b/checks/cloud/google/sql/no_contained_db_auth_test.rego new file mode 100644 index 00000000..1a7bd02b --- /dev/null +++ b/checks/cloud/google/sql/no_contained_db_auth_test.rego @@ -0,0 +1,36 @@ +package builtin.google.sql.google0023_test + +import rego.v1 + +import data.builtin.google.sql.google0023 as check +import data.lib.test + +test_allow_db_auth_disabled if { + inp := build_input({ + "databaseversion": {"value": "SQLSERVER_2017_STANDARD"}, + "settings": {"flags": {"containeddatabaseauthentication": {"value": false}}}, + }) + + check.deny with input as inp == set() +} + +test_deny_db_auth_enabled if { + inp := build_input({ + "databaseversion": {"value": "SQLSERVER_2017_STANDARD"}, + "settings": {"flags": {"containeddatabaseauthentication": {"value": true}}}, + }) + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_db_auth_enabled_for_non_sqlserver if { + inp := build_input({ + "databaseversion": {"value": "MYSQL_5_7"}, + "settings": {"flags": {"containeddatabaseauthentication": {"value": true}}}, + }) + + check.deny with input as inp == set() +} + +build_input(instance) := {"google": {"sql": {"instances": [instance]}}} diff --git a/checks/cloud/google/sql/no_cross_db_ownership_chaining.go b/checks/cloud/google/sql/no_cross_db_ownership_chaining.go index 1b605857..1f77cfb8 100755 --- a/checks/cloud/google/sql/no_cross_db_ownership_chaining.go +++ b/checks/cloud/google/sql/no_cross_db_ownership_chaining.go @@ -28,7 +28,8 @@ var CheckNoCrossDbOwnershipChaining = rules.Register( Links: terraformNoCrossDbOwnershipChainingLinks, RemediationMarkdown: terraformNoCrossDbOwnershipChainingRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.Google.SQL.Instances { diff --git a/checks/cloud/google/sql/no_cross_db_ownership_chaining.rego b/checks/cloud/google/sql/no_cross_db_ownership_chaining.rego new file mode 100644 index 00000000..1f8ae249 --- /dev/null +++ b/checks/cloud/google/sql/no_cross_db_ownership_chaining.rego @@ -0,0 +1,43 @@ +# METADATA +# title: Cross-database ownership chaining should be disabled +# description: | +# Cross-database ownership chaining, also known as cross-database chaining, is a security feature of SQL Server that allows users of databases access to other databases besides the one they are currently using. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/cross-db-ownership-chaining-server-configuration-option?view=sql-server-ver15 +# custom: +# id: AVD-GCP-0019 +# avd_id: AVD-GCP-0019 +# provider: google +# service: sql +# severity: MEDIUM +# short_code: no-cross-db-ownership-chaining +# recommended_action: Disable cross database ownership chaining +# input: +# selector: +# - type: cloud +# subtypes: +# - service: sql +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance +# good_examples: checks/cloud/google/sql/no_cross_db_ownership_chaining.tf.go +# bad_examples: checks/cloud/google/sql/no_cross_db_ownership_chaining.tf.go +package builtin.google.sql.google0019 + +import rego.v1 + +import data.lib.google + +deny contains res if { + some instance in input.google.sql.instances + google.is_sql_server(instance) + instance.settings.flags.crossdbownershipchaining.value == true + res := result.new( + "Database instance has cross database ownership chaining enabled.", + instance.settings.flags.crossdbownershipchaining, + ) +} diff --git a/checks/cloud/google/sql/no_cross_db_ownership_chaining_test.go b/checks/cloud/google/sql/no_cross_db_ownership_chaining_test.go deleted file mode 100644 index 54634558..00000000 --- a/checks/cloud/google/sql/no_cross_db_ownership_chaining_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package sql - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckNoCrossDbOwnershipChaining(t *testing.T) { - tests := []struct { - name string - input sql.SQL - expected bool - }{ - { - name: "Instance cross database ownership chaining enabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("SQLSERVER_2017_STANDARD", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - CrossDBOwnershipChaining: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Instance cross database ownership chaining disabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("SQLSERVER_2017_STANDARD", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - CrossDBOwnershipChaining: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Google.SQL = test.input - results := CheckNoCrossDbOwnershipChaining.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckNoCrossDbOwnershipChaining.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/google/sql/no_cross_db_ownership_chaining_test.rego b/checks/cloud/google/sql/no_cross_db_ownership_chaining_test.rego new file mode 100644 index 00000000..63596412 --- /dev/null +++ b/checks/cloud/google/sql/no_cross_db_ownership_chaining_test.rego @@ -0,0 +1,36 @@ +package builtin.google.sql.google0019_test + +import rego.v1 + +import data.builtin.google.sql.google0019 as check +import data.lib.test + +test_deny_cross_database_ownership_chaining_enabled if { + inp := build_input({ + "databaseversion": {"value": "SQLSERVER_2017_STANDARD"}, + "settings": {"flags": {"crossdbownershipchaining": {"value": true}}}, + }) + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_cross_database_ownership_chaining_disabled if { + inp := build_input({ + "databaseversion": {"value": "SQLSERVER_2017_STANDARD"}, + "settings": {"flags": {"crossdbownershipchaining": {"value": false}}}, + }) + + check.deny with input as inp == set() +} + +test_allow_cross_database_ownership_chaining_enabled_for_non_sql_servers if { + inp := build_input({ + "databaseversion": {"value": "SQLSERVER_2017_STANDARD"}, + "settings": {"flags": {"crossdbownershipchaining": {"value": true}}}, + }) + + check.deny with input as inp == set() +} + +build_input(instance) := {"google": {"sql": {"instances": [instance]}}} diff --git a/checks/cloud/google/sql/no_public_access.go b/checks/cloud/google/sql/no_public_access.go index 963ac82b..3c1ebe25 100755 --- a/checks/cloud/google/sql/no_public_access.go +++ b/checks/cloud/google/sql/no_public_access.go @@ -28,7 +28,8 @@ var CheckNoPublicAccess = rules.Register( Links: terraformNoPublicAccessLinks, RemediationMarkdown: terraformNoPublicAccessRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.Google.SQL.Instances { diff --git a/checks/cloud/google/sql/no_public_access.rego b/checks/cloud/google/sql/no_public_access.rego new file mode 100644 index 00000000..b6f6b898 --- /dev/null +++ b/checks/cloud/google/sql/no_public_access.rego @@ -0,0 +1,51 @@ +# METADATA +# title: Ensure that Cloud SQL Database Instances are not publicly exposed +# description: | +# Database instances should be configured so that they are not available over the public internet, but to internal compute resources which access them. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://www.cloudconformity.com/knowledge-base/gcp/CloudSQL/publicly-accessible-cloud-sql-instances.html +# custom: +# id: AVD-GCP-0017 +# avd_id: AVD-GCP-0017 +# provider: google +# service: sql +# severity: HIGH +# short_code: no-public-access +# recommended_action: Remove public access from database instances +# input: +# selector: +# - type: cloud +# subtypes: +# - service: sql +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance +# good_examples: checks/cloud/google/sql/no_public_access.tf.go +# bad_examples: checks/cloud/google/sql/no_public_access.tf.go +package builtin.google.sql.google0017 + +import rego.v1 + +deny contains res if { + some instance in input.google.sql.instances + instance.settings.ipconfiguration.enableipv4.value == true + res := result.new( + "Database instance is granted a public internet address.", + instance.settings.ipconfiguration.enableipv4, + ) +} + +# TODO: Add support for public CIDRs +# deny contains res if { +# some instance in input.google.sql.instances +# some network in instance.settings.ipconfiguration.authorizednetworks +# cidr.is_public(network.cidr.value) +# res := result.new( +# "Database instance allows access from the public internet.", +# network.cidr +# ) +# } diff --git a/checks/cloud/google/sql/no_public_access_test.rego b/checks/cloud/google/sql/no_public_access_test.rego new file mode 100644 index 00000000..9c9dde4e --- /dev/null +++ b/checks/cloud/google/sql/no_public_access_test.rego @@ -0,0 +1,21 @@ +package builtin.google.sql.google0017_test + +import rego.v1 + +import data.builtin.google.sql.google0017 as check +import data.lib.test + +test_deny_ipv4_enabled if { + inp := build_input({"settings": {"ipconfiguration": {"enableipv4": {"value": true}}}}) + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_ipv4_disabled if { + inp := build_input({"settings": {"ipconfiguration": {"enableipv4": {"value": false}}}}) + + check.deny with input as inp == set() +} + +build_input(instance) := {"google": {"sql": {"instances": [instance]}}} diff --git a/checks/cloud/google/sql/pg_log_checkpoints.go b/checks/cloud/google/sql/pg_log_checkpoints.go index 2d3e41c8..6d0fc9bb 100755 --- a/checks/cloud/google/sql/pg_log_checkpoints.go +++ b/checks/cloud/google/sql/pg_log_checkpoints.go @@ -28,7 +28,8 @@ var CheckPgLogCheckpoints = rules.Register( Links: terraformPgLogCheckpointsLinks, RemediationMarkdown: terraformPgLogCheckpointsRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.Google.SQL.Instances { diff --git a/checks/cloud/google/sql/pg_log_checkpoints.rego b/checks/cloud/google/sql/pg_log_checkpoints.rego new file mode 100644 index 00000000..35995338 --- /dev/null +++ b/checks/cloud/google/sql/pg_log_checkpoints.rego @@ -0,0 +1,43 @@ +# METADATA +# title: Ensure that logging of checkpoints is enabled. +# description: | +# Logging checkpoints provides useful diagnostic data, which can identify performance issues in an application and potential DoS vectors. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://www.postgresql.org/docs/13/runtime-config-logging.html#GUC-LOG-CHECKPOINTS +# custom: +# id: AVD-GCP-0025 +# avd_id: AVD-GCP-0025 +# provider: google +# service: sql +# severity: MEDIUM +# short_code: pg-log-checkpoints +# recommended_action: Enable checkpoints logging. +# input: +# selector: +# - type: cloud +# subtypes: +# - service: sql +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance +# good_examples: checks/cloud/google/sql/pg_log_checkpoints.tf.go +# bad_examples: checks/cloud/google/sql/pg_log_checkpoints.tf.go +package builtin.google.sql.google0025 + +import rego.v1 + +import data.lib.google + +deny contains res if { + some instance in input.google.sql.instances + google.is_postgres(instance) + instance.settings.flags.logcheckpoints.value == false + res := result.new( + "Database instance is not configured to log checkpoints.", + instance.settings.flags.logcheckpoints, + ) +} diff --git a/checks/cloud/google/sql/pg_log_checkpoints_test.go b/checks/cloud/google/sql/pg_log_checkpoints_test.go deleted file mode 100644 index 167ca621..00000000 --- a/checks/cloud/google/sql/pg_log_checkpoints_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package sql - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckPgLogCheckpoints(t *testing.T) { - tests := []struct { - name string - input sql.SQL - expected bool - }{ - { - name: "Instance checkpoint logging disabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("POSTGRES_12", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LogCheckpoints: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Instance checkpoint logging enabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("POSTGRES_12", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LogCheckpoints: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Google.SQL = test.input - results := CheckPgLogCheckpoints.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckPgLogCheckpoints.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/google/sql/pg_log_checkpoints_test.rego b/checks/cloud/google/sql/pg_log_checkpoints_test.rego new file mode 100644 index 00000000..fed4e5bb --- /dev/null +++ b/checks/cloud/google/sql/pg_log_checkpoints_test.rego @@ -0,0 +1,36 @@ +package builtin.google.sql.google0025_test + +import rego.v1 + +import data.builtin.google.sql.google0025 as check +import data.lib.test + +test_deny_log_checkpoints_disabled if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_9_6"}, + "settings": {"flags": {"logcheckpoints": {"value": false}}}, + }) + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_log_checkpoints_enabled if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_9_6"}, + "settings": {"flags": {"logcheckpoints": {"value": true}}}, + }) + + check.deny with input as inp == set() +} + +test_allow_log_checkpoints_disabled_for_non_postgres if { + inp := build_input({ + "databaseversion": {"value": "MYSQL_5_7"}, + "settings": {"flags": {"logcheckpoints": {"value": false}}}, + }) + + check.deny with input as inp == set() +} + +build_input(instance) := {"google": {"sql": {"instances": [instance]}}} diff --git a/checks/cloud/google/sql/pg_log_connections.go b/checks/cloud/google/sql/pg_log_connections.go index 7309d27f..cf958759 100755 --- a/checks/cloud/google/sql/pg_log_connections.go +++ b/checks/cloud/google/sql/pg_log_connections.go @@ -28,7 +28,8 @@ var CheckPgLogConnections = rules.Register( Links: terraformPgLogConnectionsLinks, RemediationMarkdown: terraformPgLogConnectionsRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.Google.SQL.Instances { diff --git a/checks/cloud/google/sql/pg_log_connections.rego b/checks/cloud/google/sql/pg_log_connections.rego new file mode 100644 index 00000000..b2e0d784 --- /dev/null +++ b/checks/cloud/google/sql/pg_log_connections.rego @@ -0,0 +1,43 @@ +# METADATA +# title: Ensure that logging of connections is enabled. +# description: | +# Logging connections provides useful diagnostic data such as session length, which can identify performance issues in an application and potential DoS vectors. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://www.postgresql.org/docs/13/runtime-config-logging.html#GUC-LOG-CONNECTIONS +# custom: +# id: AVD-GCP-0016 +# avd_id: AVD-GCP-0016 +# provider: google +# service: sql +# severity: MEDIUM +# short_code: pg-log-connections +# recommended_action: Enable connection logging. +# input: +# selector: +# - type: cloud +# subtypes: +# - service: sql +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance +# good_examples: checks/cloud/google/sql/pg_log_connections.tf.go +# bad_examples: checks/cloud/google/sql/pg_log_connections.tf.go +package builtin.google.sql.google0016 + +import rego.v1 + +import data.lib.google + +deny contains res if { + some instance in input.google.sql.instances + google.is_postgres(instance) + instance.settings.flags.logconnections.value == false + res := result.new( + "Database instance is not configured to log connections.", + instance.settings.flags.logconnections, + ) +} diff --git a/checks/cloud/google/sql/pg_log_connections_test.go b/checks/cloud/google/sql/pg_log_connections_test.go deleted file mode 100644 index 24ba0789..00000000 --- a/checks/cloud/google/sql/pg_log_connections_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package sql - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckPgLogConnections(t *testing.T) { - tests := []struct { - name string - input sql.SQL - expected bool - }{ - { - name: "Instance connections logging disabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("POSTGRES_12", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LogConnections: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Instance connections logging enabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("POSTGRES_12", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LogConnections: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Google.SQL = test.input - results := CheckPgLogConnections.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckPgLogConnections.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/google/sql/pg_log_connections_test.rego b/checks/cloud/google/sql/pg_log_connections_test.rego new file mode 100644 index 00000000..79682419 --- /dev/null +++ b/checks/cloud/google/sql/pg_log_connections_test.rego @@ -0,0 +1,36 @@ +package builtin.google.sql.google0016_test + +import rego.v1 + +import data.builtin.google.sql.google0016 as check +import data.lib.test + +test_deny_connections_logging_disabled if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_12"}, + "settings": {"flags": {"logconnections": {"value": false}}}, + }) + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_connections_logging_enabled if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_12"}, + "settings": {"flags": {"logconnections": {"value": true}}}, + }) + + check.deny with input as inp == set() +} + +test_allow_connections_logging_disabled_for_non_postgres if { + inp := build_input({ + "databaseversion": {"value": "MYSQL_8"}, + "settings": {"flags": {"logconnections": {"value": false}}}, + }) + + check.deny with input as inp == set() +} + +build_input(instance) := {"google": {"sql": {"instances": [instance]}}} diff --git a/checks/cloud/google/sql/pg_log_disconnections.go b/checks/cloud/google/sql/pg_log_disconnections.go index 0319be5a..be777691 100755 --- a/checks/cloud/google/sql/pg_log_disconnections.go +++ b/checks/cloud/google/sql/pg_log_disconnections.go @@ -28,7 +28,8 @@ var CheckPgLogDisconnections = rules.Register( Links: terraformPgLogDisconnectionsLinks, RemediationMarkdown: terraformPgLogDisconnectionsRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.Google.SQL.Instances { diff --git a/checks/cloud/google/sql/pg_log_disconnections.rego b/checks/cloud/google/sql/pg_log_disconnections.rego new file mode 100644 index 00000000..e270fffd --- /dev/null +++ b/checks/cloud/google/sql/pg_log_disconnections.rego @@ -0,0 +1,43 @@ +# METADATA +# title: Ensure that logging of disconnections is enabled. +# description: | +# Logging disconnections provides useful diagnostic data such as session length, which can identify performance issues in an application and potential DoS vectors. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://www.postgresql.org/docs/13/runtime-config-logging.html#GUC-LOG-DISCONNECTIONS +# custom: +# id: AVD-GCP-0022 +# avd_id: AVD-GCP-0022 +# provider: google +# service: sql +# severity: MEDIUM +# short_code: pg-log-disconnections +# recommended_action: Enable disconnection logging. +# input: +# selector: +# - type: cloud +# subtypes: +# - service: sql +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance +# good_examples: checks/cloud/google/sql/pg_log_disconnections.tf.go +# bad_examples: checks/cloud/google/sql/pg_log_disconnections.tf.go +package builtin.google.sql.google0022 + +import rego.v1 + +import data.lib.google + +deny contains res if { + some instance in input.google.sql.instances + google.is_postgres(instance) + instance.settings.flags.logdisconnections.value == false + res := result.new( + "Database instance is not configured to log disconnections.", + instance.settings.flags.logdisconnections, + ) +} diff --git a/checks/cloud/google/sql/pg_log_disconnections_test.go b/checks/cloud/google/sql/pg_log_disconnections_test.go deleted file mode 100644 index 9c2b8c10..00000000 --- a/checks/cloud/google/sql/pg_log_disconnections_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package sql - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckPgLogDisconnections(t *testing.T) { - tests := []struct { - name string - input sql.SQL - expected bool - }{ - { - name: "Instance disconnections logging disabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("POSTGRES_12", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LogDisconnections: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Instance disconnections logging enabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("POSTGRES_12", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LogDisconnections: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Google.SQL = test.input - results := CheckPgLogDisconnections.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckPgLogDisconnections.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/google/sql/pg_log_disconnections_test.rego b/checks/cloud/google/sql/pg_log_disconnections_test.rego new file mode 100644 index 00000000..f55476c0 --- /dev/null +++ b/checks/cloud/google/sql/pg_log_disconnections_test.rego @@ -0,0 +1,36 @@ +package builtin.google.sql.google0022_test + +import rego.v1 + +import data.builtin.google.sql.google0022 as check +import data.lib.test + +test_deny_disconnections_logging_disabled if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_12"}, + "settings": {"flags": {"logdisconnections": {"value": false}}}, + }) + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_disconnections_logging_enabled if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_12"}, + "settings": {"flags": {"logdisconnections": {"value": true}}}, + }) + + check.deny with input as inp == set() +} + +test_allow_disconnections_logging_disabled_for_non_postgres if { + inp := build_input({ + "databaseversion": {"value": "MYSQL_8"}, + "settings": {"flags": {"logdisconnections": {"value": false}}}, + }) + + check.deny with input as inp == set() +} + +build_input(instance) := {"google": {"sql": {"instances": [instance]}}} diff --git a/checks/cloud/google/sql/pg_log_errors.go b/checks/cloud/google/sql/pg_log_errors.go index 0adbf9fd..80c77488 100755 --- a/checks/cloud/google/sql/pg_log_errors.go +++ b/checks/cloud/google/sql/pg_log_errors.go @@ -29,7 +29,8 @@ var CheckPgLogErrors = rules.Register( Links: terraformPgLogErrorsLinks, RemediationMarkdown: terraformPgLogErrorsRemediationMarkdown, }, - Severity: severity.Low, + Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.Google.SQL.Instances { diff --git a/checks/cloud/google/sql/pg_log_errors.rego b/checks/cloud/google/sql/pg_log_errors.rego new file mode 100644 index 00000000..6c90117c --- /dev/null +++ b/checks/cloud/google/sql/pg_log_errors.rego @@ -0,0 +1,44 @@ +# METADATA +# title: Ensure that Postgres errors are logged +# description: | +# Setting the minimum log severity too high will cause errors not to be logged +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://postgresqlco.nf/doc/en/param/log_min_messages/ +# - https://www.postgresql.org/docs/13/runtime-config-logging.html#GUC-LOG-MIN-MESSAGES +# custom: +# id: AVD-GCP-0018 +# avd_id: AVD-GCP-0018 +# provider: google +# service: sql +# severity: LOW +# short_code: pg-log-errors +# recommended_action: Set the minimum log severity to at least ERROR +# input: +# selector: +# - type: cloud +# subtypes: +# - service: sql +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance +# good_examples: checks/cloud/google/sql/pg_log_errors.tf.go +# bad_examples: checks/cloud/google/sql/pg_log_errors.tf.go +package builtin.google.sql.google0018 + +import rego.v1 + +import data.lib.google + +deny contains res if { + some instance in input.google.sql.instances + google.is_postgres(instance) + instance.settings.flags.logminmessages.value in {"FATAL", "PANIC", "LOG"} + res := result.new( + "Database instance is not configured to log errors.", + instance.settings.flags.logminmessages, + ) +} diff --git a/checks/cloud/google/sql/pg_log_errors_test.go b/checks/cloud/google/sql/pg_log_errors_test.go deleted file mode 100644 index 1333a44f..00000000 --- a/checks/cloud/google/sql/pg_log_errors_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package sql - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckPgLogErrors(t *testing.T) { - tests := []struct { - name string - input sql.SQL - expected bool - }{ - { - name: "Instance minimum log severity set to PANIC", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("POSTGRES_12", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LogMinMessages: trivyTypes.String("PANIC", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Instance minimum log severity set to ERROR", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("POSTGRES_12", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LogMinMessages: trivyTypes.String("ERROR", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Google.SQL = test.input - results := CheckPgLogErrors.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckPgLogErrors.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/google/sql/pg_log_errors_test.rego b/checks/cloud/google/sql/pg_log_errors_test.rego new file mode 100644 index 00000000..6e4ea140 --- /dev/null +++ b/checks/cloud/google/sql/pg_log_errors_test.rego @@ -0,0 +1,36 @@ +package builtin.google.sql.google0018_test + +import rego.v1 + +import data.builtin.google.sql.google0018 as check +import data.lib.test + +test_deny_minimum_log_level_is_not_error if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_12"}, + "settings": {"flags": {"logminmessages": {"value": "PANIC"}}}, + }) + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_minimum_log_level_is_error if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_12"}, + "settings": {"flags": {"logminmessages": {"value": "ERROR"}}}, + }) + + check.deny with input as inp == set() +} + +test_allow_minimum_log_level_is_not_error_for_non_postgres if { + inp := build_input({ + "databaseversion": {"value": "MYSQL_5_7"}, + "settings": {"flags": {"logminmessages": {"value": "PANIC"}}}, + }) + + check.deny with input as inp == set() +} + +build_input(instance) := {"google": {"sql": {"instances": [instance]}}} diff --git a/checks/cloud/google/sql/pg_log_lock_waits.go b/checks/cloud/google/sql/pg_log_lock_waits.go index ab2a9004..16c840ed 100755 --- a/checks/cloud/google/sql/pg_log_lock_waits.go +++ b/checks/cloud/google/sql/pg_log_lock_waits.go @@ -28,7 +28,8 @@ var CheckPgLogLockWaits = rules.Register( Links: terraformPgLogLockWaitsLinks, RemediationMarkdown: terraformPgLogLockWaitsRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.Google.SQL.Instances { diff --git a/checks/cloud/google/sql/pg_log_lock_waits.rego b/checks/cloud/google/sql/pg_log_lock_waits.rego new file mode 100644 index 00000000..d9644413 --- /dev/null +++ b/checks/cloud/google/sql/pg_log_lock_waits.rego @@ -0,0 +1,43 @@ +# METADATA +# title: Ensure that logging of lock waits is enabled. +# description: | +# Lock waits are often an indication of poor performance and often an indicator of a potential denial of service vulnerability, therefore occurrences should be logged for analysis. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://www.postgresql.org/docs/13/runtime-config-logging.html#GUC-LOG-LOCK-WAITS +# custom: +# id: AVD-GCP-0020 +# avd_id: AVD-GCP-0020 +# provider: google +# service: sql +# severity: MEDIUM +# short_code: pg-log-lock-waits +# recommended_action: Enable lock wait logging. +# input: +# selector: +# - type: cloud +# subtypes: +# - service: sql +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance +# good_examples: checks/cloud/google/sql/pg_log_lock_waits.tf.go +# bad_examples: checks/cloud/google/sql/pg_log_lock_waits.tf.go +package builtin.google.sql.google0020 + +import rego.v1 + +import data.lib.google + +deny contains res if { + some instance in input.google.sql.instances + google.is_postgres(instance) + instance.settings.flags.loglockwaits.value == false + res := result.new( + "Database instance is not configured to log lock waits.", + instance.settings.flags.loglockwaits, + ) +} diff --git a/checks/cloud/google/sql/pg_log_lock_waits_test.go b/checks/cloud/google/sql/pg_log_lock_waits_test.go deleted file mode 100644 index 00193f3c..00000000 --- a/checks/cloud/google/sql/pg_log_lock_waits_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package sql - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckPgLogLockWaits(t *testing.T) { - tests := []struct { - name string - input sql.SQL - expected bool - }{ - { - name: "Instance lock waits logging disabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("POSTGRES_12", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LogLockWaits: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Instance lock waits logging enabled", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("POSTGRES_12", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LogLockWaits: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Google.SQL = test.input - results := CheckPgLogLockWaits.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckPgLogLockWaits.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/google/sql/pg_log_lock_waits_test.rego b/checks/cloud/google/sql/pg_log_lock_waits_test.rego new file mode 100644 index 00000000..468b5c8b --- /dev/null +++ b/checks/cloud/google/sql/pg_log_lock_waits_test.rego @@ -0,0 +1,36 @@ +package builtin.google.sql.google0020_test + +import rego.v1 + +import data.builtin.google.sql.google0020 as check +import data.lib.test + +test_deny_lock_waits_loggging_disabled if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_12"}, + "settings": {"flags": {"loglockwaits": {"value": false}}}, + }) + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_lock_waits_loggging_enabled if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_12"}, + "settings": {"flags": {"loglockwaits": {"value": true}}}, + }) + + check.deny with input as inp == set() +} + +test_allow_lock_waits_loggging_disabled_for_non_postgres if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_11"}, + "settings": {"flags": {"loglockwaits": {"value": false}}}, + }) + + check.deny with input as inp == set() +} + +build_input(instance) := {"google": {"sql": {"instances": [instance]}}} diff --git a/checks/cloud/google/sql/pg_no_min_statement_logging.go b/checks/cloud/google/sql/pg_no_min_statement_logging.go index 9a9b8e41..fdefd4a1 100755 --- a/checks/cloud/google/sql/pg_no_min_statement_logging.go +++ b/checks/cloud/google/sql/pg_no_min_statement_logging.go @@ -28,7 +28,8 @@ var CheckPgNoMinStatementLogging = rules.Register( Links: terraformPgNoMinStatementLoggingLinks, RemediationMarkdown: terraformPgNoMinStatementLoggingRemediationMarkdown, }, - Severity: severity.Low, + Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.Google.SQL.Instances { diff --git a/checks/cloud/google/sql/pg_no_min_statement_logging.rego b/checks/cloud/google/sql/pg_no_min_statement_logging.rego new file mode 100644 index 00000000..75970c11 --- /dev/null +++ b/checks/cloud/google/sql/pg_no_min_statement_logging.rego @@ -0,0 +1,43 @@ +# METADATA +# title: Ensure that logging of long statements is disabled. +# description: | +# Logging of statements which could contain sensitive data is not advised, therefore this setting should preclude all statements from being logged. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://www.postgresql.org/docs/13/runtime-config-logging.html#GUC-LOG-MIN-DURATION-STATEMENT +# custom: +# id: AVD-GCP-0021 +# avd_id: AVD-GCP-0021 +# provider: google +# service: sql +# severity: LOW +# short_code: pg-no-min-statement-logging +# recommended_action: Disable minimum duration statement logging completely +# input: +# selector: +# - type: cloud +# subtypes: +# - service: sql +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database_instance +# good_examples: checks/cloud/google/sql/pg_no_min_statement_logging.tf.go +# bad_examples: checks/cloud/google/sql/pg_no_min_statement_logging.tf.go +package builtin.google.sql.google0021 + +import rego.v1 + +import data.lib.google + +deny contains res if { + some instance in input.google.sql.instances + google.is_postgres(instance) + instance.settings.flags.logmindurationstatement.value != 1 + res := result.new( + "Database instance is configured to log statements.", + instance.settings.flags.logmindurationstatement, + ) +} diff --git a/checks/cloud/google/sql/pg_no_min_statement_logging_test.go b/checks/cloud/google/sql/pg_no_min_statement_logging_test.go deleted file mode 100644 index 0ba4ee11..00000000 --- a/checks/cloud/google/sql/pg_no_min_statement_logging_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package sql - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/google/sql" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckPgNoMinStatementLogging(t *testing.T) { - tests := []struct { - name string - input sql.SQL - expected bool - }{ - { - name: "Instance logging enabled for all statements", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("POSTGRES_12", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LogMinDurationStatement: trivyTypes.Int(0, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Instance logging disabled for all statements", - input: sql.SQL{ - Instances: []sql.DatabaseInstance{ - { - Metadata: trivyTypes.NewTestMetadata(), - DatabaseVersion: trivyTypes.String("POSTGRES_12", trivyTypes.NewTestMetadata()), - Settings: sql.Settings{ - Metadata: trivyTypes.NewTestMetadata(), - Flags: sql.Flags{ - Metadata: trivyTypes.NewTestMetadata(), - LogMinDurationStatement: trivyTypes.Int(-1, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Google.SQL = test.input - results := CheckPgNoMinStatementLogging.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckPgNoMinStatementLogging.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/google/sql/pg_no_min_statement_logging_test.rego b/checks/cloud/google/sql/pg_no_min_statement_logging_test.rego new file mode 100644 index 00000000..c40d6c6f --- /dev/null +++ b/checks/cloud/google/sql/pg_no_min_statement_logging_test.rego @@ -0,0 +1,36 @@ +package builtin.google.sql.google0021_test + +import rego.v1 + +import data.builtin.google.sql.google0021 as check +import data.lib.test + +test_deny_logging_enabled_for_all_statements if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_12"}, + "settings": {"flags": {"logmindurationstatement": {"value": true}}}, + }) + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_logging_disabled_for_all_statements if { + inp := build_input({ + "databaseversion": {"value": "POSTGRES_12"}, + "settings": {"flags": {"logmindurationstatement": {"value": false}}}, + }) + + check.deny with input as inp == set() +} + +test_allow_logging_enabled_for_all_statements_for_non_postgres if { + inp := build_input({ + "databaseversion": {"value": "MYSQL_8_0"}, + "settings": {"flags": {"logmindurationstatement": {"value": true}}}, + }) + + check.deny with input as inp == set() +} + +build_input(instance) := {"google": {"sql": {"instances": [instance]}}} diff --git a/checks/cloud/google/storage/bucket_encryption_customer_key.go b/checks/cloud/google/storage/bucket_encryption_customer_key.go index 99914df1..ab9807ab 100755 --- a/checks/cloud/google/storage/bucket_encryption_customer_key.go +++ b/checks/cloud/google/storage/bucket_encryption_customer_key.go @@ -27,7 +27,8 @@ var CheckBucketEncryptionCustomerKey = rules.Register( Links: terraformBucketEncryptionCustomerKeyLinks, RemediationMarkdown: terraformBucketEncryptionCustomerKeyRemediationMarkdown, }, - Severity: severity.Low, + Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, bucket := range s.Google.Storage.Buckets { diff --git a/checks/cloud/google/storage/bucket_encryption_customer_key.rego b/checks/cloud/google/storage/bucket_encryption_customer_key.rego new file mode 100644 index 00000000..0000d12f --- /dev/null +++ b/checks/cloud/google/storage/bucket_encryption_customer_key.rego @@ -0,0 +1,38 @@ +# METADATA +# title: Cloud Storage buckets should be encrypted with a customer-managed key. +# description: | +# Using unmanaged keys makes rotation and general management difficult. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://cloud.google.com/storage/docs/encryption/customer-managed-keys +# custom: +# id: AVD-GCP-0066 +# avd_id: AVD-GCP-0066 +# provider: google +# service: storage +# severity: LOW +# short_code: bucket-encryption-customer-key +# recommended_action: Encrypt Cloud Storage buckets using customer-managed keys. +# input: +# selector: +# - type: cloud +# subtypes: +# - service: storage +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket#encryption +# good_examples: checks/cloud/google/storage/bucket_encryption_customer_key.tf.go +# bad_examples: checks/cloud/google/storage/bucket_encryption_customer_key.tf.go +package builtin.google.storage.google0066 + +import rego.v1 + +deny contains res if { + some bucket in input.google.storage.buckets + bucket.__defsec_metadata.managed + bucket.encryption.defaultkmskeyname.value == "" + res := result.new("Storage bucket encryption does not use a customer-managed key.", bucket.encryption.defaultkmskeyname) +} diff --git a/checks/cloud/google/storage/bucket_encryption_customer_key_test.rego b/checks/cloud/google/storage/bucket_encryption_customer_key_test.rego new file mode 100644 index 00000000..f04ef71d --- /dev/null +++ b/checks/cloud/google/storage/bucket_encryption_customer_key_test.rego @@ -0,0 +1,26 @@ +package builtin.google.storage.google0066_test + +import rego.v1 + +import data.builtin.google.storage.google0066 as check +import data.lib.test + +test_allow_bucket_with_customer_key if { + inp := {"google": {"storage": {"buckets": [{ + "__defsec_metadata": {"managed": true}, + "encryption": {"defaultkmskeyname": {"value": "key"}}, + }]}}} + + res := check.deny with input as inp + res == set() +} + +test_deny_bucket_without_customer_key if { + inp := {"google": {"storage": {"buckets": [{ + "__defsec_metadata": {"managed": true}, + "encryption": {"defaultkmskeyname": {"value": ""}}, + }]}}} + + res := check.deny with input as inp + count(res) == 1 +} diff --git a/checks/cloud/google/storage/bucket_encryption_customer_test.go b/checks/cloud/google/storage/bucket_encryption_customer_test.go deleted file mode 100644 index e988f547..00000000 --- a/checks/cloud/google/storage/bucket_encryption_customer_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package storage - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/google/storage" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckBucketEncryptionCustomerKey(t *testing.T) { - tests := []struct { - name string - input storage.Storage - expected bool - }{ - { - name: "Storage bucket missing default kms key name", - input: storage.Storage{ - Buckets: []storage.Bucket{ - { - Metadata: trivyTypes.NewTestMetadata(), - Encryption: storage.BucketEncryption{ - Metadata: trivyTypes.NewTestMetadata(), - DefaultKMSKeyName: trivyTypes.String("", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "Storage bucket with default kms key name provided", - input: storage.Storage{ - Buckets: []storage.Bucket{ - { - Metadata: trivyTypes.NewTestMetadata(), - Encryption: storage.BucketEncryption{ - Metadata: trivyTypes.NewTestMetadata(), - DefaultKMSKeyName: trivyTypes.String("default-kms-key-name", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Google.Storage = test.input - results := CheckBucketEncryptionCustomerKey.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckBucketEncryptionCustomerKey.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/google/storage/enable_ubla.go b/checks/cloud/google/storage/enable_ubla.go index 1f0411d0..ea974af4 100755 --- a/checks/cloud/google/storage/enable_ubla.go +++ b/checks/cloud/google/storage/enable_ubla.go @@ -28,7 +28,8 @@ var CheckEnableUbla = rules.Register( Links: terraformEnableUblaLinks, RemediationMarkdown: terraformEnableUblaRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, bucket := range s.Google.Storage.Buckets { diff --git a/checks/cloud/google/storage/enable_ubla.rego b/checks/cloud/google/storage/enable_ubla.rego new file mode 100644 index 00000000..53e5dcdb --- /dev/null +++ b/checks/cloud/google/storage/enable_ubla.rego @@ -0,0 +1,39 @@ +# METADATA +# title: Ensure that Cloud Storage buckets have uniform bucket-level access enabled +# description: | +# When you enable uniform bucket-level access on a bucket, Access Control Lists (ACLs) are disabled, and only bucket-level Identity and Access Management (IAM) permissions grant access to that bucket and the objects it contains. You revoke all access granted by object ACLs and the ability to administrate permissions using bucket ACLs. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://cloud.google.com/storage/docs/uniform-bucket-level-access +# - https://jbrojbrojbro.medium.com/you-make-the-rules-with-authentication-controls-for-cloud-storage-53c32543747b +# custom: +# id: AVD-GCP-0002 +# avd_id: AVD-GCP-0002 +# provider: google +# service: storage +# severity: MEDIUM +# short_code: enable-ubla +# recommended_action: Enable uniform bucket level access to provide a uniform permissioning system. +# input: +# selector: +# - type: cloud +# subtypes: +# - service: storage +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket#uniform_bucket_level_access +# good_examples: checks/cloud/google/storage/enable_ubla.tf.go +# bad_examples: checks/cloud/google/storage/enable_ubla.tf.go +package builtin.google.storage.google0002 + +import rego.v1 + +deny contains res if { + some bucket in input.google.storage.buckets + bucket.__defsec_metadata.managed + bucket.enableuniformbucketlevelaccess.value == false + res := result.new("Bucket has uniform bucket level access disabled.", bucket.enableuniformbucketlevelaccess) +} diff --git a/checks/cloud/google/storage/enable_ubla_test.go b/checks/cloud/google/storage/enable_ubla_test.go deleted file mode 100644 index 31854d21..00000000 --- a/checks/cloud/google/storage/enable_ubla_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package storage - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/google/storage" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableUbla(t *testing.T) { - tests := []struct { - name string - input storage.Storage - expected bool - }{ - { - name: "Uniform bucket level access disabled", - input: storage.Storage{ - Buckets: []storage.Bucket{ - { - Metadata: trivyTypes.NewTestMetadata(), - EnableUniformBucketLevelAccess: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "Uniform bucket level access enabled", - input: storage.Storage{ - Buckets: []storage.Bucket{ - { - Metadata: trivyTypes.NewTestMetadata(), - EnableUniformBucketLevelAccess: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Google.Storage = test.input - results := CheckEnableUbla.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableUbla.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/google/storage/enable_ubla_test.rego b/checks/cloud/google/storage/enable_ubla_test.rego new file mode 100644 index 00000000..3428912f --- /dev/null +++ b/checks/cloud/google/storage/enable_ubla_test.rego @@ -0,0 +1,26 @@ +package builtin.google.storage.google0002_test + +import rego.v1 + +import data.builtin.google.storage.google0002 as check +import data.lib.test + +test_allow_uniform_bucket_level_access_enabled if { + inp := {"google": {"storage": {"buckets": [{ + "__defsec_metadata": {"managed": true}, + "enableuniformbucketlevelaccess": {"value": true}, + }]}}} + + res := check.deny with input as inp + res == set() +} + +test_deny_uniform_bucket_level_access_disabled if { + inp := {"google": {"storage": {"buckets": [{ + "__defsec_metadata": {"managed": true}, + "enableuniformbucketlevelaccess": {"value": false}, + }]}}} + + res := check.deny with input as inp + count(res) == 1 +} diff --git a/checks/cloud/google/storage/no_public_access.go b/checks/cloud/google/storage/no_public_access.go index a0569bf9..ef92b14e 100755 --- a/checks/cloud/google/storage/no_public_access.go +++ b/checks/cloud/google/storage/no_public_access.go @@ -27,7 +27,8 @@ var CheckNoPublicAccess = rules.Register( Links: terraformNoPublicAccessLinks, RemediationMarkdown: terraformNoPublicAccessRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, bucket := range s.Google.Storage.Buckets { diff --git a/checks/cloud/google/storage/no_public_access.rego b/checks/cloud/google/storage/no_public_access.rego new file mode 100644 index 00000000..0b01b836 --- /dev/null +++ b/checks/cloud/google/storage/no_public_access.rego @@ -0,0 +1,49 @@ +# METADATA +# title: Ensure that Cloud Storage bucket is not anonymously or publicly accessible. +# description: | +# Using 'allUsers' or 'allAuthenticatedUsers' as members in an IAM member/binding causes data to be exposed outside of the organisation. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://jbrojbrojbro.medium.com/you-make-the-rules-with-authentication-controls-for-cloud-storage-53c32543747b +# custom: +# id: AVD-GCP-0001 +# avd_id: AVD-GCP-0001 +# provider: google +# service: storage +# severity: HIGH +# short_code: no-public-access +# recommended_action: Restrict public access to the bucket. +# input: +# selector: +# - type: cloud +# subtypes: +# - service: storage +# provider: google +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket_iam#member/members +# good_examples: checks/cloud/google/storage/no_public_access.tf.go +# bad_examples: checks/cloud/google/storage/no_public_access.tf.go +package builtin.google.storage.google0001 + +import rego.v1 + +deny contains res if { + some bucket in input.google.storage.buckets + bucket.__defsec_metadata.managed + some member in bucket.bindings[_].members + is_member_external(member.value) + res := result.new("Bucket allows public access.", member) +} + +deny contains res if { + some bucket in input.google.storage.buckets + bucket.__defsec_metadata.managed + some member in bucket.members + is_member_external(member.member.value) + res := result.new("Bucket allows public access.", member.member) +} + +is_member_external(member) := member in {"allUsers", "allAuthenticatedUsers"} diff --git a/checks/cloud/google/storage/no_public_access_test.go b/checks/cloud/google/storage/no_public_access_test.go deleted file mode 100644 index 22ec3820..00000000 --- a/checks/cloud/google/storage/no_public_access_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package storage - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/google/iam" - "github.com/aquasecurity/trivy/pkg/iac/providers/google/storage" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckNoPublicAccess(t *testing.T) { - tests := []struct { - name string - input storage.Storage - expected bool - }{ - { - name: "Members set to all authenticated users", - input: storage.Storage{ - Buckets: []storage.Bucket{ - { - Metadata: trivyTypes.NewTestMetadata(), - Bindings: []iam.Binding{ - { - Metadata: trivyTypes.NewTestMetadata(), - Members: []trivyTypes.StringValue{ - trivyTypes.String("allAuthenticatedUsers", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Members set to all users", - input: storage.Storage{ - Buckets: []storage.Bucket{ - { - Metadata: trivyTypes.NewTestMetadata(), - Members: []iam.Member{ - { - Metadata: trivyTypes.NewTestMetadata(), - Member: trivyTypes.String("allUsers", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Members set to specific users", - input: storage.Storage{ - Buckets: []storage.Bucket{ - { - Metadata: trivyTypes.NewTestMetadata(), - Bindings: []iam.Binding{ - { - Metadata: trivyTypes.NewTestMetadata(), - Members: []trivyTypes.StringValue{ - trivyTypes.String("user:jane@example.com", trivyTypes.NewTestMetadata()), - }, - }, - }, - Members: []iam.Member{ - { - Metadata: trivyTypes.NewTestMetadata(), - Member: trivyTypes.String("user:john@example.com", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.Google.Storage = test.input - results := CheckNoPublicAccess.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckNoPublicAccess.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/google/storage/no_public_access_test.rego b/checks/cloud/google/storage/no_public_access_test.rego new file mode 100644 index 00000000..c08a0186 --- /dev/null +++ b/checks/cloud/google/storage/no_public_access_test.rego @@ -0,0 +1,37 @@ +package builtin.google.storage.google0001_test + +import rego.v1 + +import data.builtin.google.storage.google0001 as check +import data.lib.test + +test_allow_bucket_does_not_allow_public_access if { + inp := build_input({ + "__defsec_metadata": {"managed": true}, + "bindings": [{"members": [{"value": "user:zKqzW@example.com"}]}], + }) + res := check.deny with input as inp + res == set() +} + +test_deny_bucket_allows_public_access_members if { + inp := build_input({ + "__defsec_metadata": {"managed": true}, + "bindings": [{"members": [{"value": "allAuthenticatedUsers"}]}], + }) + + res := check.deny with input as inp + count(res) == 1 +} + +test_deny_bucket_allows_public_access_bindings if { + inp := build_input({ + "__defsec_metadata": {"managed": true}, + "bindings": [{"members": [{"value": "allAuthenticatedUsers"}]}], + }) + + res := check.deny with input as inp + count(res) == 1 +} + +build_input(bucket) := {"google": {"storage": {"buckets": [bucket]}}} diff --git a/lib/google.rego b/lib/google.rego new file mode 100644 index 00000000..a9712623 --- /dev/null +++ b/lib/google.rego @@ -0,0 +1,24 @@ +# METADATA +# custom: +# library: true +package lib.google + +import rego.v1 + +sql_server_family := "SQLSERVER" + +postgres_family := "POSTGRES" + +mysql_family := "MYSQL" + +is_sql_server(instance) := is_database_family(instance, sql_server_family) + +is_postgres(instance) := is_database_family(instance, postgres_family) + +is_mysql(instance) := is_database_family(instance, mysql_family) + +is_database_family(instance, family) if { + parts := split(instance.databaseversion.value, "_") + count(parts) > 1 + parts[0] == family +}