From 0db327360c8c85008c90927a887482a97053769d Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Tue, 27 Aug 2024 04:46:54 +0600 Subject: [PATCH] refactor(checks): migrate AWS elasticache, elasticsearch, elb to Rego (#227) * refactor(checks): migrate AWS elasticache, elasticsearch, elb to Rego Signed-off-by: Nikita Pivkin * test: initialise tests in each test file Signed-off-by: Nikita Pivkin --------- Signed-off-by: Nikita Pivkin Co-authored-by: simar7 <1254783+simar7@users.noreply.github.com> --- avd_docs/aws/elasticache/AVD-AWS-0045/docs.md | 3 +- avd_docs/aws/elasticache/AVD-AWS-0049/docs.md | 4 +- avd_docs/aws/elasticache/AVD-AWS-0050/docs.md | 3 +- avd_docs/aws/elasticache/AVD-AWS-0051/docs.md | 3 +- .../aws/elasticsearch/AVD-AWS-0042/docs.md | 12 +- .../aws/elasticsearch/AVD-AWS-0043/docs.md | 3 +- .../aws/elasticsearch/AVD-AWS-0046/docs.md | 4 +- .../aws/elasticsearch/AVD-AWS-0048/docs.md | 3 +- .../aws/elasticsearch/AVD-AWS-0126/docs.md | 3 +- avd_docs/aws/elb/AVD-AWS-0047/docs.md | 3 +- avd_docs/aws/elb/AVD-AWS-0052/docs.md | 6 +- avd_docs/aws/elb/AVD-AWS-0053/docs.md | 3 +- avd_docs/aws/elb/AVD-AWS-0054/docs.md | 4 +- .../add_description_for_security_group.go | 3 +- .../add_description_for_security_group.rego | 41 +++ ...add_description_for_security_group_test.go | 65 ----- ...d_description_for_security_group_test.rego | 18 ++ .../elasticache/enable_at_rest_encryption.go | 3 +- .../enable_at_rest_encryption.rego | 37 +++ .../enable_at_rest_encryption_test.rego | 18 ++ .../elasticache/enable_backup_retention.go | 3 +- .../elasticache/enable_backup_retention.rego | 42 ++++ .../enable_backup_retention_test.go | 69 ----- .../enable_backup_retention_test.rego | 46 ++++ .../enable_in_transit_encryption.go | 3 +- .../enable_in_transit_encryption.rego | 40 +++ .../enable_in_transit_encryption_test.go | 65 ----- .../enable_in_transit_encryption_test.rego | 18 ++ .../elasticsearch/enable_domain_encryption.go | 3 +- .../enable_domain_encryption.rego | 43 ++++ .../enable_domain_encryption_test.go | 71 ------ .../enable_domain_encryption_test.rego | 18 ++ .../elasticsearch/enable_domain_logging.go | 3 +- .../elasticsearch/enable_domain_logging.rego | 43 ++++ .../enable_domain_logging_test.go | 71 ------ .../enable_domain_logging_test.rego | 18 ++ .../enable_in_transit_encryption.go | 3 +- .../enable_in_transit_encryption.rego | 40 +++ .../enable_in_transit_encryption_test.go | 71 ------ .../enable_in_transit_encryption_test.rego | 18 ++ .../cloud/aws/elasticsearch/enforce_https.go | 3 +- .../aws/elasticsearch/enforce_https.rego | 41 +++ .../aws/elasticsearch/enforce_https_test.go | 71 ------ .../aws/elasticsearch/enforce_https_test.rego | 18 ++ .../elasticsearch/use_secure_tls_policy.go | 3 +- .../elasticsearch/use_secure_tls_policy.rego | 40 +++ .../use_secure_tls_policy_test.go | 71 ------ .../use_secure_tls_policy_test.rego | 18 ++ checks/cloud/aws/elb/alb_not_public.go | 3 +- checks/cloud/aws/elb/alb_not_public.rego | 37 +++ checks/cloud/aws/elb/alb_not_public_test.go | 67 ----- checks/cloud/aws/elb/alb_not_public_test.rego | 33 +++ checks/cloud/aws/elb/drop_invalid_headers.go | 3 +- .../cloud/aws/elb/drop_invalid_headers.rego | 39 +++ .../aws/elb/drop_invalid_headers_test.go | 78 ------ .../aws/elb/drop_invalid_headers_test.rego | 33 +++ checks/cloud/aws/elb/http_not_used.go | 3 +- checks/cloud/aws/elb/http_not_used.rego | 51 ++++ checks/cloud/aws/elb/http_not_used_test.go | 141 ----------- checks/cloud/aws/elb/http_not_used_test.rego | 63 +++++ checks/cloud/aws/elb/use_secure_tls_policy.go | 3 +- .../cloud/aws/elb/use_secure_tls_policy.rego | 53 ++++ .../aws/elb/use_secure_tls_policy_test.go | 92 ------- .../aws/elb/use_secure_tls_policy_test.rego | 18 ++ test/rego/aws_elasticache_test.go | 123 +++++++++ test/rego/aws_elasticsearch_test.go | 175 +++++++++++++ test/rego/aws_elb_test.go | 238 ++++++++++++++++++ 67 files changed, 1476 insertions(+), 969 deletions(-) create mode 100644 checks/cloud/aws/elasticache/add_description_for_security_group.rego delete mode 100644 checks/cloud/aws/elasticache/add_description_for_security_group_test.go create mode 100644 checks/cloud/aws/elasticache/add_description_for_security_group_test.rego create mode 100644 checks/cloud/aws/elasticache/enable_at_rest_encryption.rego create mode 100644 checks/cloud/aws/elasticache/enable_at_rest_encryption_test.rego create mode 100644 checks/cloud/aws/elasticache/enable_backup_retention.rego delete mode 100644 checks/cloud/aws/elasticache/enable_backup_retention_test.go create mode 100644 checks/cloud/aws/elasticache/enable_backup_retention_test.rego create mode 100644 checks/cloud/aws/elasticache/enable_in_transit_encryption.rego delete mode 100644 checks/cloud/aws/elasticache/enable_in_transit_encryption_test.go create mode 100644 checks/cloud/aws/elasticache/enable_in_transit_encryption_test.rego create mode 100644 checks/cloud/aws/elasticsearch/enable_domain_encryption.rego delete mode 100644 checks/cloud/aws/elasticsearch/enable_domain_encryption_test.go create mode 100644 checks/cloud/aws/elasticsearch/enable_domain_encryption_test.rego create mode 100644 checks/cloud/aws/elasticsearch/enable_domain_logging.rego delete mode 100644 checks/cloud/aws/elasticsearch/enable_domain_logging_test.go create mode 100644 checks/cloud/aws/elasticsearch/enable_domain_logging_test.rego create mode 100644 checks/cloud/aws/elasticsearch/enable_in_transit_encryption.rego delete mode 100644 checks/cloud/aws/elasticsearch/enable_in_transit_encryption_test.go create mode 100644 checks/cloud/aws/elasticsearch/enable_in_transit_encryption_test.rego create mode 100644 checks/cloud/aws/elasticsearch/enforce_https.rego delete mode 100644 checks/cloud/aws/elasticsearch/enforce_https_test.go create mode 100644 checks/cloud/aws/elasticsearch/enforce_https_test.rego create mode 100644 checks/cloud/aws/elasticsearch/use_secure_tls_policy.rego delete mode 100644 checks/cloud/aws/elasticsearch/use_secure_tls_policy_test.go create mode 100644 checks/cloud/aws/elasticsearch/use_secure_tls_policy_test.rego create mode 100644 checks/cloud/aws/elb/alb_not_public.rego delete mode 100644 checks/cloud/aws/elb/alb_not_public_test.go create mode 100644 checks/cloud/aws/elb/alb_not_public_test.rego create mode 100644 checks/cloud/aws/elb/drop_invalid_headers.rego delete mode 100644 checks/cloud/aws/elb/drop_invalid_headers_test.go create mode 100644 checks/cloud/aws/elb/drop_invalid_headers_test.rego create mode 100644 checks/cloud/aws/elb/http_not_used.rego delete mode 100644 checks/cloud/aws/elb/http_not_used_test.go create mode 100644 checks/cloud/aws/elb/http_not_used_test.rego create mode 100644 checks/cloud/aws/elb/use_secure_tls_policy.rego delete mode 100644 checks/cloud/aws/elb/use_secure_tls_policy_test.go create mode 100644 checks/cloud/aws/elb/use_secure_tls_policy_test.rego create mode 100644 test/rego/aws_elasticache_test.go create mode 100644 test/rego/aws_elasticsearch_test.go create mode 100644 test/rego/aws_elb_test.go diff --git a/avd_docs/aws/elasticache/AVD-AWS-0045/docs.md b/avd_docs/aws/elasticache/AVD-AWS-0045/docs.md index fa4c7ad9..86e61c93 100644 --- a/avd_docs/aws/elasticache/AVD-AWS-0045/docs.md +++ b/avd_docs/aws/elasticache/AVD-AWS-0045/docs.md @@ -1,8 +1,9 @@ Data stored within an Elasticache replication node should be encrypted to ensure sensitive data is kept private. + ### Impact -At-rest data in the Replication Group could be compromised if accessed. + {{ remediationActions }} diff --git a/avd_docs/aws/elasticache/AVD-AWS-0049/docs.md b/avd_docs/aws/elasticache/AVD-AWS-0049/docs.md index fc7e319e..4e1a1b02 100644 --- a/avd_docs/aws/elasticache/AVD-AWS-0049/docs.md +++ b/avd_docs/aws/elasticache/AVD-AWS-0049/docs.md @@ -1,10 +1,10 @@ Security groups and security group rules should include a description for auditing purposes. - Simplifies auditing, debugging, and managing security groups. + ### Impact -Descriptions provide context for the firewall rule reasons + {{ remediationActions }} diff --git a/avd_docs/aws/elasticache/AVD-AWS-0050/docs.md b/avd_docs/aws/elasticache/AVD-AWS-0050/docs.md index 06f2494b..10ac81c6 100644 --- a/avd_docs/aws/elasticache/AVD-AWS-0050/docs.md +++ b/avd_docs/aws/elasticache/AVD-AWS-0050/docs.md @@ -1,8 +1,9 @@ Redis clusters should have a snapshot retention time to ensure that they are backed up and can be restored if required. + ### Impact -Without backups of the redis cluster recovery is made difficult + {{ remediationActions }} diff --git a/avd_docs/aws/elasticache/AVD-AWS-0051/docs.md b/avd_docs/aws/elasticache/AVD-AWS-0051/docs.md index 4c2394a4..959a33c8 100644 --- a/avd_docs/aws/elasticache/AVD-AWS-0051/docs.md +++ b/avd_docs/aws/elasticache/AVD-AWS-0051/docs.md @@ -1,8 +1,9 @@ Traffic flowing between Elasticache replication nodes should be encrypted to ensure sensitive data is kept private. + ### Impact -In transit data in the Replication Group could be read if intercepted + {{ remediationActions }} diff --git a/avd_docs/aws/elasticsearch/AVD-AWS-0042/docs.md b/avd_docs/aws/elasticsearch/AVD-AWS-0042/docs.md index fafceadb..2391211a 100644 --- a/avd_docs/aws/elasticsearch/AVD-AWS-0042/docs.md +++ b/avd_docs/aws/elasticsearch/AVD-AWS-0042/docs.md @@ -1,14 +1,12 @@ -Amazon ES exposes four Elasticsearch logs through Amazon CloudWatch Logs: error logs, search slow logs, index slow logs, and audit logs. - -Search slow logs, index slow logs, and error logs are useful for troubleshooting performance and stability issues. - -Audit logs track user activity for compliance purposes. - +Amazon ES exposes four Elasticsearch logs through Amazon CloudWatch Logs: error logs, search slow logs, index slow logs, and audit logs. +Search slow logs, index slow logs, and error logs are useful for troubleshooting performance and stability issues. +Audit logs track user activity for compliance purposes. All the logs are disabled by default. + ### Impact -Logging provides vital information about access and usage + {{ remediationActions }} diff --git a/avd_docs/aws/elasticsearch/AVD-AWS-0043/docs.md b/avd_docs/aws/elasticsearch/AVD-AWS-0043/docs.md index bd7481b3..a79b0142 100644 --- a/avd_docs/aws/elasticsearch/AVD-AWS-0043/docs.md +++ b/avd_docs/aws/elasticsearch/AVD-AWS-0043/docs.md @@ -1,8 +1,9 @@ Traffic flowing between Elasticsearch nodes should be encrypted to ensure sensitive data is kept private. + ### Impact -In transit data between nodes could be read if intercepted + {{ remediationActions }} diff --git a/avd_docs/aws/elasticsearch/AVD-AWS-0046/docs.md b/avd_docs/aws/elasticsearch/AVD-AWS-0046/docs.md index a34dc261..b202cbb4 100644 --- a/avd_docs/aws/elasticsearch/AVD-AWS-0046/docs.md +++ b/avd_docs/aws/elasticsearch/AVD-AWS-0046/docs.md @@ -1,10 +1,10 @@ Plain HTTP is unencrypted and human-readable. This means that if a malicious actor was to eavesdrop on your connection, they would be able to see all of your data flowing back and forth. - You should use HTTPS, which is HTTP over an encrypted (TLS) connection, meaning eavesdroppers cannot read your traffic. + ### Impact -HTTP traffic can be intercepted and the contents read + {{ remediationActions }} diff --git a/avd_docs/aws/elasticsearch/AVD-AWS-0048/docs.md b/avd_docs/aws/elasticsearch/AVD-AWS-0048/docs.md index 1543c1ee..2f52fd77 100644 --- a/avd_docs/aws/elasticsearch/AVD-AWS-0048/docs.md +++ b/avd_docs/aws/elasticsearch/AVD-AWS-0048/docs.md @@ -1,8 +1,9 @@ You should ensure your Elasticsearch data is encrypted at rest to help prevent sensitive information from being read by unauthorised users. + ### Impact -Data will be readable if compromised + {{ remediationActions }} diff --git a/avd_docs/aws/elasticsearch/AVD-AWS-0126/docs.md b/avd_docs/aws/elasticsearch/AVD-AWS-0126/docs.md index 93fda8d2..358f26f1 100644 --- a/avd_docs/aws/elasticsearch/AVD-AWS-0126/docs.md +++ b/avd_docs/aws/elasticsearch/AVD-AWS-0126/docs.md @@ -1,8 +1,9 @@ You should not use outdated/insecure TLS versions for encryption. You should be using TLS v1.2+. + ### Impact -Outdated SSL policies increase exposure to known vulnerabilities + {{ remediationActions }} diff --git a/avd_docs/aws/elb/AVD-AWS-0047/docs.md b/avd_docs/aws/elb/AVD-AWS-0047/docs.md index 0c162332..a2dd0979 100644 --- a/avd_docs/aws/elb/AVD-AWS-0047/docs.md +++ b/avd_docs/aws/elb/AVD-AWS-0047/docs.md @@ -1,8 +1,9 @@ You should not use outdated/insecure TLS versions for encryption. You should be using TLS v1.2+. + ### Impact -The SSL policy is outdated and has known vulnerabilities + {{ remediationActions }} diff --git a/avd_docs/aws/elb/AVD-AWS-0052/docs.md b/avd_docs/aws/elb/AVD-AWS-0052/docs.md index b89221e7..901f7438 100644 --- a/avd_docs/aws/elb/AVD-AWS-0052/docs.md +++ b/avd_docs/aws/elb/AVD-AWS-0052/docs.md @@ -1,10 +1,10 @@ -Passing unknown or invalid headers through to the target poses a potential risk of compromise. - +Passing unknown or invalid headers through to the target poses a potential risk of compromise. By setting drop_invalid_header_fields to true, anything that doe not conform to well known, defined headers will be removed by the load balancer. + ### Impact -Invalid headers being passed through to the target of the load balance may exploit vulnerabilities + {{ remediationActions }} diff --git a/avd_docs/aws/elb/AVD-AWS-0053/docs.md b/avd_docs/aws/elb/AVD-AWS-0053/docs.md index f298784f..4299258d 100644 --- a/avd_docs/aws/elb/AVD-AWS-0053/docs.md +++ b/avd_docs/aws/elb/AVD-AWS-0053/docs.md @@ -1,8 +1,9 @@ There are many scenarios in which you would want to expose a load balancer to the wider internet, but this check exists as a warning to prevent accidental exposure of internal assets. You should ensure that this resource should be exposed publicly. + ### Impact -The load balancer is exposed on the internet + {{ remediationActions }} diff --git a/avd_docs/aws/elb/AVD-AWS-0054/docs.md b/avd_docs/aws/elb/AVD-AWS-0054/docs.md index b5ec5a8b..69915da5 100644 --- a/avd_docs/aws/elb/AVD-AWS-0054/docs.md +++ b/avd_docs/aws/elb/AVD-AWS-0054/docs.md @@ -1,10 +1,10 @@ Plain HTTP is unencrypted and human-readable. This means that if a malicious actor was to eavesdrop on your connection, they would be able to see all of your data flowing back and forth. - You should use HTTPS, which is HTTP over an encrypted (TLS) connection, meaning eavesdroppers cannot read your traffic. + ### Impact -Your traffic is not protected + {{ remediationActions }} diff --git a/checks/cloud/aws/elasticache/add_description_for_security_group.go b/checks/cloud/aws/elasticache/add_description_for_security_group.go index afb000e1..1c791262 100755 --- a/checks/cloud/aws/elasticache/add_description_for_security_group.go +++ b/checks/cloud/aws/elasticache/add_description_for_security_group.go @@ -35,7 +35,8 @@ Simplifies auditing, debugging, and managing security groups.`, Links: cloudFormationAddDescriptionForSecurityGroupLinks, RemediationMarkdown: cloudFormationAddDescriptionForSecurityGroupRemediationMarkdown, }, - Severity: severity.Low, + Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, sg := range s.AWS.ElastiCache.SecurityGroups { diff --git a/checks/cloud/aws/elasticache/add_description_for_security_group.rego b/checks/cloud/aws/elasticache/add_description_for_security_group.rego new file mode 100644 index 00000000..778547c9 --- /dev/null +++ b/checks/cloud/aws/elasticache/add_description_for_security_group.rego @@ -0,0 +1,41 @@ +# METADATA +# title: Missing description for security group/security group rule. +# description: | +# Security groups and security group rules should include a description for auditing purposes. +# Simplifies auditing, debugging, and managing security groups. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/SecurityGroups.Creating.html +# custom: +# id: AVD-AWS-0049 +# avd_id: AVD-AWS-0049 +# provider: aws +# service: elasticache +# severity: LOW +# short_code: add-description-for-security-group +# recommended_action: Add descriptions for all security groups and rules +# input: +# selector: +# - type: cloud +# subtypes: +# - service: elasticache +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_security_group#description +# good_examples: checks/cloud/aws/elasticache/add_description_for_security_group.tf.go +# bad_examples: checks/cloud/aws/elasticache/add_description_for_security_group.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/elasticache/add_description_for_security_group.cf.go +# bad_examples: checks/cloud/aws/elasticache/add_description_for_security_group.cf.go +package builtin.aws.elasticache.aws0049 + +import rego.v1 + +deny contains res if { + some secgroup in input.aws.elasticache.securitygroups + secgroup.description.value == "" + res := result.new("Security group does not have a description.", secgroup.description) +} diff --git a/checks/cloud/aws/elasticache/add_description_for_security_group_test.go b/checks/cloud/aws/elasticache/add_description_for_security_group_test.go deleted file mode 100644 index 8fb3a81f..00000000 --- a/checks/cloud/aws/elasticache/add_description_for_security_group_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package elasticache - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticache" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckAddDescriptionForSecurityGroup(t *testing.T) { - tests := []struct { - name string - input elasticache.ElastiCache - expected bool - }{ - { - name: "ElastiCache security group with no description provided", - input: elasticache.ElastiCache{ - SecurityGroups: []elasticache.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Description: trivyTypes.String("", trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "ElastiCache security group with description", - input: elasticache.ElastiCache{ - SecurityGroups: []elasticache.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Description: trivyTypes.String("some decent description", trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.ElastiCache = test.input - results := CheckAddDescriptionForSecurityGroup.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckAddDescriptionForSecurityGroup.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/aws/elasticache/add_description_for_security_group_test.rego b/checks/cloud/aws/elasticache/add_description_for_security_group_test.rego new file mode 100644 index 00000000..cd6f7c54 --- /dev/null +++ b/checks/cloud/aws/elasticache/add_description_for_security_group_test.rego @@ -0,0 +1,18 @@ +package builtin.aws.elasticache.aws0049_test + +import rego.v1 + +import data.builtin.aws.elasticache.aws0049 as check +import data.lib.test + +test_allow_sg_with_description if { + inp := {"aws": {"elasticache": {"securitygroups": [{"description": {"value": "sg description"}}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_sg_without_description if { + inp := {"aws": {"elasticache": {"securitygroups": [{"description": {"value": ""}}]}}} + + test.assert_equal_message("Security group does not have a description.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/elasticache/enable_at_rest_encryption.go b/checks/cloud/aws/elasticache/enable_at_rest_encryption.go index cbd01634..206efde1 100755 --- a/checks/cloud/aws/elasticache/enable_at_rest_encryption.go +++ b/checks/cloud/aws/elasticache/enable_at_rest_encryption.go @@ -27,7 +27,8 @@ var CheckEnableAtRestEncryption = rules.Register( Links: terraformEnableAtRestEncryptionLinks, RemediationMarkdown: terraformEnableAtRestEncryptionRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, group := range s.AWS.ElastiCache.ReplicationGroups { diff --git a/checks/cloud/aws/elasticache/enable_at_rest_encryption.rego b/checks/cloud/aws/elasticache/enable_at_rest_encryption.rego new file mode 100644 index 00000000..e3d06bf1 --- /dev/null +++ b/checks/cloud/aws/elasticache/enable_at_rest_encryption.rego @@ -0,0 +1,37 @@ +# METADATA +# title: Elasticache Replication Group stores unencrypted data at-rest. +# description: | +# Data stored within an Elasticache replication node should be encrypted to ensure sensitive data is kept private. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/at-rest-encryption.html +# custom: +# id: AVD-AWS-0045 +# avd_id: AVD-AWS-0045 +# provider: aws +# service: elasticache +# severity: HIGH +# short_code: enable-at-rest-encryption +# recommended_action: Enable at-rest encryption for replication group +# input: +# selector: +# - type: cloud +# subtypes: +# - service: elasticache +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_replication_group#at_rest_encryption_enabled +# good_examples: checks/cloud/aws/elasticache/enable_at_rest_encryption.tf.go +# bad_examples: checks/cloud/aws/elasticache/enable_at_rest_encryption.tf.go +package builtin.aws.elasticache.aws0045 + +import rego.v1 + +deny contains res if { + some group in input.aws.elasticache.replicationgroups + group.atrestencryptionenabled.value == false + res := result.new("Replication group does not have at-rest encryption enabled.", group.atrestencryptionenabled) +} diff --git a/checks/cloud/aws/elasticache/enable_at_rest_encryption_test.rego b/checks/cloud/aws/elasticache/enable_at_rest_encryption_test.rego new file mode 100644 index 00000000..be5e83df --- /dev/null +++ b/checks/cloud/aws/elasticache/enable_at_rest_encryption_test.rego @@ -0,0 +1,18 @@ +package builtin.aws.elasticache.aws0045_test + +import rego.v1 + +import data.builtin.aws.elasticache.aws0045 as check +import data.lib.test + +test_allow_with_encryption_enabled if { + inp := {"aws": {"elasticache": {"replicationgroups": [{"atrestencryptionenabled": {"value": true}}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_with_encryption_disabled if { + inp := {"aws": {"elasticache": {"replicationgroups": [{"atrestencryptionenabled": {"value": false}}]}}} + + test.assert_equal_message("Replication group does not have at-rest encryption enabled.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/elasticache/enable_backup_retention.go b/checks/cloud/aws/elasticache/enable_backup_retention.go index 01de6fa2..af5711aa 100755 --- a/checks/cloud/aws/elasticache/enable_backup_retention.go +++ b/checks/cloud/aws/elasticache/enable_backup_retention.go @@ -33,7 +33,8 @@ var CheckEnableBackupRetention = rules.Register( Links: cloudFormationEnableBackupRetentionLinks, RemediationMarkdown: cloudFormationEnableBackupRetentionRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, cluster := range s.AWS.ElastiCache.Clusters { diff --git a/checks/cloud/aws/elasticache/enable_backup_retention.rego b/checks/cloud/aws/elasticache/enable_backup_retention.rego new file mode 100644 index 00000000..db93d623 --- /dev/null +++ b/checks/cloud/aws/elasticache/enable_backup_retention.rego @@ -0,0 +1,42 @@ +# METADATA +# title: Redis cluster should have backup retention turned on +# description: | +# Redis clusters should have a snapshot retention time to ensure that they are backed up and can be restored if required. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/backups-automatic.html +# custom: +# id: AVD-AWS-0050 +# avd_id: AVD-AWS-0050 +# provider: aws +# service: elasticache +# severity: MEDIUM +# short_code: enable-backup-retention +# recommended_action: Configure snapshot retention for redis cluster +# input: +# selector: +# - type: cloud +# subtypes: +# - service: elasticache +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_cluster#snapshot_retention_limit +# good_examples: checks/cloud/aws/elasticache/enable_backup_retention.tf.go +# bad_examples: checks/cloud/aws/elasticache/enable_backup_retention.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/elasticache/enable_backup_retention.cf.go +# bad_examples: checks/cloud/aws/elasticache/enable_backup_retention.cf.go +package builtin.aws.elasticache.aws0050 + +import rego.v1 + +deny contains res if { + some cluster in input.aws.elasticache.clusters + cluster.engine.value == "redis" + cluster.nodetype.value != "cache.t1.micro" + cluster.snapshotretentionlimit.value == 0 + res := result.new("Cluster snapshot retention is not enabled.", cluster.snapshotretentionlimit) +} diff --git a/checks/cloud/aws/elasticache/enable_backup_retention_test.go b/checks/cloud/aws/elasticache/enable_backup_retention_test.go deleted file mode 100644 index e1c486ea..00000000 --- a/checks/cloud/aws/elasticache/enable_backup_retention_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package elasticache - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticache" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableBackupRetention(t *testing.T) { - tests := []struct { - name string - input elasticache.ElastiCache - expected bool - }{ - { - name: "Cluster snapshot retention days set to 0", - input: elasticache.ElastiCache{ - Clusters: []elasticache.Cluster{ - { - Metadata: trivyTypes.NewTestMetadata(), - Engine: trivyTypes.String("redis", trivyTypes.NewTestMetadata()), - NodeType: trivyTypes.String("cache.m4.large", trivyTypes.NewTestMetadata()), - SnapshotRetentionLimit: trivyTypes.Int(0, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "Cluster snapshot retention days set to 5", - input: elasticache.ElastiCache{ - Clusters: []elasticache.Cluster{ - { - Metadata: trivyTypes.NewTestMetadata(), - Engine: trivyTypes.String("redis", trivyTypes.NewTestMetadata()), - NodeType: trivyTypes.String("cache.m4.large", trivyTypes.NewTestMetadata()), - SnapshotRetentionLimit: trivyTypes.Int(5, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.ElastiCache = test.input - results := CheckEnableBackupRetention.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableBackupRetention.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/aws/elasticache/enable_backup_retention_test.rego b/checks/cloud/aws/elasticache/enable_backup_retention_test.rego new file mode 100644 index 00000000..6ab67ab9 --- /dev/null +++ b/checks/cloud/aws/elasticache/enable_backup_retention_test.rego @@ -0,0 +1,46 @@ +package builtin.aws.elasticache.aws0050_test + +import rego.v1 + +import data.builtin.aws.elasticache.aws0050 as check +import data.lib.test + +test_allow_retention_limit_greater_than_zero if { + inp := {"aws": {"elasticache": {"clusters": [{ + "engine": {"value": "redis"}, + "nodetype": {"value": "cache.t3.micro"}, + "snapshotretentionlimit": {"value": 1}, + }]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_allow_retention_limit_zero_but_engine_is_not_redis if { + inp := {"aws": {"elasticache": {"clusters": [{ + "engine": {"value": "memcached"}, + "nodetype": {"value": "cache.t3.micro"}, + "snapshotretentionlimit": {"value": 0}, + }]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_allow_retention_limit_zero_but_nodetype_is_t1micro if { + inp := {"aws": {"elasticache": {"clusters": [{ + "engine": {"value": "redis"}, + "nodetype": {"value": "cache.t1.micro"}, + "snapshotretentionlimit": {"value": 0}, + }]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_retention_limit_zero if { + inp := {"aws": {"elasticache": {"clusters": [{ + "engine": {"value": "redis"}, + "nodetype": {"value": "cache.t3.micro"}, + "snapshotretentionlimit": {"value": 0}, + }]}}} + + test.assert_equal_message("Cluster snapshot retention is not enabled.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/elasticache/enable_in_transit_encryption.go b/checks/cloud/aws/elasticache/enable_in_transit_encryption.go index b5be9908..08a1165a 100755 --- a/checks/cloud/aws/elasticache/enable_in_transit_encryption.go +++ b/checks/cloud/aws/elasticache/enable_in_transit_encryption.go @@ -33,7 +33,8 @@ var CheckEnableInTransitEncryption = rules.Register( Links: cloudFormationEnableInTransitEncryptionLinks, RemediationMarkdown: cloudFormationEnableInTransitEncryptionRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, group := range s.AWS.ElastiCache.ReplicationGroups { diff --git a/checks/cloud/aws/elasticache/enable_in_transit_encryption.rego b/checks/cloud/aws/elasticache/enable_in_transit_encryption.rego new file mode 100644 index 00000000..71b80908 --- /dev/null +++ b/checks/cloud/aws/elasticache/enable_in_transit_encryption.rego @@ -0,0 +1,40 @@ +# METADATA +# title: Elasticache Replication Group uses unencrypted traffic. +# description: | +# Traffic flowing between Elasticache replication nodes should be encrypted to ensure sensitive data is kept private. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/in-transit-encryption.html +# custom: +# id: AVD-AWS-0051 +# avd_id: AVD-AWS-0051 +# provider: aws +# service: elasticache +# severity: HIGH +# short_code: enable-in-transit-encryption +# recommended_action: Enable in transit encryption for replication group +# input: +# selector: +# - type: cloud +# subtypes: +# - service: elasticache +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_replication_group#transit_encryption_enabled +# good_examples: checks/cloud/aws/elasticache/enable_in_transit_encryption.tf.go +# bad_examples: checks/cloud/aws/elasticache/enable_in_transit_encryption.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/elasticache/enable_in_transit_encryption.cf.go +# bad_examples: checks/cloud/aws/elasticache/enable_in_transit_encryption.cf.go +package builtin.aws.elasticache.aws0051 + +import rego.v1 + +deny contains res if { + some group in input.aws.elasticache.replicationgroups + group.transitencryptionenabled.value == false + res := result.new("Replication group does not have transit encryption enabled.", group.transitencryptionenabled) +} diff --git a/checks/cloud/aws/elasticache/enable_in_transit_encryption_test.go b/checks/cloud/aws/elasticache/enable_in_transit_encryption_test.go deleted file mode 100644 index 1a8ae0e9..00000000 --- a/checks/cloud/aws/elasticache/enable_in_transit_encryption_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package elasticache - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticache" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableInTransitEncryption(t *testing.T) { - tests := []struct { - name string - input elasticache.ElastiCache - expected bool - }{ - { - name: "ElastiCache replication group with in-transit encryption disabled", - input: elasticache.ElastiCache{ - ReplicationGroups: []elasticache.ReplicationGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - TransitEncryptionEnabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "ElastiCache replication group with in-transit encryption enabled", - input: elasticache.ElastiCache{ - ReplicationGroups: []elasticache.ReplicationGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - TransitEncryptionEnabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.ElastiCache = test.input - results := CheckEnableInTransitEncryption.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableInTransitEncryption.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/aws/elasticache/enable_in_transit_encryption_test.rego b/checks/cloud/aws/elasticache/enable_in_transit_encryption_test.rego new file mode 100644 index 00000000..045471c6 --- /dev/null +++ b/checks/cloud/aws/elasticache/enable_in_transit_encryption_test.rego @@ -0,0 +1,18 @@ +package builtin.aws.elasticache.aws0051_test + +import rego.v1 + +import data.builtin.aws.elasticache.aws0051 as check +import data.lib.test + +test_allow_encyption_enabled if { + inp := {"aws": {"elasticache": {"replicationgroups": [{"transitencryptionenabled": {"value": true}}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_encyption_disabled if { + inp := {"aws": {"elasticache": {"replicationgroups": [{"transitencryptionenabled": {"value": false}}]}}} + + test.assert_equal_message("Replication group does not have transit encryption enabled.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/elasticsearch/enable_domain_encryption.go b/checks/cloud/aws/elasticsearch/enable_domain_encryption.go index 434d31e1..d5045922 100755 --- a/checks/cloud/aws/elasticsearch/enable_domain_encryption.go +++ b/checks/cloud/aws/elasticsearch/enable_domain_encryption.go @@ -33,7 +33,8 @@ var CheckEnableDomainEncryption = rules.Register( Links: cloudFormationEnableDomainEncryptionLinks, RemediationMarkdown: cloudFormationEnableDomainEncryptionRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, domain := range s.AWS.Elasticsearch.Domains { diff --git a/checks/cloud/aws/elasticsearch/enable_domain_encryption.rego b/checks/cloud/aws/elasticsearch/enable_domain_encryption.rego new file mode 100644 index 00000000..154b895e --- /dev/null +++ b/checks/cloud/aws/elasticsearch/enable_domain_encryption.rego @@ -0,0 +1,43 @@ +# METADATA +# title: Elasticsearch domain isn't encrypted at rest. +# description: | +# You should ensure your Elasticsearch data is encrypted at rest to help prevent sensitive information from being read by unauthorised users. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/encryption-at-rest.html +# custom: +# id: AVD-AWS-0048 +# avd_id: AVD-AWS-0048 +# provider: aws +# service: elasticsearch +# severity: HIGH +# short_code: enable-domain-encryption +# recommended_action: Enable ElasticSearch domain encryption +# input: +# selector: +# - type: cloud +# subtypes: +# - service: elasticsearch +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticsearch_domain#encrypt_at_rest +# good_examples: checks/cloud/aws/elasticsearch/enable_domain_encryption.tf.go +# bad_examples: checks/cloud/aws/elasticsearch/enable_domain_encryption.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/elasticsearch/enable_domain_encryption.cf.go +# bad_examples: checks/cloud/aws/elasticsearch/enable_domain_encryption.cf.go +package builtin.aws.elasticsearch.aws0048 + +import rego.v1 + +deny contains res if { + some domain in input.aws.elasticsearch.domains + domain.atrestencryption.enabled.value == false + res := result.new( + "Domain does not have at-rest encryption enabled.", + domain.atrestencryption.enabled, + ) +} diff --git a/checks/cloud/aws/elasticsearch/enable_domain_encryption_test.go b/checks/cloud/aws/elasticsearch/enable_domain_encryption_test.go deleted file mode 100644 index 6a0fd35f..00000000 --- a/checks/cloud/aws/elasticsearch/enable_domain_encryption_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package elasticsearch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticsearch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableDomainEncryption(t *testing.T) { - tests := []struct { - name string - input elasticsearch.Elasticsearch - expected bool - }{ - { - name: "Elasticsearch domain with at-rest encryption disabled", - input: elasticsearch.Elasticsearch{ - Domains: []elasticsearch.Domain{ - { - Metadata: trivyTypes.NewTestMetadata(), - AtRestEncryption: elasticsearch.AtRestEncryption{ - Metadata: trivyTypes.NewTestMetadata(), - Enabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "Elasticsearch domain with at-rest encryption enabled", - input: elasticsearch.Elasticsearch{ - Domains: []elasticsearch.Domain{ - { - Metadata: trivyTypes.NewTestMetadata(), - AtRestEncryption: elasticsearch.AtRestEncryption{ - Metadata: trivyTypes.NewTestMetadata(), - Enabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.Elasticsearch = test.input - results := CheckEnableDomainEncryption.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableDomainEncryption.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/aws/elasticsearch/enable_domain_encryption_test.rego b/checks/cloud/aws/elasticsearch/enable_domain_encryption_test.rego new file mode 100644 index 00000000..c1fe6733 --- /dev/null +++ b/checks/cloud/aws/elasticsearch/enable_domain_encryption_test.rego @@ -0,0 +1,18 @@ +package builtin.aws.elasticsearch.aws0048_test + +import rego.v1 + +import data.builtin.aws.elasticsearch.aws0048 as check +import data.lib.test + +test_allow_encryption_enabled if { + inp := {"aws": {"elasticsearch": {"domains": [{"atrestencryption": {"enabled": {"value": true}}}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_encryption_disabled if { + inp := {"aws": {"elasticsearch": {"domains": [{"atrestencryption": {"enabled": {"value": false}}}]}}} + + test.assert_equal_message("Domain has at-rest encryption enabled.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/elasticsearch/enable_domain_logging.go b/checks/cloud/aws/elasticsearch/enable_domain_logging.go index 8b561ecf..36840433 100755 --- a/checks/cloud/aws/elasticsearch/enable_domain_logging.go +++ b/checks/cloud/aws/elasticsearch/enable_domain_logging.go @@ -39,7 +39,8 @@ All the logs are disabled by default.`, Links: cloudFormationEnableDomainLoggingLinks, RemediationMarkdown: cloudFormationEnableDomainLoggingRemediationMarkdown, }, - Severity: severity.Medium, + Severity: severity.Medium, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, domain := range s.AWS.Elasticsearch.Domains { diff --git a/checks/cloud/aws/elasticsearch/enable_domain_logging.rego b/checks/cloud/aws/elasticsearch/enable_domain_logging.rego new file mode 100644 index 00000000..b0146b50 --- /dev/null +++ b/checks/cloud/aws/elasticsearch/enable_domain_logging.rego @@ -0,0 +1,43 @@ +# METADATA +# title: Domain logging should be enabled for Elastic Search domains +# description: | +# Amazon ES exposes four Elasticsearch logs through Amazon CloudWatch Logs: error logs, search slow logs, index slow logs, and audit logs. +# Search slow logs, index slow logs, and error logs are useful for troubleshooting performance and stability issues. +# Audit logs track user activity for compliance purposes. +# All the logs are disabled by default. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-createdomain-configure-slow-logs.html +# custom: +# id: AVD-AWS-0042 +# avd_id: AVD-AWS-0042 +# provider: aws +# service: elasticsearch +# severity: MEDIUM +# short_code: enable-domain-logging +# recommended_action: Enable logging for ElasticSearch domains +# input: +# selector: +# - type: cloud +# subtypes: +# - service: elasticsearch +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticsearch_domain#log_type +# good_examples: checks/cloud/aws/elasticsearch/enable_domain_logging.tf.go +# bad_examples: checks/cloud/aws/elasticsearch/enable_domain_logging.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/elasticsearch/enable_domain_logging.cf.go +# bad_examples: checks/cloud/aws/elasticsearch/enable_domain_logging.cf.go +package builtin.aws.elasticsearch.aws0042 + +import rego.v1 + +deny contains res if { + some domain in input.aws.elasticsearch.domains + domain.logpublishing.auditenabled.value == false + res := result.new("Domain audit logging is not enabled.", domain.logpublishing.auditenabled) +} diff --git a/checks/cloud/aws/elasticsearch/enable_domain_logging_test.go b/checks/cloud/aws/elasticsearch/enable_domain_logging_test.go deleted file mode 100644 index 680fb8e6..00000000 --- a/checks/cloud/aws/elasticsearch/enable_domain_logging_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package elasticsearch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticsearch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableDomainLogging(t *testing.T) { - tests := []struct { - name string - input elasticsearch.Elasticsearch - expected bool - }{ - { - name: "Elasticsearch domain with audit logging disabled", - input: elasticsearch.Elasticsearch{ - Domains: []elasticsearch.Domain{ - { - Metadata: trivyTypes.NewTestMetadata(), - LogPublishing: elasticsearch.LogPublishing{ - Metadata: trivyTypes.NewTestMetadata(), - AuditEnabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "Elasticsearch domain with audit logging enabled", - input: elasticsearch.Elasticsearch{ - Domains: []elasticsearch.Domain{ - { - Metadata: trivyTypes.NewTestMetadata(), - LogPublishing: elasticsearch.LogPublishing{ - Metadata: trivyTypes.NewTestMetadata(), - AuditEnabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.Elasticsearch = test.input - results := CheckEnableDomainLogging.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableDomainLogging.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/aws/elasticsearch/enable_domain_logging_test.rego b/checks/cloud/aws/elasticsearch/enable_domain_logging_test.rego new file mode 100644 index 00000000..471a874a --- /dev/null +++ b/checks/cloud/aws/elasticsearch/enable_domain_logging_test.rego @@ -0,0 +1,18 @@ +package builtin.aws.elasticsearch.aws0042_test + +import rego.v1 + +import data.builtin.aws.elasticsearch.aws0042 as check +import data.lib.test + +test_allow_logging_enabled if { + inp := {"aws": {"elasticsearch": {"domains": [{"logpublishing": {"auditenabled": {"value": true}}}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_disallow_logging_disabled if { + inp := {"aws": {"elasticsearch": {"domains": [{"logpublishing": {"auditenabled": {"value": false}}}]}}} + + test.assert_equal_message("Domain audit logging is not enabled.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/elasticsearch/enable_in_transit_encryption.go b/checks/cloud/aws/elasticsearch/enable_in_transit_encryption.go index 88a2618f..f4d2c1fa 100755 --- a/checks/cloud/aws/elasticsearch/enable_in_transit_encryption.go +++ b/checks/cloud/aws/elasticsearch/enable_in_transit_encryption.go @@ -33,7 +33,8 @@ var CheckEnableInTransitEncryption = rules.Register( Links: cloudFormationEnableInTransitEncryptionLinks, RemediationMarkdown: cloudFormationEnableInTransitEncryptionRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, domain := range s.AWS.Elasticsearch.Domains { diff --git a/checks/cloud/aws/elasticsearch/enable_in_transit_encryption.rego b/checks/cloud/aws/elasticsearch/enable_in_transit_encryption.rego new file mode 100644 index 00000000..10aaff75 --- /dev/null +++ b/checks/cloud/aws/elasticsearch/enable_in_transit_encryption.rego @@ -0,0 +1,40 @@ +# METADATA +# title: Elasticsearch domain uses plaintext traffic for node to node communication. +# description: | +# Traffic flowing between Elasticsearch nodes should be encrypted to ensure sensitive data is kept private. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/ntn.html +# custom: +# id: AVD-AWS-0043 +# avd_id: AVD-AWS-0043 +# provider: aws +# service: elasticsearch +# severity: HIGH +# short_code: enable-in-transit-encryption +# recommended_action: Enable encrypted node to node communication +# input: +# selector: +# - type: cloud +# subtypes: +# - service: elasticsearch +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticsearch_domain#encrypt_at_rest +# good_examples: checks/cloud/aws/elasticsearch/enable_in_transit_encryption.tf.go +# bad_examples: checks/cloud/aws/elasticsearch/enable_in_transit_encryption.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/elasticsearch/enable_in_transit_encryption.cf.go +# bad_examples: checks/cloud/aws/elasticsearch/enable_in_transit_encryption.cf.go +package builtin.aws.elasticsearch.aws0043 + +import rego.v1 + +deny contains res if { + some domain in input.aws.elasticsearch.domains + domain.transitencryption.enabled.value == false + res := result.new("Domain does not have in-transit encryption enabled.", domain.transitencryption.enabled) +} diff --git a/checks/cloud/aws/elasticsearch/enable_in_transit_encryption_test.go b/checks/cloud/aws/elasticsearch/enable_in_transit_encryption_test.go deleted file mode 100644 index ecbf72c9..00000000 --- a/checks/cloud/aws/elasticsearch/enable_in_transit_encryption_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package elasticsearch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticsearch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableInTransitEncryption(t *testing.T) { - tests := []struct { - name string - input elasticsearch.Elasticsearch - expected bool - }{ - { - name: "Elasticsearch domain without in-transit encryption", - input: elasticsearch.Elasticsearch{ - Domains: []elasticsearch.Domain{ - { - Metadata: trivyTypes.NewTestMetadata(), - TransitEncryption: elasticsearch.TransitEncryption{ - Metadata: trivyTypes.NewTestMetadata(), - Enabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "Elasticsearch domain with in-transit encryption", - input: elasticsearch.Elasticsearch{ - Domains: []elasticsearch.Domain{ - { - Metadata: trivyTypes.NewTestMetadata(), - TransitEncryption: elasticsearch.TransitEncryption{ - Metadata: trivyTypes.NewTestMetadata(), - Enabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.Elasticsearch = test.input - results := CheckEnableInTransitEncryption.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableInTransitEncryption.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/aws/elasticsearch/enable_in_transit_encryption_test.rego b/checks/cloud/aws/elasticsearch/enable_in_transit_encryption_test.rego new file mode 100644 index 00000000..c58c3f07 --- /dev/null +++ b/checks/cloud/aws/elasticsearch/enable_in_transit_encryption_test.rego @@ -0,0 +1,18 @@ +package builtin.aws.elasticsearch.aws0043_test + +import rego.v1 + +import data.builtin.aws.elasticsearch.aws0043 as check +import data.lib.test + +test_allow_encryption_enabled if { + inp := {"aws": {"elasticsearch": {"domains": [{"transitencryption": {"enabled": {"value": true}}}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_encryption_disabled if { + inp := {"aws": {"elasticsearch": {"domains": [{"transitencryption": {"enabled": {"value": false}}}]}}} + + test.assert_equal_message("Domain does not have in-transit encryption enabled.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/elasticsearch/enforce_https.go b/checks/cloud/aws/elasticsearch/enforce_https.go index 8bc714ee..4b6b8cca 100755 --- a/checks/cloud/aws/elasticsearch/enforce_https.go +++ b/checks/cloud/aws/elasticsearch/enforce_https.go @@ -35,7 +35,8 @@ You should use HTTPS, which is HTTP over an encrypted (TLS) connection, meaning Links: cloudFormationEnforceHttpsLinks, RemediationMarkdown: cloudFormationEnforceHttpsRemediationMarkdown, }, - Severity: severity.Critical, + Severity: severity.Critical, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, domain := range s.AWS.Elasticsearch.Domains { diff --git a/checks/cloud/aws/elasticsearch/enforce_https.rego b/checks/cloud/aws/elasticsearch/enforce_https.rego new file mode 100644 index 00000000..eb7f6370 --- /dev/null +++ b/checks/cloud/aws/elasticsearch/enforce_https.rego @@ -0,0 +1,41 @@ +# METADATA +# title: Elasticsearch doesn't enforce HTTPS traffic. +# description: | +# Plain HTTP is unencrypted and human-readable. This means that if a malicious actor was to eavesdrop on your connection, they would be able to see all of your data flowing back and forth. +# You should use HTTPS, which is HTTP over an encrypted (TLS) connection, meaning eavesdroppers cannot read your traffic. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-data-protection.html +# custom: +# id: AVD-AWS-0046 +# avd_id: AVD-AWS-0046 +# provider: aws +# service: elasticsearch +# severity: CRITICAL +# short_code: enforce-https +# recommended_action: Enforce the use of HTTPS for ElasticSearch +# input: +# selector: +# - type: cloud +# subtypes: +# - service: elasticsearch +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticsearch_domain#enforce_https +# good_examples: checks/cloud/aws/elasticsearch/enforce_https.tf.go +# bad_examples: checks/cloud/aws/elasticsearch/enforce_https.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/elasticsearch/enforce_https.cf.go +# bad_examples: checks/cloud/aws/elasticsearch/enforce_https.cf.go +package builtin.aws.elasticsearch.aws0046 + +import rego.v1 + +deny contains res if { + some domain in input.aws.elasticsearch.domains + domain.endpoint.enforcehttps.value == false + res := result.new("Domain does not enforce HTTPS.", domain.endpoint.enforcehttps) +} diff --git a/checks/cloud/aws/elasticsearch/enforce_https_test.go b/checks/cloud/aws/elasticsearch/enforce_https_test.go deleted file mode 100644 index 06b6a91a..00000000 --- a/checks/cloud/aws/elasticsearch/enforce_https_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package elasticsearch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticsearch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnforceHttps(t *testing.T) { - tests := []struct { - name string - input elasticsearch.Elasticsearch - expected bool - }{ - { - name: "Elasticsearch domain with enforce HTTPS disabled", - input: elasticsearch.Elasticsearch{ - Domains: []elasticsearch.Domain{ - { - Metadata: trivyTypes.NewTestMetadata(), - Endpoint: elasticsearch.Endpoint{ - Metadata: trivyTypes.NewTestMetadata(), - EnforceHTTPS: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "Elasticsearch domain with enforce HTTPS enabled", - input: elasticsearch.Elasticsearch{ - Domains: []elasticsearch.Domain{ - { - Metadata: trivyTypes.NewTestMetadata(), - Endpoint: elasticsearch.Endpoint{ - Metadata: trivyTypes.NewTestMetadata(), - EnforceHTTPS: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.Elasticsearch = test.input - results := CheckEnforceHttps.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnforceHttps.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/aws/elasticsearch/enforce_https_test.rego b/checks/cloud/aws/elasticsearch/enforce_https_test.rego new file mode 100644 index 00000000..7246a860 --- /dev/null +++ b/checks/cloud/aws/elasticsearch/enforce_https_test.rego @@ -0,0 +1,18 @@ +package builtin.aws.elasticsearch.aws0046_test + +import rego.v1 + +import data.builtin.aws.elasticsearch.aws0046 as check +import data.lib.test + +test_allow_enforce_https if { + inp := {"aws": {"elasticsearch": {"domains": [{"endpoint": {"enforcehttps": {"value": true}}}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_does_not_enforce_https if { + inp := {"aws": {"elasticsearch": {"domains": [{"endpoint": {"enforcehttps": {"value": false}}}]}}} + + test.assert_equal_message("Domain does not enforce HTTPS.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/elasticsearch/use_secure_tls_policy.go b/checks/cloud/aws/elasticsearch/use_secure_tls_policy.go index eccef1d5..533c2b1c 100755 --- a/checks/cloud/aws/elasticsearch/use_secure_tls_policy.go +++ b/checks/cloud/aws/elasticsearch/use_secure_tls_policy.go @@ -33,7 +33,8 @@ var CheckUseSecureTlsPolicy = rules.Register( Links: cloudFormationUseSecureTlsPolicyLinks, RemediationMarkdown: cloudFormationUseSecureTlsPolicyRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, domain := range s.AWS.Elasticsearch.Domains { diff --git a/checks/cloud/aws/elasticsearch/use_secure_tls_policy.rego b/checks/cloud/aws/elasticsearch/use_secure_tls_policy.rego new file mode 100644 index 00000000..7f1c013e --- /dev/null +++ b/checks/cloud/aws/elasticsearch/use_secure_tls_policy.rego @@ -0,0 +1,40 @@ +# METADATA +# title: Elasticsearch domain endpoint is using outdated TLS policy. +# description: | +# You should not use outdated/insecure TLS versions for encryption. You should be using TLS v1.2+. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-data-protection.html +# custom: +# id: AVD-AWS-0126 +# avd_id: AVD-AWS-0126 +# provider: aws +# service: elasticsearch +# severity: HIGH +# short_code: use-secure-tls-policy +# recommended_action: Use the most modern TLS/SSL policies available +# input: +# selector: +# - type: cloud +# subtypes: +# - service: elasticsearch +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticsearch_domain#tls_security_policy +# good_examples: checks/cloud/aws/elasticsearch/use_secure_tls_policy.tf.go +# bad_examples: checks/cloud/aws/elasticsearch/use_secure_tls_policy.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/elasticsearch/use_secure_tls_policy.cf.go +# bad_examples: checks/cloud/aws/elasticsearch/use_secure_tls_policy.cf.go +package builtin.aws.elasticsearch.aws0126 + +import rego.v1 + +deny contains res if { + some domain in input.aws.elasticsearch.domains + domain.endpoint.tlspolicy.value != "Policy-Min-TLS-1-2-2019-07" + res := result.new("Domain does not have a secure TLS policy.", domain.endpoint.tlspolicy) +} diff --git a/checks/cloud/aws/elasticsearch/use_secure_tls_policy_test.go b/checks/cloud/aws/elasticsearch/use_secure_tls_policy_test.go deleted file mode 100644 index b1af6726..00000000 --- a/checks/cloud/aws/elasticsearch/use_secure_tls_policy_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package elasticsearch - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticsearch" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckUseSecureTlsPolicy(t *testing.T) { - tests := []struct { - name string - input elasticsearch.Elasticsearch - expected bool - }{ - { - name: "Elasticsearch domain with TLS v1.0", - input: elasticsearch.Elasticsearch{ - Domains: []elasticsearch.Domain{ - { - Metadata: trivyTypes.NewTestMetadata(), - Endpoint: elasticsearch.Endpoint{ - Metadata: trivyTypes.NewTestMetadata(), - TLSPolicy: trivyTypes.String("Policy-Min-TLS-1-0-2019-07", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "Elasticsearch domain with TLS v1.2", - input: elasticsearch.Elasticsearch{ - Domains: []elasticsearch.Domain{ - { - Metadata: trivyTypes.NewTestMetadata(), - Endpoint: elasticsearch.Endpoint{ - Metadata: trivyTypes.NewTestMetadata(), - TLSPolicy: trivyTypes.String("Policy-Min-TLS-1-2-2019-07", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.Elasticsearch = test.input - results := CheckUseSecureTlsPolicy.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckUseSecureTlsPolicy.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/aws/elasticsearch/use_secure_tls_policy_test.rego b/checks/cloud/aws/elasticsearch/use_secure_tls_policy_test.rego new file mode 100644 index 00000000..ff44dc96 --- /dev/null +++ b/checks/cloud/aws/elasticsearch/use_secure_tls_policy_test.rego @@ -0,0 +1,18 @@ +package builtin.aws.elasticsearch.aws0126_test + +import rego.v1 + +import data.builtin.aws.elasticsearch.aws0126 as check +import data.lib.test + +test_allow_use_secure_tls_policy if { + inp := {"aws": {"elasticsearch": {"domains": [{"endpoint": {"tlspolicy": {"value": "Policy-Min-TLS-1-2-2019-07"}}}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_does_not_use_secure_tls_policy if { + inp := {"aws": {"elasticsearch": {"domains": [{"endpoint": {"tlspolicy": {"value": "Policy-Min-TLS-1-0-2019-07"}}}]}}} + + test.assert_equal_message("Domain does not have a secure TLS policy.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/elb/alb_not_public.go b/checks/cloud/aws/elb/alb_not_public.go index ee137ffc..fa3dc15a 100755 --- a/checks/cloud/aws/elb/alb_not_public.go +++ b/checks/cloud/aws/elb/alb_not_public.go @@ -26,7 +26,8 @@ var CheckAlbNotPublic = rules.Register( Links: terraformAlbNotPublicLinks, RemediationMarkdown: terraformAlbNotPublicRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, lb := range s.AWS.ELB.LoadBalancers { diff --git a/checks/cloud/aws/elb/alb_not_public.rego b/checks/cloud/aws/elb/alb_not_public.rego new file mode 100644 index 00000000..75f35edb --- /dev/null +++ b/checks/cloud/aws/elb/alb_not_public.rego @@ -0,0 +1,37 @@ +# METADATA +# title: Load balancer is exposed to the internet. +# description: | +# There are many scenarios in which you would want to expose a load balancer to the wider internet, but this check exists as a warning to prevent accidental exposure of internal assets. You should ensure that this resource should be exposed publicly. +# scope: package +# schemas: +# - input: schema["cloud"] +# custom: +# id: AVD-AWS-0053 +# avd_id: AVD-AWS-0053 +# provider: aws +# service: elb +# severity: HIGH +# short_code: alb-not-public +# recommended_action: Switch to an internal load balancer or add a tfsec ignore +# input: +# selector: +# - type: cloud +# subtypes: +# - service: elb +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb +# good_examples: checks/cloud/aws/elb/alb_not_public.tf.go +# bad_examples: checks/cloud/aws/elb/alb_not_public.tf.go +package builtin.aws.elb.aws0053 + +import rego.v1 + +deny contains res if { + some lb in input.aws.elb.loadbalancers + lb.type.value != "gateway" + lb.internal.value == false + + res := result.new("Load balancer is exposed publicly.", lb.internal) +} diff --git a/checks/cloud/aws/elb/alb_not_public_test.go b/checks/cloud/aws/elb/alb_not_public_test.go deleted file mode 100644 index 0915e1cf..00000000 --- a/checks/cloud/aws/elb/alb_not_public_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package elb - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elb" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckAlbNotPublic(t *testing.T) { - tests := []struct { - name string - input elb.ELB - expected bool - }{ - { - name: "Load balancer publicly accessible", - input: elb.ELB{ - LoadBalancers: []elb.LoadBalancer{ - { - Metadata: trivyTypes.NewTestMetadata(), - Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), - Internal: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "Load balancer internally accessible", - input: elb.ELB{ - LoadBalancers: []elb.LoadBalancer{ - { - Metadata: trivyTypes.NewTestMetadata(), - Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), - Internal: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.ELB = test.input - results := CheckAlbNotPublic.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckAlbNotPublic.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/aws/elb/alb_not_public_test.rego b/checks/cloud/aws/elb/alb_not_public_test.rego new file mode 100644 index 00000000..9b7490f6 --- /dev/null +++ b/checks/cloud/aws/elb/alb_not_public_test.rego @@ -0,0 +1,33 @@ +package builtin.aws.elb.aws0053_test + +import rego.v1 + +import data.builtin.aws.elb.aws0053 as check +import data.lib.test + +test_deny_public_alb if { + inp := {"aws": {"elb": {"loadbalancers": [{ + "type": {"value": "application"}, + "internal": {"value": false}, + }]}}} + + test.assert_equal_message("Load balancer is exposed publicly.", check.deny) with input as inp +} + +test_allow_public_but_gateway if { + inp := {"aws": {"elb": {"loadbalancers": [{ + "type": {"value": "gateway"}, + "internal": {"value": false}, + }]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_allow_internal if { + inp := {"aws": {"elb": {"loadbalancers": [{ + "type": {"value": "application"}, + "internal": {"value": true}, + }]}}} + + test.assert_empty(check.deny) with input as inp +} diff --git a/checks/cloud/aws/elb/drop_invalid_headers.go b/checks/cloud/aws/elb/drop_invalid_headers.go index a06e0053..46329ea0 100755 --- a/checks/cloud/aws/elb/drop_invalid_headers.go +++ b/checks/cloud/aws/elb/drop_invalid_headers.go @@ -30,7 +30,8 @@ By setting drop_invalid_header_fields to true, anything that doe not conform to Links: terraformDropInvalidHeadersLinks, RemediationMarkdown: terraformDropInvalidHeadersRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, lb := range s.AWS.ELB.LoadBalancers { diff --git a/checks/cloud/aws/elb/drop_invalid_headers.rego b/checks/cloud/aws/elb/drop_invalid_headers.rego new file mode 100644 index 00000000..c4c1e4c3 --- /dev/null +++ b/checks/cloud/aws/elb/drop_invalid_headers.rego @@ -0,0 +1,39 @@ +# METADATA +# title: Load balancers should drop invalid headers +# description: | +# Passing unknown or invalid headers through to the target poses a potential risk of compromise. +# By setting drop_invalid_header_fields to true, anything that doe not conform to well known, defined headers will be removed by the load balancer. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html +# custom: +# id: AVD-AWS-0052 +# avd_id: AVD-AWS-0052 +# provider: aws +# service: elb +# severity: HIGH +# short_code: drop-invalid-headers +# recommended_action: Set drop_invalid_header_fields to true +# input: +# selector: +# - type: cloud +# subtypes: +# - service: elb +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb#drop_invalid_header_fields +# good_examples: checks/cloud/aws/elb/drop_invalid_headers.tf.go +# bad_examples: checks/cloud/aws/elb/drop_invalid_headers.tf.go +package builtin.aws.elb.aws0052 + +import rego.v1 + +deny contains res if { + some lb in input.aws.elb.loadbalancers + lb.type.value == "application" + lb.dropinvalidheaderfields.value == false + res := result.new("Application load balancer is not set to drop invalid headers.", lb.dropinvalidheaderfields) +} diff --git a/checks/cloud/aws/elb/drop_invalid_headers_test.go b/checks/cloud/aws/elb/drop_invalid_headers_test.go deleted file mode 100644 index f57e1ef6..00000000 --- a/checks/cloud/aws/elb/drop_invalid_headers_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package elb - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elb" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckDropInvalidHeaders(t *testing.T) { - tests := []struct { - name string - input elb.ELB - expected bool - }{ - { - name: "Load balancer drop invalid headers disabled", - input: elb.ELB{ - LoadBalancers: []elb.LoadBalancer{ - { - Metadata: trivyTypes.NewTestMetadata(), - Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), - DropInvalidHeaderFields: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "Load balancer drop invalid headers enabled", - input: elb.ELB{ - LoadBalancers: []elb.LoadBalancer{ - { - Metadata: trivyTypes.NewTestMetadata(), - Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), - DropInvalidHeaderFields: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, { - name: "Classic load balanace doesn't fail when no drop headers", - input: elb.ELB{ - LoadBalancers: []elb.LoadBalancer{ - { - Metadata: trivyTypes.NewTestMetadata(), - Type: trivyTypes.String(elb.TypeClassic, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.ELB = test.input - results := CheckDropInvalidHeaders.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckDropInvalidHeaders.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/aws/elb/drop_invalid_headers_test.rego b/checks/cloud/aws/elb/drop_invalid_headers_test.rego new file mode 100644 index 00000000..0f21b322 --- /dev/null +++ b/checks/cloud/aws/elb/drop_invalid_headers_test.rego @@ -0,0 +1,33 @@ +package builtin.aws.elb.aws0052_test + +import rego.v1 + +import data.builtin.aws.elb.aws0052 as check +import data.lib.test + +test_allow_drop_invalid_headers if { + inp := {"aws": {"elb": {"loadbalancers": [{ + "type": {"value": "application"}, + "dropinvalidheaderfields": {"value": true}, + }]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_allow_without_drop_invalid_headers_but_no_application if { + inp := {"aws": {"elb": {"loadbalancers": [{ + "type": {"value": "gateway"}, + "dropinvalidheaderfields": {"value": false}, + }]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_without_drop_invalid_headers_and_application if { + inp := {"aws": {"elb": {"loadbalancers": [{ + "type": {"value": "application"}, + "dropinvalidheaderfields": {"value": false}, + }]}}} + + test.assert_equal_message("Application load balancer is not set to drop invalid headers.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/elb/http_not_used.go b/checks/cloud/aws/elb/http_not_used.go index aa345644..2c0a1278 100755 --- a/checks/cloud/aws/elb/http_not_used.go +++ b/checks/cloud/aws/elb/http_not_used.go @@ -30,7 +30,8 @@ You should use HTTPS, which is HTTP over an encrypted (TLS) connection, meaning Links: terraformHttpNotUsedLinks, RemediationMarkdown: terraformHttpNotUsedRemediationMarkdown, }, - Severity: severity.Critical, + Severity: severity.Critical, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, lb := range s.AWS.ELB.LoadBalancers { diff --git a/checks/cloud/aws/elb/http_not_used.rego b/checks/cloud/aws/elb/http_not_used.rego new file mode 100644 index 00000000..8e7d3a1d --- /dev/null +++ b/checks/cloud/aws/elb/http_not_used.rego @@ -0,0 +1,51 @@ +# METADATA +# title: Use of plain HTTP. +# description: | +# Plain HTTP is unencrypted and human-readable. This means that if a malicious actor was to eavesdrop on your connection, they would be able to see all of your data flowing back and forth. +# You should use HTTPS, which is HTTP over an encrypted (TLS) connection, meaning eavesdroppers cannot read your traffic. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://www.cloudflare.com/en-gb/learning/ssl/why-is-http-not-secure/ +# custom: +# id: AVD-AWS-0054 +# avd_id: AVD-AWS-0054 +# provider: aws +# service: elb +# severity: CRITICAL +# short_code: http-not-used +# recommended_action: Switch to HTTPS to benefit from TLS security features +# input: +# selector: +# - type: cloud +# subtypes: +# - service: elb +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener +# good_examples: checks/cloud/aws/elb/http_not_used.tf.go +# bad_examples: checks/cloud/aws/elb/http_not_used.tf.go +package builtin.aws.elb.aws0054 + +import rego.v1 + +deny contains res if { + some lb in input.aws.elb.loadbalancers + lb.type.value == "application" + + some listener in lb.listeners + use_http(listener) + res := result.new("Listener for application load balancer does not use HTTPS.", listener) +} + +use_http(listener) if { + listener.protocol.value == "HTTP" + not has_redirect(listener) +} + +has_redirect(listener) if { + some action in listener.defaultactions + action.type.value == "redirect" +} diff --git a/checks/cloud/aws/elb/http_not_used_test.go b/checks/cloud/aws/elb/http_not_used_test.go deleted file mode 100644 index f301ecfe..00000000 --- a/checks/cloud/aws/elb/http_not_used_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package elb - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elb" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckHttpNotUsed(t *testing.T) { - tests := []struct { - name string - input elb.ELB - expected bool - }{ - { - name: "Load balancer listener with HTTP protocol", - input: elb.ELB{ - LoadBalancers: []elb.LoadBalancer{ - { - Metadata: trivyTypes.NewTestMetadata(), - Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), - Listeners: []elb.Listener{ - { - Metadata: trivyTypes.NewTestMetadata(), - Protocol: trivyTypes.String("HTTP", trivyTypes.NewTestMetadata()), - DefaultActions: []elb.Action{ - { - Metadata: trivyTypes.NewTestMetadata(), - Type: trivyTypes.String("forward", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Load balancer listener with HTTP protocol but redirect default action", - input: elb.ELB{ - LoadBalancers: []elb.LoadBalancer{ - { - Metadata: trivyTypes.NewTestMetadata(), - Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), - Listeners: []elb.Listener{ - { - Metadata: trivyTypes.NewTestMetadata(), - Protocol: trivyTypes.String("HTTP", trivyTypes.NewTestMetadata()), - DefaultActions: []elb.Action{ - { - Metadata: trivyTypes.NewTestMetadata(), - Type: trivyTypes.String("redirect", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Load balancer listener with HTTP protocol but redirect among multiple default actions", - input: elb.ELB{ - LoadBalancers: []elb.LoadBalancer{ - { - Metadata: trivyTypes.NewTestMetadata(), - Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), - Listeners: []elb.Listener{ - { - Metadata: trivyTypes.NewTestMetadata(), - Protocol: trivyTypes.String("HTTP", trivyTypes.NewTestMetadata()), - DefaultActions: []elb.Action{ - { - Metadata: trivyTypes.NewTestMetadata(), - Type: trivyTypes.String("forward", trivyTypes.NewTestMetadata()), - }, - { - Metadata: trivyTypes.NewTestMetadata(), - Type: trivyTypes.String("redirect", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Load balancer listener with HTTPS protocol", - input: elb.ELB{ - LoadBalancers: []elb.LoadBalancer{ - { - Metadata: trivyTypes.NewTestMetadata(), - Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), - Listeners: []elb.Listener{ - { - Metadata: trivyTypes.NewTestMetadata(), - Protocol: trivyTypes.String("HTTPS", trivyTypes.NewTestMetadata()), - DefaultActions: []elb.Action{ - { - Metadata: trivyTypes.NewTestMetadata(), - Type: trivyTypes.String("forward", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.ELB = test.input - results := CheckHttpNotUsed.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckHttpNotUsed.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/aws/elb/http_not_used_test.rego b/checks/cloud/aws/elb/http_not_used_test.rego new file mode 100644 index 00000000..953e76da --- /dev/null +++ b/checks/cloud/aws/elb/http_not_used_test.rego @@ -0,0 +1,63 @@ +package builtin.aws.elb.aws0054_test + +import rego.v1 + +import data.builtin.aws.elb.aws0054 as check +import data.lib.test + +test_allow_https if { + inp := {"aws": {"elb": {"loadbalancers": [{ + "type": {"value": "application"}, + "listeners": [{"protocol": {"value": "HTTPS"}}], + }]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_allow_http_with_redirect if { + inp := {"aws": {"elb": {"loadbalancers": [{ + "type": {"value": "application"}, + "listeners": [{ + "protocol": {"value": "HTTP"}, + "defaultactions": [{"type": {"value": "redirect"}}], + }], + }]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_allow_http_mixed_actions if { + inp := {"aws": {"elb": {"loadbalancers": [{ + "type": {"value": "application"}, + "listeners": [{ + "protocol": {"value": "HTTP"}, + "defaultactions": [ + {"type": {"value": "redirect"}}, + {"type": {"value": "forward"}}, + ], + }], + }]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_allow_http_but_not_application if { + inp := {"aws": {"elb": {"loadbalancers": [{ + "type": {"value": "network"}, + "listeners": [{"protocol": {"value": "HTTP"}}], + }]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_http_without_redirect if { + inp := {"aws": {"elb": {"loadbalancers": [{ + "type": {"value": "application"}, + "listeners": [{ + "protocol": {"value": "HTTP"}, + "defaultactions": [{"type": {"value": "forward"}}], + }], + }]}}} + + test.assert_equal_message("Listener for application load balancer does not use HTTPS.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/elb/use_secure_tls_policy.go b/checks/cloud/aws/elb/use_secure_tls_policy.go index 33a5341e..9115e5d3 100755 --- a/checks/cloud/aws/elb/use_secure_tls_policy.go +++ b/checks/cloud/aws/elb/use_secure_tls_policy.go @@ -38,7 +38,8 @@ var CheckUseSecureTlsPolicy = rules.Register( Links: terraformUseSecureTlsPolicyLinks, RemediationMarkdown: terraformUseSecureTlsPolicyRemediationMarkdown, }, - Severity: severity.Critical, + Severity: severity.Critical, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, lb := range s.AWS.ELB.LoadBalancers { diff --git a/checks/cloud/aws/elb/use_secure_tls_policy.rego b/checks/cloud/aws/elb/use_secure_tls_policy.rego new file mode 100644 index 00000000..e7b45784 --- /dev/null +++ b/checks/cloud/aws/elb/use_secure_tls_policy.rego @@ -0,0 +1,53 @@ +# METADATA +# title: An outdated SSL policy is in use by a load balancer. +# description: | +# You should not use outdated/insecure TLS versions for encryption. You should be using TLS v1.2+. +# scope: package +# schemas: +# - input: schema["cloud"] +# custom: +# id: AVD-AWS-0047 +# avd_id: AVD-AWS-0047 +# provider: aws +# service: elb +# severity: CRITICAL +# short_code: use-secure-tls-policy +# recommended_action: Use a more recent TLS/SSL policy for the load balancer +# input: +# selector: +# - type: cloud +# subtypes: +# - service: elb +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener +# good_examples: checks/cloud/aws/elb/use_secure_tls_policy.tf.go +# bad_examples: checks/cloud/aws/elb/use_secure_tls_policy.tf.go +package builtin.aws.elb.aws0047 + +import rego.v1 + +outdated_ssl_policies := { + "ELBSecurityPolicy-2015-05", + "ELBSecurityPolicy-2016-08", + "ELBSecurityPolicy-FS-2018-06", + "ELBSecurityPolicy-FS-1-1-2019-08", + "ELBSecurityPolicy-TLS-1-0-2015-04", + "ELBSecurityPolicy-TLS-1-1-2017-01", + "ELBSecurityPolicy-TLS13-1-0-2021-06", + "ELBSecurityPolicy-TLS13-1-1-2021-06", + "ELBSecurityPolicy-TLS13-1-2-Ext1-2021-06", + "ELBSecurityPolicy-TLS13-1-2-Ext2-2021-06", +} + +deny contains res if { + some lb in input.aws.elb.loadbalancers + some listener in lb.listeners + has_outdated_policy(listener) + res := result.new("Listener uses an outdated TLS policy.", listener.tlspolicy) +} + +has_outdated_policy(listener) if { + listener.tlspolicy.value in outdated_ssl_policies +} diff --git a/checks/cloud/aws/elb/use_secure_tls_policy_test.go b/checks/cloud/aws/elb/use_secure_tls_policy_test.go deleted file mode 100644 index 4f46b2c6..00000000 --- a/checks/cloud/aws/elb/use_secure_tls_policy_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package elb - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elb" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckUseSecureTlsPolicy(t *testing.T) { - tests := []struct { - name string - input elb.ELB - expected bool - }{ - { - name: "Load balancer listener using TLS v1.0", - input: elb.ELB{ - LoadBalancers: []elb.LoadBalancer{ - { - Metadata: trivyTypes.NewTestMetadata(), - Listeners: []elb.Listener{ - { - Metadata: trivyTypes.NewTestMetadata(), - TLSPolicy: trivyTypes.String("ELBSecurityPolicy-TLS-1-0-2015-04", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Load balancer listener using TLS v1.2", - input: elb.ELB{ - LoadBalancers: []elb.LoadBalancer{ - { - Metadata: trivyTypes.NewTestMetadata(), - Listeners: []elb.Listener{ - { - Metadata: trivyTypes.NewTestMetadata(), - TLSPolicy: trivyTypes.String("ELBSecurityPolicy-TLS-1-2-2017-01", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Load balancer listener using TLS v1.3", - input: elb.ELB{ - LoadBalancers: []elb.LoadBalancer{ - { - Metadata: trivyTypes.NewTestMetadata(), - Listeners: []elb.Listener{ - { - Metadata: trivyTypes.NewTestMetadata(), - TLSPolicy: trivyTypes.String("ELBSecurityPolicy-TLS13-1-2-2021-06", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.ELB = test.input - results := CheckUseSecureTlsPolicy.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckUseSecureTlsPolicy.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/aws/elb/use_secure_tls_policy_test.rego b/checks/cloud/aws/elb/use_secure_tls_policy_test.rego new file mode 100644 index 00000000..220fbda8 --- /dev/null +++ b/checks/cloud/aws/elb/use_secure_tls_policy_test.rego @@ -0,0 +1,18 @@ +package builtin.aws.elb.aws0047_test + +import rego.v1 + +import data.builtin.aws.elb.aws0047 as check +import data.lib.test + +test_deny_with_outdated_tls_policy if { + inp := {"aws": {"elb": {"loadbalancers": [{"listeners": [{"tlspolicy": {"value": "ELBSecurityPolicy-TLS-1-0-2015-04"}}]}]}}} + + test.assert_equal_message("Load balancer listener using TLS v1.0", check.deny) with input as inp +} + +test_allow_with_actual_tls_policy if { + inp := {"aws": {"elb": {"loadbalancers": [{"listeners": [{"tlspolicy": {"value": "ELBSecurityPolicy-TLS-1-2-2017-01"}}]}]}}} + + test.assert_empty(check.deny) with input as inp +} diff --git a/test/rego/aws_elasticache_test.go b/test/rego/aws_elasticache_test.go new file mode 100644 index 00000000..b36c018c --- /dev/null +++ b/test/rego/aws_elasticache_test.go @@ -0,0 +1,123 @@ +package test + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticache" + "github.com/aquasecurity/trivy/pkg/iac/state" + trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func init() { + addTests(awsElastiCacheTestCases) +} + +var awsElastiCacheTestCases = testCases{ + "AVD-AWS-0049": { + { + name: "ElastiCache security group with no description provided", + input: state.State{AWS: aws.AWS{ElastiCache: elasticache.ElastiCache{ + SecurityGroups: []elasticache.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Description: trivyTypes.String("", trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "ElastiCache security group with description", + input: state.State{AWS: aws.AWS{ElastiCache: elasticache.ElastiCache{ + SecurityGroups: []elasticache.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Description: trivyTypes.String("some decent description", trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0045": { + { + name: "ElastiCache replication group with at-rest encryption disabled", + input: state.State{AWS: aws.AWS{ElastiCache: elasticache.ElastiCache{ + ReplicationGroups: []elasticache.ReplicationGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + AtRestEncryptionEnabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "ElastiCache replication group with at-rest encryption enabled", + input: state.State{AWS: aws.AWS{ElastiCache: elasticache.ElastiCache{ + ReplicationGroups: []elasticache.ReplicationGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + AtRestEncryptionEnabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0050": { + { + name: "Cluster snapshot retention days set to 0", + input: state.State{AWS: aws.AWS{ElastiCache: elasticache.ElastiCache{ + Clusters: []elasticache.Cluster{ + { + Metadata: trivyTypes.NewTestMetadata(), + Engine: trivyTypes.String("redis", trivyTypes.NewTestMetadata()), + NodeType: trivyTypes.String("cache.m4.large", trivyTypes.NewTestMetadata()), + SnapshotRetentionLimit: trivyTypes.Int(0, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "Cluster snapshot retention days set to 5", + input: state.State{AWS: aws.AWS{ElastiCache: elasticache.ElastiCache{ + Clusters: []elasticache.Cluster{ + { + Metadata: trivyTypes.NewTestMetadata(), + Engine: trivyTypes.String("redis", trivyTypes.NewTestMetadata()), + NodeType: trivyTypes.String("cache.m4.large", trivyTypes.NewTestMetadata()), + SnapshotRetentionLimit: trivyTypes.Int(5, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0051": { + { + name: "ElastiCache replication group with in-transit encryption disabled", + input: state.State{AWS: aws.AWS{ElastiCache: elasticache.ElastiCache{ + ReplicationGroups: []elasticache.ReplicationGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + TransitEncryptionEnabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "ElastiCache replication group with in-transit encryption enabled", + input: state.State{AWS: aws.AWS{ElastiCache: elasticache.ElastiCache{ + ReplicationGroups: []elasticache.ReplicationGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + TransitEncryptionEnabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + }, +} diff --git a/test/rego/aws_elasticsearch_test.go b/test/rego/aws_elasticsearch_test.go new file mode 100644 index 00000000..ae08dd50 --- /dev/null +++ b/test/rego/aws_elasticsearch_test.go @@ -0,0 +1,175 @@ +package test + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticsearch" + "github.com/aquasecurity/trivy/pkg/iac/state" + trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func init() { + addTests(awsElasticsearchTestCases) +} + +var awsElasticsearchTestCases = testCases{ + "AVD-AWS-0048": { + { + name: "Elasticsearch domain with at-rest encryption disabled", + input: state.State{AWS: aws.AWS{Elasticsearch: elasticsearch.Elasticsearch{ + Domains: []elasticsearch.Domain{ + { + Metadata: trivyTypes.NewTestMetadata(), + AtRestEncryption: elasticsearch.AtRestEncryption{ + Metadata: trivyTypes.NewTestMetadata(), + Enabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Elasticsearch domain with at-rest encryption enabled", + input: state.State{AWS: aws.AWS{Elasticsearch: elasticsearch.Elasticsearch{ + Domains: []elasticsearch.Domain{ + { + Metadata: trivyTypes.NewTestMetadata(), + AtRestEncryption: elasticsearch.AtRestEncryption{ + Metadata: trivyTypes.NewTestMetadata(), + Enabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0042": { + { + name: "Elasticsearch domain with audit logging disabled", + input: state.State{AWS: aws.AWS{Elasticsearch: elasticsearch.Elasticsearch{ + Domains: []elasticsearch.Domain{ + { + Metadata: trivyTypes.NewTestMetadata(), + LogPublishing: elasticsearch.LogPublishing{ + Metadata: trivyTypes.NewTestMetadata(), + AuditEnabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Elasticsearch domain with audit logging enabled", + input: state.State{AWS: aws.AWS{Elasticsearch: elasticsearch.Elasticsearch{ + Domains: []elasticsearch.Domain{ + { + Metadata: trivyTypes.NewTestMetadata(), + LogPublishing: elasticsearch.LogPublishing{ + Metadata: trivyTypes.NewTestMetadata(), + AuditEnabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0043": { + { + name: "Elasticsearch domain without in-transit encryption", + input: state.State{AWS: aws.AWS{Elasticsearch: elasticsearch.Elasticsearch{ + Domains: []elasticsearch.Domain{ + { + Metadata: trivyTypes.NewTestMetadata(), + TransitEncryption: elasticsearch.TransitEncryption{ + Metadata: trivyTypes.NewTestMetadata(), + Enabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Elasticsearch domain with in-transit encryption", + input: state.State{AWS: aws.AWS{Elasticsearch: elasticsearch.Elasticsearch{ + Domains: []elasticsearch.Domain{ + { + Metadata: trivyTypes.NewTestMetadata(), + TransitEncryption: elasticsearch.TransitEncryption{ + Metadata: trivyTypes.NewTestMetadata(), + Enabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0046": { + { + name: "Elasticsearch domain with enforce HTTPS disabled", + input: state.State{AWS: aws.AWS{Elasticsearch: elasticsearch.Elasticsearch{ + Domains: []elasticsearch.Domain{ + { + Metadata: trivyTypes.NewTestMetadata(), + Endpoint: elasticsearch.Endpoint{ + Metadata: trivyTypes.NewTestMetadata(), + EnforceHTTPS: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Elasticsearch domain with enforce HTTPS enabled", + input: state.State{AWS: aws.AWS{Elasticsearch: elasticsearch.Elasticsearch{ + Domains: []elasticsearch.Domain{ + { + Metadata: trivyTypes.NewTestMetadata(), + Endpoint: elasticsearch.Endpoint{ + Metadata: trivyTypes.NewTestMetadata(), + EnforceHTTPS: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0126": { + { + name: "Elasticsearch domain with TLS v1.0", + input: state.State{AWS: aws.AWS{Elasticsearch: elasticsearch.Elasticsearch{ + Domains: []elasticsearch.Domain{ + { + Metadata: trivyTypes.NewTestMetadata(), + Endpoint: elasticsearch.Endpoint{ + Metadata: trivyTypes.NewTestMetadata(), + TLSPolicy: trivyTypes.String("Policy-Min-TLS-1-0-2019-07", trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Elasticsearch domain with TLS v1.2", + input: state.State{AWS: aws.AWS{Elasticsearch: elasticsearch.Elasticsearch{ + Domains: []elasticsearch.Domain{ + { + Metadata: trivyTypes.NewTestMetadata(), + Endpoint: elasticsearch.Endpoint{ + Metadata: trivyTypes.NewTestMetadata(), + TLSPolicy: trivyTypes.String("Policy-Min-TLS-1-2-2019-07", trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: false, + }, + }, +} diff --git a/test/rego/aws_elb_test.go b/test/rego/aws_elb_test.go new file mode 100644 index 00000000..2ed58d70 --- /dev/null +++ b/test/rego/aws_elb_test.go @@ -0,0 +1,238 @@ +package test + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elb" + "github.com/aquasecurity/trivy/pkg/iac/state" + trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func init() { + addTests(awsElbTestCases) +} + +var awsElbTestCases = testCases{ + "AVD-AWS-0053": { + { + name: "Load balancer publicly accessible", + input: state.State{AWS: aws.AWS{ELB: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), + Internal: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "Load balancer internally accessible", + input: state.State{AWS: aws.AWS{ELB: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), + Internal: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0052": { + { + name: "Load balancer drop invalid headers disabled", + input: state.State{AWS: aws.AWS{ELB: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), + DropInvalidHeaderFields: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "Load balancer drop invalid headers enabled", + input: state.State{AWS: aws.AWS{ELB: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), + DropInvalidHeaderFields: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + { + name: "Classic load balanace doesn't fail when no drop headers", + input: state.State{AWS: aws.AWS{ELB: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String(elb.TypeClassic, trivyTypes.NewTestMetadata()), + }, + }}}, + }, + expected: false, + }, + }, + "AVD-AWS-0054": { + { + name: "Load balancer listener with HTTP protocol", + input: state.State{AWS: aws.AWS{ELB: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), + Listeners: []elb.Listener{ + { + Metadata: trivyTypes.NewTestMetadata(), + Protocol: trivyTypes.String("HTTP", trivyTypes.NewTestMetadata()), + DefaultActions: []elb.Action{ + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String("forward", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Load balancer listener with HTTP protocol but redirect default action", + input: state.State{AWS: aws.AWS{ELB: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), + Listeners: []elb.Listener{ + { + Metadata: trivyTypes.NewTestMetadata(), + Protocol: trivyTypes.String("HTTP", trivyTypes.NewTestMetadata()), + DefaultActions: []elb.Action{ + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String("redirect", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }}}, + expected: false, + }, + { + name: "Load balancer listener with HTTP protocol but redirect among multiple default actions", + input: state.State{AWS: aws.AWS{ELB: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), + Listeners: []elb.Listener{ + { + Metadata: trivyTypes.NewTestMetadata(), + Protocol: trivyTypes.String("HTTP", trivyTypes.NewTestMetadata()), + DefaultActions: []elb.Action{ + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String("forward", trivyTypes.NewTestMetadata()), + }, + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String("redirect", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }}}, + expected: false, + }, + { + name: "Load balancer listener with HTTPS protocol", + input: state.State{AWS: aws.AWS{ELB: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String(elb.TypeApplication, trivyTypes.NewTestMetadata()), + Listeners: []elb.Listener{ + { + Metadata: trivyTypes.NewTestMetadata(), + Protocol: trivyTypes.String("HTTPS", trivyTypes.NewTestMetadata()), + DefaultActions: []elb.Action{ + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String("forward", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0047": { + { + name: "Load balancer listener using TLS v1.0", + input: state.State{AWS: aws.AWS{ELB: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Metadata: trivyTypes.NewTestMetadata(), + Listeners: []elb.Listener{ + { + Metadata: trivyTypes.NewTestMetadata(), + TLSPolicy: trivyTypes.String("ELBSecurityPolicy-TLS-1-0-2015-04", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Load balancer listener using TLS v1.2", + input: state.State{AWS: aws.AWS{ELB: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Metadata: trivyTypes.NewTestMetadata(), + Listeners: []elb.Listener{ + { + Metadata: trivyTypes.NewTestMetadata(), + TLSPolicy: trivyTypes.String("ELBSecurityPolicy-TLS-1-2-2017-01", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: false, + }, + { + name: "Load balancer listener using TLS v1.3", + input: state.State{AWS: aws.AWS{ELB: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Metadata: trivyTypes.NewTestMetadata(), + Listeners: []elb.Listener{ + { + Metadata: trivyTypes.NewTestMetadata(), + TLSPolicy: trivyTypes.String("ELBSecurityPolicy-TLS13-1-2-2021-06", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: false, + }, + }, +}