diff --git a/avd_docs/aws/ec2/AVD-AWS-0008/docs.md b/avd_docs/aws/ec2/AVD-AWS-0008/docs.md index 3d9ac8d3..fcbf2653 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0008/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0008/docs.md @@ -1,8 +1,9 @@ Block devices should be encrypted to ensure sensitive data is held securely at rest. + ### Impact -The block device could be compromised and read from + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0009/docs.md b/avd_docs/aws/ec2/AVD-AWS-0009/docs.md index 5500d147..d44209d5 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0009/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0009/docs.md @@ -1,8 +1,9 @@ You should limit the provision of public IP addresses for resources. Resources should not be exposed on the public internet, but should have access limited to consumers required for the function of your application. + ### Impact -The instance or configuration is publicly accessible + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0026/docs.md b/avd_docs/aws/ec2/AVD-AWS-0026/docs.md index eb61bd60..a0b4e07e 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0026/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0026/docs.md @@ -1,8 +1,9 @@ By enabling encryption on EBS volumes you protect the volume, the disk I/O and any derived snapshots from compromise if intercepted. + ### Impact -Unencrypted sensitive data is vulnerable to compromise. + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0027/docs.md b/avd_docs/aws/ec2/AVD-AWS-0027/docs.md index f2f509b2..5a57f1dc 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0027/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0027/docs.md @@ -1,8 +1,9 @@ Encryption using AWS keys provides protection for your EBS volume. To increase control of the encryption and manage factors like rotation use customer managed keys. + ### Impact -Using AWS managed keys does not allow for fine grained control + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0028/docs.md b/avd_docs/aws/ec2/AVD-AWS-0028/docs.md index 9ecb4268..89377994 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0028/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0028/docs.md @@ -1,12 +1,13 @@ - IMDS v2 (Instance Metadata Service) introduced session authentication tokens which improve security when talking to IMDS. -By default aws_instance resource sets IMDS session auth tokens to be optional. + +By default aws_instance resource sets IMDS session auth tokens to be optional. + To fully protect IMDS you need to enable session tokens by using metadata_options block and its http_tokens variable set to required. ### Impact -Instance metadata service can be interacted with freely + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0029/docs.md b/avd_docs/aws/ec2/AVD-AWS-0029/docs.md index e17616eb..cd2e6ada 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0029/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0029/docs.md @@ -1,8 +1,9 @@ EC2 instance data is used to pass start up information into the EC2 instance. This userdata must not contain access key credentials. Instead use an IAM Instance Profile assigned to the instance to grant access to other AWS Services. + ### Impact -User data is visible through the AWS Management console + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0099/docs.md b/avd_docs/aws/ec2/AVD-AWS-0099/docs.md index 9f7c227f..d4a3f8fb 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0099/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0099/docs.md @@ -3,8 +3,9 @@ Security groups 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/ec2/AVD-AWS-0101/docs.md b/avd_docs/aws/ec2/AVD-AWS-0101/docs.md index ba54aa40..f2384f42 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0101/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0101/docs.md @@ -1,8 +1,9 @@ Default VPC does not have a lot of the critical security features that standard VPC comes with, new resources should not be created in the default VPC and it should not be present in the Terraform. + ### Impact -The default VPC does not have critical security features applied + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0102/docs.md b/avd_docs/aws/ec2/AVD-AWS-0102/docs.md index 05ba9ce0..a3fc9bed 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0102/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0102/docs.md @@ -1,8 +1,9 @@ Ensure access to specific required ports is allowed, and nothing else. + ### Impact -All ports exposed for ingressing/egressing data + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0104/docs.md b/avd_docs/aws/ec2/AVD-AWS-0104/docs.md index a0c6194a..5c16c680 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0104/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0104/docs.md @@ -1,8 +1,9 @@ Opening up ports to connect out to the public internet is generally to be avoided. You should restrict access to IP addresses or ranges that are explicitly required where possible. + ### Impact -Your port is egressing data to the internet + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0105/docs.md b/avd_docs/aws/ec2/AVD-AWS-0105/docs.md index 86d50b27..b47833ec 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0105/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0105/docs.md @@ -1,8 +1,9 @@ Opening up ACLs to the public internet is potentially dangerous. You should restrict access to IP addresses or ranges that explicitly require it where possible. + ### Impact -The ports are exposed for ingressing data to the internet + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0107/docs.md b/avd_docs/aws/ec2/AVD-AWS-0107/docs.md index d1542f4e..c96eb465 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0107/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0107/docs.md @@ -1,8 +1,9 @@ Opening up ports to the public internet is generally to be avoided. You should restrict access to IP addresses or ranges that explicitly require it where possible. + ### Impact -Your port exposed to the internet + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0122/docs.md b/avd_docs/aws/ec2/AVD-AWS-0122/docs.md index c3774def..ca86ac2d 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0122/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0122/docs.md @@ -1,8 +1,9 @@ When creating Launch Configurations, user data can be used for the initial configuration of the instance. User data must not contain any sensitive data. + ### Impact -Sensitive credentials in user data can be leaked + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0124/docs.md b/avd_docs/aws/ec2/AVD-AWS-0124/docs.md index fbb27853..08bee77f 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0124/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0124/docs.md @@ -3,8 +3,9 @@ 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/ec2/AVD-AWS-0129/docs.md b/avd_docs/aws/ec2/AVD-AWS-0129/docs.md index e17616eb..cd2e6ada 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0129/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0129/docs.md @@ -1,8 +1,9 @@ EC2 instance data is used to pass start up information into the EC2 instance. This userdata must not contain access key credentials. Instead use an IAM Instance Profile assigned to the instance to grant access to other AWS Services. + ### Impact -User data is visible through the AWS Management console + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0130/docs.md b/avd_docs/aws/ec2/AVD-AWS-0130/docs.md index 9ecb4268..89377994 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0130/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0130/docs.md @@ -1,12 +1,13 @@ - IMDS v2 (Instance Metadata Service) introduced session authentication tokens which improve security when talking to IMDS. -By default aws_instance resource sets IMDS session auth tokens to be optional. + +By default aws_instance resource sets IMDS session auth tokens to be optional. + To fully protect IMDS you need to enable session tokens by using metadata_options block and its http_tokens variable set to required. ### Impact -Instance metadata service can be interacted with freely + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0131/docs.md b/avd_docs/aws/ec2/AVD-AWS-0131/docs.md index 3d9ac8d3..fcbf2653 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0131/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0131/docs.md @@ -1,8 +1,9 @@ Block devices should be encrypted to ensure sensitive data is held securely at rest. + ### Impact -The block device could be compromised and read from + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0164/docs.md b/avd_docs/aws/ec2/AVD-AWS-0164/docs.md index 2efd40e6..02106d89 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0164/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0164/docs.md @@ -1,8 +1,9 @@ You should limit the provision of public IP addresses for resources. Resources should not be exposed on the public internet, but should have access limited to consumers required for the function of your application. + ### Impact -The instance is publicly accessible + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0173/docs.md b/avd_docs/aws/ec2/AVD-AWS-0173/docs.md index 3b6d4cab..9d5bf5ad 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0173/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0173/docs.md @@ -1,12 +1,13 @@ - Configuring all VPC default security groups to restrict all traffic will encourage least + privilege security group development and mindful placement of AWS resources into + security groups which will in-turn reduce the exposure of those resources. ### Impact -Easier to accidentally expose resources - goes against principle of least privilege + {{ remediationActions }} diff --git a/avd_docs/aws/ec2/AVD-AWS-0178/docs.md b/avd_docs/aws/ec2/AVD-AWS-0178/docs.md index 775832fe..eebd35c2 100644 --- a/avd_docs/aws/ec2/AVD-AWS-0178/docs.md +++ b/avd_docs/aws/ec2/AVD-AWS-0178/docs.md @@ -1,8 +1,9 @@ VPC Flow Logs provide visibility into network traffic that traverses the VPC and can be used to detect anomalous traffic or insight during security workflows. + ### Impact -Without VPC flow logs, you risk not having enough information about network traffic flow to investigate incidents or identify security issues. + {{ remediationActions }} diff --git a/checks/cloud/aws/ec2/add_description_to_security_group.go b/checks/cloud/aws/ec2/add_description_to_security_group.go index 24b9a48f..09bd2234 100755 --- a/checks/cloud/aws/ec2/add_description_to_security_group.go +++ b/checks/cloud/aws/ec2/add_description_to_security_group.go @@ -36,7 +36,8 @@ Simplifies auditing, debugging, and managing security groups.`, Links: cloudFormationAddDescriptionToSecurityGroupLinks, RemediationMarkdown: cloudFormationAddDescriptionToSecurityGroupRemediationMarkdown, }, - Severity: severity.Low, + Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, group := range s.AWS.EC2.SecurityGroups { diff --git a/checks/cloud/aws/ec2/add_description_to_security_group.rego b/checks/cloud/aws/ec2/add_description_to_security_group.rego new file mode 100644 index 00000000..679ebe14 --- /dev/null +++ b/checks/cloud/aws/ec2/add_description_to_security_group.rego @@ -0,0 +1,51 @@ +# METADATA +# title: Missing description for security group. +# description: | +# Security groups should include a description for auditing purposes. +# +# Simplifies auditing, debugging, and managing security groups. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://www.cloudconformity.com/knowledge-base/aws/EC2/security-group-rules-description.html +# custom: +# id: AVD-AWS-0099 +# avd_id: AVD-AWS-0099 +# provider: aws +# service: ec2 +# severity: LOW +# short_code: add-description-to-security-group +# recommended_action: Add descriptions for all security groups +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule +# good_examples: checks/cloud/aws/ec2/add_description_to_security_group.tf.go +# bad_examples: checks/cloud/aws/ec2/add_description_to_security_group.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/ec2/add_description_to_security_group.cf.go +# bad_examples: checks/cloud/aws/ec2/add_description_to_security_group.cf.go +package builtin.aws.ec2.aws0099 + +import rego.v1 + +deny contains res if { + some sg in input.aws.ec2.securitygroups + sg.__defsec_metadata.managed + sg.description.value == "" + res := result.new("Security group does not have a description.", sg) +} + +deny contains res if { + some sg in input.aws.ec2.securitygroups + sg.__defsec_metadata.managed + sg.description.value == "Managed by Terraform" + res := result.new("Security group explicitly uses the default description.", sg) +} diff --git a/checks/cloud/aws/ec2/add_description_to_security_group_rule.go b/checks/cloud/aws/ec2/add_description_to_security_group_rule.go index 3e7a511a..13a74d76 100755 --- a/checks/cloud/aws/ec2/add_description_to_security_group_rule.go +++ b/checks/cloud/aws/ec2/add_description_to_security_group_rule.go @@ -36,7 +36,8 @@ Simplifies auditing, debugging, and managing security groups.`, Links: cloudFormationAddDescriptionToSecurityGroupRuleLinks, RemediationMarkdown: cloudFormationAddDescriptionToSecurityGroupRuleRemediationMarkdown, }, - Severity: severity.Low, + Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, group := range s.AWS.EC2.SecurityGroups { diff --git a/checks/cloud/aws/ec2/add_description_to_security_group_rule.rego b/checks/cloud/aws/ec2/add_description_to_security_group_rule.rego new file mode 100644 index 00000000..ffed7a2b --- /dev/null +++ b/checks/cloud/aws/ec2/add_description_to_security_group_rule.rego @@ -0,0 +1,47 @@ +# METADATA +# title: Missing description for security group rule. +# description: | +# 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://www.cloudconformity.com/knowledge-base/aws/EC2/security-group-rules-description.html +# custom: +# id: AVD-AWS-0124 +# avd_id: AVD-AWS-0124 +# provider: aws +# service: ec2 +# severity: LOW +# short_code: add-description-to-security-group-rule +# recommended_action: Add descriptions for all security groups rules +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule +# good_examples: checks/cloud/aws/ec2/add_description_to_security_group_rule.tf.go +# bad_examples: checks/cloud/aws/ec2/add_description_to_security_group_rule.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/ec2/add_description_to_security_group_rule.cf.go +# bad_examples: checks/cloud/aws/ec2/add_description_to_security_group_rule.cf.go +package builtin.aws.ec2.aws0124 + +import rego.v1 + +deny contains res if { + some group in input.aws.ec2.securitygroups + some rule in array.concat( + object.get(group, "egressrules", []), + object.get(group, "ingressrules", []), + ) + rule.description.value == "" + res := result.new("Security group rule does not have a description.", rule.description) +} diff --git a/checks/cloud/aws/ec2/add_description_to_security_group_rule_test.go b/checks/cloud/aws/ec2/add_description_to_security_group_rule_test.go deleted file mode 100644 index 714174b0..00000000 --- a/checks/cloud/aws/ec2/add_description_to_security_group_rule_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package ec2 - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckAddDescriptionToSecurityGroupRule(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "AWS VPC security group rule has no description", - input: ec2.EC2{ - SecurityGroups: []ec2.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - IngressRules: []ec2.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Description: trivyTypes.String("", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "AWS VPC security group rule has description", - input: ec2.EC2{ - SecurityGroups: []ec2.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - IngressRules: []ec2.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Description: trivyTypes.String("some description", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.EC2 = test.input - results := CheckAddDescriptionToSecurityGroupRule.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckAddDescriptionToSecurityGroupRule.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/ec2/add_description_to_security_group_rule_test.rego b/checks/cloud/aws/ec2/add_description_to_security_group_rule_test.rego new file mode 100644 index 00000000..aab0bb4c --- /dev/null +++ b/checks/cloud/aws/ec2/add_description_to_security_group_rule_test.rego @@ -0,0 +1,18 @@ +package builtin.aws.ec2.aws0124_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0124 as check +import data.lib.test + +test_allow_rule_with_description if { + inp := {"aws": {"ec2": {"securitygroups": [{"egressrules": [{"description": {"value": "test"}}]}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_disallow_rule_without_description if { + inp := {"aws": {"ec2": {"securitygroups": [{"egressrules": [{"description": {"value": ""}}]}]}}} + + test.assert_equal_message("Security group rule does not have a description.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/ec2/add_description_to_security_group_test.go b/checks/cloud/aws/ec2/add_description_to_security_group_test.go deleted file mode 100644 index 9a22ef45..00000000 --- a/checks/cloud/aws/ec2/add_description_to_security_group_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package ec2 - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckAddDescriptionToSecurityGroup(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "AWS VPC security group with no description provided", - input: ec2.EC2{ - SecurityGroups: []ec2.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Description: trivyTypes.String("", trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "AWS VPC security group with default description", - input: ec2.EC2{ - SecurityGroups: []ec2.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Description: trivyTypes.String("Managed by Terraform", trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "AWS VPC security group with proper description", - input: ec2.EC2{ - SecurityGroups: []ec2.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - Description: trivyTypes.String("some proper description", trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.EC2 = test.input - results := CheckAddDescriptionToSecurityGroup.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckAddDescriptionToSecurityGroup.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/ec2/add_description_to_security_group_test.rego b/checks/cloud/aws/ec2/add_description_to_security_group_test.rego new file mode 100644 index 00000000..cb9f0e1e --- /dev/null +++ b/checks/cloud/aws/ec2/add_description_to_security_group_test.rego @@ -0,0 +1,33 @@ +package builtin.aws.ec2.aws0099_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0099 as check +import data.lib.test + +test_allow_sg_with_description if { + inp := {"aws": {"ec2": {"securitygroups": [{ + "__defsec_metadata": {"managed": true}, + "description": {"value": "test"}, + }]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_disallow_sg_without_description if { + inp := {"aws": {"ec2": {"securitygroups": [{ + "__defsec_metadata": {"managed": true}, + "description": {"value": ""}, + }]}}} + + test.assert_equal_message("Security group does not have a description", check.deny) with input as inp +} + +test_disallow_sg_with_default_description if { + inp := {"aws": {"ec2": {"securitygroups": [{ + "__defsec_metadata": {"managed": true}, + "description": {"value": "Managed by Terraform"}, + }]}}} + + test.assert_equal_message("Security group explicitly uses the default description", check.deny) with input as inp +} diff --git a/checks/cloud/aws/ec2/as_enable_at_rest_encryption.go b/checks/cloud/aws/ec2/as_enable_at_rest_encryption.go index 83a7d737..3e3922a7 100755 --- a/checks/cloud/aws/ec2/as_enable_at_rest_encryption.go +++ b/checks/cloud/aws/ec2/as_enable_at_rest_encryption.go @@ -34,7 +34,8 @@ var CheckASEnableAtRestEncryption = rules.Register( Links: cloudFormationASEnableAtRestEncryptionLinks, RemediationMarkdown: cloudFormationASEnableAtRestEncryptionRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, launchConfig := range s.AWS.EC2.LaunchConfigurations { diff --git a/checks/cloud/aws/ec2/as_enable_at_rest_encryption.rego b/checks/cloud/aws/ec2/as_enable_at_rest_encryption.rego new file mode 100644 index 00000000..eac799c6 --- /dev/null +++ b/checks/cloud/aws/ec2/as_enable_at_rest_encryption.rego @@ -0,0 +1,47 @@ +# METADATA +# title: Launch configuration with unencrypted block device. +# description: | +# Block devices should be encrypted to ensure sensitive data is held securely at rest. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/RootDeviceStorage.html +# custom: +# id: AVD-AWS-0008 +# avd_id: AVD-AWS-0008 +# provider: aws +# service: ec2 +# severity: HIGH +# short_code: enable-launch-config-at-rest-encryption +# recommended_action: Turn on encryption for all block devices +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#ebs-ephemeral-and-root-block-devices +# good_examples: checks/cloud/aws/ec2/as_enable_at_rest_encryption.tf.go +# bad_examples: checks/cloud/aws/ec2/as_enable_at_rest_encryption.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/ec2/as_enable_at_rest_encryption.cf.go +# bad_examples: checks/cloud/aws/ec2/as_enable_at_rest_encryption.cf.go +package builtin.aws.ec2.aws0008 + +import rego.v1 + +deny contains res if { + some cfg in input.aws.ec2.launchconfigurations + cfg.rootblockdevice.encrypted.value == false + res := result.new("Root block device is not encrypted.", cfg.rootblockdevice.encrypted) +} + +deny contains res if { + some cfg in input.aws.ec2.launchconfigurations + some device in cfg.ebsblockdevices + device.encrypted.value == false + res := result.new("EBS block device is not encrypted.", device.encrypted.value) +} diff --git a/checks/cloud/aws/ec2/as_enable_at_rest_encryption_test.go b/checks/cloud/aws/ec2/as_enable_at_rest_encryption_test.go deleted file mode 100644 index 8ddbfd5e..00000000 --- a/checks/cloud/aws/ec2/as_enable_at_rest_encryption_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package ec2 - -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/ec2" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestASCheckEnableAtRestEncryption(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "Autoscaling unencrypted root block device", - input: ec2.EC2{ - LaunchConfigurations: []ec2.LaunchConfiguration{ - { - Metadata: trivyTypes.NewTestMetadata(), - RootBlockDevice: &ec2.BlockDevice{ - Metadata: trivyTypes.NewTestMetadata(), - Encrypted: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "Autoscaling unencrypted EBS block device", - input: ec2.EC2{ - LaunchConfigurations: []ec2.LaunchConfiguration{ - { - Metadata: trivyTypes.NewTestMetadata(), - EBSBlockDevices: []*ec2.BlockDevice{ - { - Metadata: trivyTypes.NewTestMetadata(), - Encrypted: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Autoscaling encrypted root and EBS block devices", - input: ec2.EC2{ - LaunchConfigurations: []ec2.LaunchConfiguration{ - { - Metadata: trivyTypes.NewTestMetadata(), - RootBlockDevice: &ec2.BlockDevice{ - Metadata: trivyTypes.NewTestMetadata(), - Encrypted: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - EBSBlockDevices: []*ec2.BlockDevice{ - { - Metadata: trivyTypes.NewTestMetadata(), - Encrypted: 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.EC2 = test.input - results := CheckASEnableAtRestEncryption.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckASEnableAtRestEncryption.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/ec2/as_enable_at_rest_encryption_test.rego b/checks/cloud/aws/ec2/as_enable_at_rest_encryption_test.rego new file mode 100644 index 00000000..5e56382b --- /dev/null +++ b/checks/cloud/aws/ec2/as_enable_at_rest_encryption_test.rego @@ -0,0 +1,30 @@ +package builtin.aws.ec2.aws0008_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0008 as check +import data.lib.test + +test_allow_root_block_device_encrypted if { + inp := {"aws": {"ec2": {"launchconfigurations": [{"rootblockdevice": {"encrypted": {"value": true}}}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_allow_ebs_block_device_encrypted if { + inp := {"aws": {"ec2": {"launchconfigurations": [{"ebsblockdevices": [{"encrypted": {"value": true}}]}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_root_block_device_not_encrypted if { + inp := {"aws": {"ec2": {"launchconfigurations": [{"rootblockdevice": {"encrypted": {"value": false}}}]}}} + + test.assert_equal_message("Root block device is not encrypted.", check.deny) with input as inp +} + +test_deny_ebs_block_device_not_encrypted if { + inp := {"aws": {"ec2": {"launchconfigurations": [{"ebsblockdevices": [{"encrypted": {"value": false}}]}]}}} + + test.assert_equal_message("EBS block device is not encrypted.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/ec2/as_enforce_http_token_imds.go b/checks/cloud/aws/ec2/as_enforce_http_token_imds.go index dda0f93f..3304b928 100755 --- a/checks/cloud/aws/ec2/as_enforce_http_token_imds.go +++ b/checks/cloud/aws/ec2/as_enforce_http_token_imds.go @@ -40,7 +40,8 @@ To fully protect IMDS you need to enable session tokens by using metadata_ Links: cloudformationASEnforceHttpTokenImdsLinks, RemediationMarkdown: cloudformationASEnforceHttpTokenImdsRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, configuration := range s.AWS.EC2.LaunchConfigurations { diff --git a/checks/cloud/aws/ec2/as_enforce_http_token_imds.rego b/checks/cloud/aws/ec2/as_enforce_http_token_imds.rego new file mode 100644 index 00000000..12c96086 --- /dev/null +++ b/checks/cloud/aws/ec2/as_enforce_http_token_imds.rego @@ -0,0 +1,61 @@ +# METADATA +# title: aws_instance should activate session tokens for Instance Metadata Service. +# description: | +# IMDS v2 (Instance Metadata Service) introduced session authentication tokens which improve security when talking to IMDS. +# +# By default aws_instance resource sets IMDS session auth tokens to be optional. +# +# To fully protect IMDS you need to enable session tokens by using metadata_options block and its http_tokens variable set to required. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://aws.amazon.com/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service +# custom: +# id: AVD-AWS-0130 +# avd_id: AVD-AWS-0130 +# provider: aws +# service: ec2 +# severity: HIGH +# short_code: enforce-launch-config-http-token-imds +# recommended_action: Enable HTTP token requirement for IMDS +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#metadata-options +# good_examples: checks/cloud/aws/ec2/as_enforce_http_token_imds.tf.go +# bad_examples: checks/cloud/aws/ec2/as_enforce_http_token_imds.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/ec2/as_enforce_http_token_imds.cf.go +# bad_examples: checks/cloud/aws/ec2/as_enforce_http_token_imds.cf.go +package builtin.aws.ec2.aws0130 + +import rego.v1 + +deny contains res if { + some config in input.aws.ec2.launchconfigurations + opts_do_not_require_token(config.metadataoptions) + res := result.new( + "Launch configuration does not require IMDS access to require a token", + config.metadataoptions.httptokens, + ) +} + +deny contains res if { + some tpl in input.aws.ec2.launchtemplates + opts_do_not_require_token(tpl.instance.metadataoptions) + res := result.new( + "Launch template does not require IMDS access to require a token", + tpl.instance.metadataoptions.httptokens, + ) +} + +opts_do_not_require_token(opts) if { + opts.httptokens.value != "required" + opts.httpendpoint.value != "disabled" +} diff --git a/checks/cloud/aws/ec2/as_enforce_http_token_imds_test.go b/checks/cloud/aws/ec2/as_enforce_http_token_imds_test.go deleted file mode 100644 index b82c08fa..00000000 --- a/checks/cloud/aws/ec2/as_enforce_http_token_imds_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package ec2 - -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/ec2" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestASCheckIMDSAccessRequiresToken(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "Launch configuration with optional tokens", - input: ec2.EC2{ - LaunchConfigurations: []ec2.LaunchConfiguration{ - { - Metadata: trivyTypes.NewTestMetadata(), - MetadataOptions: ec2.MetadataOptions{ - Metadata: trivyTypes.NewTestMetadata(), - HttpTokens: trivyTypes.String("optional", trivyTypes.NewTestMetadata()), - HttpEndpoint: trivyTypes.String("enabled", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "Launch template with optional tokens", - input: ec2.EC2{ - LaunchTemplates: []ec2.LaunchTemplate{ - { - Metadata: trivyTypes.NewTestMetadata(), - Instance: ec2.Instance{ - Metadata: trivyTypes.NewTestMetadata(), - MetadataOptions: ec2.MetadataOptions{ - Metadata: trivyTypes.NewTestMetadata(), - HttpTokens: trivyTypes.String("optional", trivyTypes.NewTestMetadata()), - HttpEndpoint: trivyTypes.String("enabled", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "Launch configuration with required tokens", - input: ec2.EC2{ - LaunchConfigurations: []ec2.LaunchConfiguration{ - { - Metadata: trivyTypes.NewTestMetadata(), - MetadataOptions: ec2.MetadataOptions{ - Metadata: trivyTypes.NewTestMetadata(), - HttpTokens: trivyTypes.String("required", trivyTypes.NewTestMetadata()), - HttpEndpoint: trivyTypes.String("enabled", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.EC2 = test.input - results := CheckASIMDSAccessRequiresToken.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckASIMDSAccessRequiresToken.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/ec2/as_enforce_http_token_imds_test.rego b/checks/cloud/aws/ec2/as_enforce_http_token_imds_test.rego new file mode 100644 index 00000000..b233e07a --- /dev/null +++ b/checks/cloud/aws/ec2/as_enforce_http_token_imds_test.rego @@ -0,0 +1,51 @@ +package builtin.aws.ec2.aws0130_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0130 as check +import data.lib.test + +test_allow_launch_config_with_tokens if { + inp := {"aws": {"ec2": {"launchconfigurations": [{"metadataoptions": { + "httptokens": {"value": "required"}, + "httpendpoint": {"value": "enabled"}, + }}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_allow_launch_template_with_tokens if { + inp := {"aws": {"ec2": {"launchtemplates": [{"instance": {"metadataoptions": { + "httptokens": {"value": "required"}, + "httpendpoint": {"value": "enabled"}, + }}}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_allow_launch_config_without_tokens_but_endpoint_disabled if { + inp := {"aws": {"ec2": {"launchconfigurations": [{"metadataoptions": { + "httptokens": {"value": "optional"}, + "httpendpoint": {"value": "disabled"}, + }}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_launch_config_without_tokens if { + inp := {"aws": {"ec2": {"launchconfigurations": [{"metadataoptions": { + "httptokens": {"value": "disabled"}, + "httpendpoint": {"value": "enabled"}, + }}]}}} + + test.assert_equal_message("Launch configuration does not require IMDS access to require a token", check.deny) with input as inp +} + +test_deny_launch_template_without_tokens if { + inp := {"aws": {"ec2": {"launchtemplates": [{"instance": {"metadataoptions": { + "httptokens": {"value": "disabled"}, + "httpendpoint": {"value": "enabled"}, + }}}]}}} + + test.assert_equal_message("Launch template does not require IMDS access to require a token", check.deny) with input as inp +} diff --git a/checks/cloud/aws/ec2/as_no_secrets_in_user_data.go b/checks/cloud/aws/ec2/as_no_secrets_in_user_data.go index 10755aa2..549f587b 100755 --- a/checks/cloud/aws/ec2/as_no_secrets_in_user_data.go +++ b/checks/cloud/aws/ec2/as_no_secrets_in_user_data.go @@ -44,7 +44,8 @@ var CheckASNoSecretsInUserData = rules.Register( Links: cloudFormationASNoSecretsInUserDataLinks, RemediationMarkdown: cloudFormationASNoSecretsInUserDataRemediationMarkdown, }, - Severity: severity.Critical, + Severity: severity.Critical, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.AWS.EC2.LaunchTemplates { diff --git a/checks/cloud/aws/ec2/as_no_secrets_in_user_data.rego b/checks/cloud/aws/ec2/as_no_secrets_in_user_data.rego new file mode 100644 index 00000000..744707f1 --- /dev/null +++ b/checks/cloud/aws/ec2/as_no_secrets_in_user_data.rego @@ -0,0 +1,44 @@ +# METADATA +# title: User data for EC2 instances must not contain sensitive AWS keys +# description: | +# EC2 instance data is used to pass start up information into the EC2 instance. This userdata must not contain access key credentials. Instead use an IAM Instance Profile assigned to the instance to grant access to other AWS Services. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-add-user-data.html +# custom: +# id: AVD-AWS-0129 +# avd_id: AVD-AWS-0129 +# provider: aws +# service: ec2 +# severity: CRITICAL +# short_code: no-secrets-in-launch-template-user-data +# recommended_action: Remove sensitive data from the EC2 instance user-data generated by launch templates +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#user_data +# good_examples: checks/cloud/aws/ec2/as_no_secrets_in_user_data.tf.go +# bad_examples: checks/cloud/aws/ec2/as_no_secrets_in_user_data.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/ec2/as_no_secrets_in_user_data.cf.go +# bad_examples: checks/cloud/aws/ec2/as_no_secrets_in_user_data.cf.go +package builtin.aws.ec2.aws0129 + +import rego.v1 + +deny contains res if { + some tmpl in input.aws.ec2.launchtemplates + scan_result := squealer.scan_string(tmpl.instance.userdata.value) + scan_result.transgressionFound + res := result.new( + sprintf("Sensitive data found in user data: %s", [scan_result.description]), + tmpl.instance.userdata, + ) +} diff --git a/checks/cloud/aws/ec2/as_no_secrets_in_user_data_test.go b/checks/cloud/aws/ec2/as_no_secrets_in_user_data_test.go deleted file mode 100644 index 8d085844..00000000 --- a/checks/cloud/aws/ec2/as_no_secrets_in_user_data_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package ec2 - -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/ec2" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestASCheckNoSecretsInUserData(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "Launch template with sensitive info in user data", - input: ec2.EC2{ - LaunchTemplates: []ec2.LaunchTemplate{ - { - Metadata: trivyTypes.NewTestMetadata(), - Instance: ec2.Instance{ - Metadata: trivyTypes.NewTestMetadata(), - UserData: trivyTypes.String(` - export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE - export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY - export AWS_DEFAULT_REGION=us-west-2 - `, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "Launch template with no sensitive info in user data", - input: ec2.EC2{ - LaunchTemplates: []ec2.LaunchTemplate{ - { - Metadata: trivyTypes.NewTestMetadata(), - Instance: ec2.Instance{ - Metadata: trivyTypes.NewTestMetadata(), - UserData: trivyTypes.String(` - export GREETING=hello - `, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.EC2 = test.input - results := CheckASNoSecretsInUserData.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckASNoSecretsInUserData.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/ec2/as_no_secrets_in_user_data_test.rego b/checks/cloud/aws/ec2/as_no_secrets_in_user_data_test.rego new file mode 100644 index 00000000..920abb3d --- /dev/null +++ b/checks/cloud/aws/ec2/as_no_secrets_in_user_data_test.rego @@ -0,0 +1,24 @@ +package builtin.aws.ec2.aws0129_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0129 as check +import data.lib.test + +test_deny_launch_tmpl_with_sensitive_info if { + inp := {"aws": {"ec2": {"launchtemplates": [{"instance": {"userdata": {"value": ` +export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE +export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY +export AWS_DEFAULT_REGION=us-west-2 +`}}}]}}} + + res := check.deny with input as inp + count(res) == 1 +} + +test_allow_launch_tmpl_without_sensitive_info if { + inp := {"aws": {"ec2": {"launchtemplates": [{"instance": {"userdata": {"value": "export GREETING=hello"}}}]}}} + + res := check.deny with input as inp + count(res) == 0 +} diff --git a/checks/cloud/aws/ec2/enable_at_rest_encryption.go b/checks/cloud/aws/ec2/enable_at_rest_encryption.go index 8371bc7d..44decc03 100755 --- a/checks/cloud/aws/ec2/enable_at_rest_encryption.go +++ b/checks/cloud/aws/ec2/enable_at_rest_encryption.go @@ -33,7 +33,8 @@ var CheckEnableAtRestEncryption = rules.Register( Links: cloudFormationEnableAtRestEncryptionLinks, RemediationMarkdown: cloudFormationEnableAtRestEncryptionRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.AWS.EC2.Instances { diff --git a/checks/cloud/aws/ec2/enable_at_rest_encryption.rego b/checks/cloud/aws/ec2/enable_at_rest_encryption.rego new file mode 100644 index 00000000..7d7dce4f --- /dev/null +++ b/checks/cloud/aws/ec2/enable_at_rest_encryption.rego @@ -0,0 +1,47 @@ +# METADATA +# title: Instance with unencrypted block device. +# description: | +# Block devices should be encrypted to ensure sensitive data is held securely at rest. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/RootDeviceStorage.html +# custom: +# id: AVD-AWS-0131 +# avd_id: AVD-AWS-0131 +# provider: aws +# service: ec2 +# severity: HIGH +# short_code: enable-at-rest-encryption +# recommended_action: Turn on encryption for all block devices +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#ebs-ephemeral-and-root-block-devices +# good_examples: checks/cloud/aws/ec2/enable_at_rest_encryption.tf.go +# bad_examples: checks/cloud/aws/ec2/enable_at_rest_encryption.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/ec2/enable_at_rest_encryption.cf.go +# bad_examples: checks/cloud/aws/ec2/enable_at_rest_encryption.cf.go +package builtin.aws.ec2.aws0131 + +import rego.v1 + +deny contains res if { + some instance in input.aws.ec2.instances + instance.rootblockdevice.encrypted.value == false + res := result.new("Root block device is not encrypted.", instance.rootblockdevice.encrypted) +} + +deny contains res if { + some instance in input.aws.ec2.instances + some ebs in instance.ebsblockdevices + ebs.encrypted.value == false + res := result.new("EBS block device is not encrypted.", ebs.encrypted) +} diff --git a/checks/cloud/aws/ec2/enable_at_rest_encryption_test.go b/checks/cloud/aws/ec2/enable_at_rest_encryption_test.go deleted file mode 100644 index 925eabf2..00000000 --- a/checks/cloud/aws/ec2/enable_at_rest_encryption_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package ec2 - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableAtRestEncryption(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "encrypted block device", - input: ec2.EC2{ - Instances: []ec2.Instance{ - { - RootBlockDevice: &ec2.BlockDevice{ - Encrypted: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - { - name: "unencrypted block device", - input: ec2.EC2{ - Instances: []ec2.Instance{ - { - RootBlockDevice: &ec2.BlockDevice{ - Encrypted: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.EC2 = test.input - results := CheckEnableAtRestEncryption.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableAtRestEncryption.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/ec2/enable_at_rest_encryption_test.rego b/checks/cloud/aws/ec2/enable_at_rest_encryption_test.rego new file mode 100644 index 00000000..9ff50e07 --- /dev/null +++ b/checks/cloud/aws/ec2/enable_at_rest_encryption_test.rego @@ -0,0 +1,30 @@ +package builtin.aws.ec2.aws0131_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0131 as check +import data.lib.test + +test_allow_root_block_device_encrypted if { + inp := {"aws": {"ec2": {"instances": [{"rootblockdevice": {"encrypted": {"value": true}}}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_root_block_device_not_encrypted if { + inp := {"aws": {"ec2": {"instances": [{"rootblockdevice": {"encrypted": {"value": false}}}]}}} + + test.assert_equal_message("Instance does not have encryption enabled for root block device", check.deny) with input as inp +} + +test_allow_ebs_block_device_encrypted if { + inp := {"aws": {"ec2": {"instances": [{"ebsblockdevices": [{"encrypted": {"value": true}}]}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_ebs_block_device_not_encrypted if { + inp := {"aws": {"ec2": {"instances": [{"ebsblockdevices": [{"encrypted": {"value": false}}]}]}}} + + test.assert_equal_message("EBS block device is not encrypted.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/ec2/enable_volume_encryption.go b/checks/cloud/aws/ec2/enable_volume_encryption.go index 62e3ecee..9b97ebb7 100755 --- a/checks/cloud/aws/ec2/enable_volume_encryption.go +++ b/checks/cloud/aws/ec2/enable_volume_encryption.go @@ -32,7 +32,8 @@ var CheckEnableVolumeEncryption = rules.Register( Links: cloudFormationEnableVolumeEncryptionLinks, RemediationMarkdown: cloudFormationEnableVolumeEncryptionRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, volume := range s.AWS.EC2.Volumes { diff --git a/checks/cloud/aws/ec2/enable_volume_encryption.rego b/checks/cloud/aws/ec2/enable_volume_encryption.rego new file mode 100644 index 00000000..324d1628 --- /dev/null +++ b/checks/cloud/aws/ec2/enable_volume_encryption.rego @@ -0,0 +1,41 @@ +# METADATA +# title: EBS volumes must be encrypted +# description: | +# By enabling encryption on EBS volumes you protect the volume, the disk I/O and any derived snapshots from compromise if intercepted. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html +# custom: +# id: AVD-AWS-0026 +# avd_id: AVD-AWS-0026 +# provider: aws +# service: ec2 +# severity: HIGH +# short_code: enable-volume-encryption +# recommended_action: Enable encryption of EBS volumes +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ebs_volume#encrypted +# good_examples: checks/cloud/aws/ec2/enable_volume_encryption.tf.go +# bad_examples: checks/cloud/aws/ec2/enable_volume_encryption.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/ec2/enable_volume_encryption.cf.go +# bad_examples: checks/cloud/aws/ec2/enable_volume_encryption.cf.go +package builtin.aws.ec2.aws0026 + +import rego.v1 + +deny contains res if { + some volume in input.aws.ec2.volumes + volume.__defsec_metadata.managed + volume.encryption.enabled.value == false + res := result.new("EBS volume is not encrypted.", volume.encryption.enabled) +} diff --git a/checks/cloud/aws/ec2/enable_volume_encryption_test.go b/checks/cloud/aws/ec2/enable_volume_encryption_test.go deleted file mode 100644 index e5261c57..00000000 --- a/checks/cloud/aws/ec2/enable_volume_encryption_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package ec2 - -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/ec2" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEnableVolumeEncryption(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "unencrypted EBS volume", - input: ec2.EC2{ - Volumes: []ec2.Volume{ - { - Metadata: trivyTypes.NewTestMetadata(), - Encryption: ec2.Encryption{ - Metadata: trivyTypes.NewTestMetadata(), - Enabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "encrypted EBS volume", - input: ec2.EC2{ - Volumes: []ec2.Volume{ - { - Metadata: trivyTypes.NewTestMetadata(), - Encryption: ec2.Encryption{ - 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.EC2 = test.input - results := CheckEnableVolumeEncryption.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEnableVolumeEncryption.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/ec2/enable_volume_encryption_test.rego b/checks/cloud/aws/ec2/enable_volume_encryption_test.rego new file mode 100644 index 00000000..e9b4215d --- /dev/null +++ b/checks/cloud/aws/ec2/enable_volume_encryption_test.rego @@ -0,0 +1,21 @@ +package builtin.aws.ec2.aws0026_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0026 as check +import data.lib.test + +test_allow_encrypted_volume if { + inp := build_input({"enabled": {"value": true}}) + test.assert_empty(check.deny) with input as inp +} + +test_deny_not_encrypted_volume if { + inp := build_input({"enabled": {"value": false}}) + test.assert_equal_message("EBS volume is not encrypted", check.deny) with input as inp +} + +build_input(encryption) := {"aws": {"ec2": {"volumes": [{ + "__defsec_metadata": {"managed": true}, + "encryption": encryption, +}]}}} diff --git a/checks/cloud/aws/ec2/encryption_customer_key.go b/checks/cloud/aws/ec2/encryption_customer_key.go index d519134b..d1f47d27 100755 --- a/checks/cloud/aws/ec2/encryption_customer_key.go +++ b/checks/cloud/aws/ec2/encryption_customer_key.go @@ -32,7 +32,8 @@ var CheckEncryptionCustomerKey = rules.Register( Links: cloudFormationEncryptionCustomerKeyLinks, RemediationMarkdown: cloudFormationEncryptionCustomerKeyRemediationMarkdown, }, - Severity: severity.Low, + Severity: severity.Low, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, volume := range s.AWS.EC2.Volumes { diff --git a/checks/cloud/aws/ec2/encryption_customer_key.rego b/checks/cloud/aws/ec2/encryption_customer_key.rego new file mode 100644 index 00000000..9c6dbb9b --- /dev/null +++ b/checks/cloud/aws/ec2/encryption_customer_key.rego @@ -0,0 +1,41 @@ +# METADATA +# title: EBS volume encryption should use Customer Managed Keys +# description: | +# Encryption using AWS keys provides protection for your EBS volume. To increase control of the encryption and manage factors like rotation use customer managed keys. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html +# custom: +# id: AVD-AWS-0027 +# avd_id: AVD-AWS-0027 +# provider: aws +# service: ec2 +# severity: LOW +# short_code: volume-encryption-customer-key +# recommended_action: Enable encryption using customer managed keys +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ebs_volume#kms_key_id +# good_examples: checks/cloud/aws/ec2/encryption_customer_key.tf.go +# bad_examples: checks/cloud/aws/ec2/encryption_customer_key.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/ec2/encryption_customer_key.cf.go +# bad_examples: checks/cloud/aws/ec2/encryption_customer_key.cf.go +package builtin.aws.ec2.aws0027 + +import rego.v1 + +deny contains res if { + some volume in input.aws.ec2.volumes + volume.__defsec_metadata.managed + volume.encryption.kmskeyid.value == "" + res := result.new("EBS volume does not use a customer-managed KMS key.", volume.encryption.kmskeyid) +} diff --git a/checks/cloud/aws/ec2/encryption_customer_key_test.go b/checks/cloud/aws/ec2/encryption_customer_key_test.go deleted file mode 100644 index 0affec79..00000000 --- a/checks/cloud/aws/ec2/encryption_customer_key_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package ec2 - -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/ec2" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckEncryptionCustomerKey(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "EC2 volume missing KMS key", - input: ec2.EC2{ - Volumes: []ec2.Volume{ - { - Metadata: trivyTypes.NewTestMetadata(), - Encryption: ec2.Encryption{ - Metadata: trivyTypes.NewTestMetadata(), - KMSKeyID: trivyTypes.String("", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "EC2 volume encrypted with KMS key", - input: ec2.EC2{ - Volumes: []ec2.Volume{ - { - Metadata: trivyTypes.NewTestMetadata(), - Encryption: ec2.Encryption{ - Metadata: trivyTypes.NewTestMetadata(), - KMSKeyID: trivyTypes.String("some-kms-key", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.EC2 = test.input - results := CheckEncryptionCustomerKey.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckEncryptionCustomerKey.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/ec2/encryption_customer_key_test.rego b/checks/cloud/aws/ec2/encryption_customer_key_test.rego new file mode 100644 index 00000000..71a22afa --- /dev/null +++ b/checks/cloud/aws/ec2/encryption_customer_key_test.rego @@ -0,0 +1,21 @@ +package builtin.aws.ec2.aws0027_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0027 as check +import data.lib.test + +test_allow_volume_with_cmk if { + inp := build_input({"kmskeyid": {"value": "test"}}) + test.assert_empty(check.deny) with input as inp +} + +test_deny_volume_without_cmk if { + inp := build_input({"kmskeyid": {"value": ""}}) + test.assert_equal_message("EBS volume does not use a customer-managed KMS key.", check.deny) with input as inp +} + +build_input(encryption) := {"aws": {"ec2": {"volumes": [{ + "__defsec_metadata": {"managed": true}, + "encryption": encryption, +}]}}} diff --git a/checks/cloud/aws/ec2/enforce_http_token_imds.go b/checks/cloud/aws/ec2/enforce_http_token_imds.go index ae64df5a..aaf22672 100755 --- a/checks/cloud/aws/ec2/enforce_http_token_imds.go +++ b/checks/cloud/aws/ec2/enforce_http_token_imds.go @@ -33,7 +33,8 @@ To fully protect IMDS you need to enable session tokens by using metadata_ Links: terraformEnforceHttpTokenImdsLinks, RemediationMarkdown: terraformEnforceHttpTokenImdsRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.AWS.EC2.Instances { diff --git a/checks/cloud/aws/ec2/enforce_http_token_imds.rego b/checks/cloud/aws/ec2/enforce_http_token_imds.rego new file mode 100644 index 00000000..8c1a1a8f --- /dev/null +++ b/checks/cloud/aws/ec2/enforce_http_token_imds.rego @@ -0,0 +1,45 @@ +# METADATA +# title: aws_instance should activate session tokens for Instance Metadata Service. +# description: | +# IMDS v2 (Instance Metadata Service) introduced session authentication tokens which improve security when talking to IMDS. +# +# By default aws_instance resource sets IMDS session auth tokens to be optional. +# +# To fully protect IMDS you need to enable session tokens by using metadata_options block and its http_tokens variable set to required. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://aws.amazon.com/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service +# custom: +# id: AVD-AWS-0028 +# avd_id: AVD-AWS-0028 +# provider: aws +# service: ec2 +# severity: HIGH +# short_code: enforce-http-token-imds +# recommended_action: Enable HTTP token requirement for IMDS +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#metadata-options +# good_examples: checks/cloud/aws/ec2/enforce_http_token_imds.tf.go +# bad_examples: checks/cloud/aws/ec2/enforce_http_token_imds.tf.go +package builtin.aws.ec2.aws0028 + +import rego.v1 + +deny contains res if { + some instance in input.aws.ec2.instances + instance.metadataoptions.httptokens.value != "required" + instance.metadataoptions.httpendpoint.value != "disabled" + res := result.new( + "Instance does not require IMDS access to require a token.", + instance.metadataoptions.httptokens, + ) +} diff --git a/checks/cloud/aws/ec2/enforce_http_token_imds_test.go b/checks/cloud/aws/ec2/enforce_http_token_imds_test.go deleted file mode 100644 index 0d9e8214..00000000 --- a/checks/cloud/aws/ec2/enforce_http_token_imds_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package ec2 - -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/ec2" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckIMDSAccessRequiresToken(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "positive result", - input: ec2.EC2{ - Instances: []ec2.Instance{ - { - Metadata: trivyTypes.NewTestMetadata(), - MetadataOptions: ec2.MetadataOptions{ - Metadata: trivyTypes.NewTestMetadata(), - HttpTokens: trivyTypes.String("optional", trivyTypes.NewTestMetadata()), - HttpEndpoint: trivyTypes.String("enabled", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: true, - }, - { - name: "negative result", - input: ec2.EC2{ - Instances: []ec2.Instance{ - { - Metadata: trivyTypes.NewTestMetadata(), - MetadataOptions: ec2.MetadataOptions{ - Metadata: trivyTypes.NewTestMetadata(), - HttpTokens: trivyTypes.String("required", trivyTypes.NewTestMetadata()), - HttpEndpoint: trivyTypes.String("disabled", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.EC2 = test.input - results := CheckIMDSAccessRequiresToken.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckIMDSAccessRequiresToken.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/ec2/enforce_http_token_imds_test.rego b/checks/cloud/aws/ec2/enforce_http_token_imds_test.rego new file mode 100644 index 00000000..919d8d92 --- /dev/null +++ b/checks/cloud/aws/ec2/enforce_http_token_imds_test.rego @@ -0,0 +1,35 @@ +package builtin.aws.ec2.aws0028_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0028 as check +import data.lib.test + +test_allow_instance_with_tokens if { + inp := build_input({ + "httptokens": {"value": "required"}, + "httpendpoint": {"value": "enabled"}, + }) + + test.assert_empty(check.deny) with input as inp +} + +test_deny_instance_without_tokens if { + inp := build_input({ + "httptokens": {"value": "disabled"}, + "httpendpoint": {"value": "enabled"}, + }) + + test.assert_equal_message("Instance does not require IMDS access to require a token", check.deny) with input as inp +} + +test_allow_instance_with_endpoint_disabled if { + inp := build_input({ + "httptokens": {"value": "disabled"}, + "httpendpoint": {"value": "disabled"}, + }) + + test.assert_empty(check.deny) with input as inp +} + +build_input(meta_opts) := {"aws": {"ec2": {"instances": [{"metadataoptions": meta_opts}]}}} diff --git a/checks/cloud/aws/ec2/no_default_vpc.go b/checks/cloud/aws/ec2/no_default_vpc.go index a48c77f7..bf513faa 100755 --- a/checks/cloud/aws/ec2/no_default_vpc.go +++ b/checks/cloud/aws/ec2/no_default_vpc.go @@ -28,7 +28,8 @@ var CheckNoDefaultVpc = rules.Register( Links: terraformNoDefaultVpcLinks, RemediationMarkdown: terraformNoDefaultVpcRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, def := range s.AWS.EC2.VPCs { diff --git a/checks/cloud/aws/ec2/no_default_vpc.rego b/checks/cloud/aws/ec2/no_default_vpc.rego new file mode 100644 index 00000000..fb250616 --- /dev/null +++ b/checks/cloud/aws/ec2/no_default_vpc.rego @@ -0,0 +1,37 @@ +# METADATA +# title: AWS best practice to not use the default VPC for workflows +# description: | +# Default VPC does not have a lot of the critical security features that standard VPC comes with, new resources should not be created in the default VPC and it should not be present in the Terraform. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/vpc/latest/userguide/default-vpc.html +# custom: +# id: AVD-AWS-0101 +# avd_id: AVD-AWS-0101 +# provider: aws +# service: ec2 +# severity: HIGH +# short_code: no-default-vpc +# recommended_action: Create a non-default vpc for resources to be created in +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_vpc +# good_examples: checks/cloud/aws/ec2/no_default_vpc.tf.go +# bad_examples: checks/cloud/aws/ec2/no_default_vpc.tf.go +package builtin.aws.ec2.aws0101 + +import rego.v1 + +deny contains res if { + some vpc in input.aws.ec2.vpcs + vpc.isdefault.value == true + res := result.new("Default VPC is used.", vpc) +} diff --git a/checks/cloud/aws/ec2/no_default_vpc_test.go b/checks/cloud/aws/ec2/no_default_vpc_test.go deleted file mode 100644 index ff95b4d7..00000000 --- a/checks/cloud/aws/ec2/no_default_vpc_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package ec2 - -import ( - "testing" - - "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckNoDefaultVpc(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "default AWS VPC", - input: ec2.EC2{ - VPCs: []ec2.VPC{ - { - Metadata: types.NewTestMetadata(), - IsDefault: types.Bool(true, types.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "vpc but not default AWS VPC", - input: ec2.EC2{ - VPCs: []ec2.VPC{ - { - Metadata: types.NewTestMetadata(), - IsDefault: types.Bool(false, types.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - { - name: "no default AWS VPC", - input: ec2.EC2{}, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.EC2 = test.input - results := CheckNoDefaultVpc.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckNoDefaultVpc.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/ec2/no_default_vpc_test.rego b/checks/cloud/aws/ec2/no_default_vpc_test.rego new file mode 100644 index 00000000..50765f17 --- /dev/null +++ b/checks/cloud/aws/ec2/no_default_vpc_test.rego @@ -0,0 +1,20 @@ +package builtin.aws.ec2.aws0101_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0101 as check +import data.lib.test + +test_allow_no_default_vpc if { + inp := build_input({"isdefault": {"value": false}}) + + test.assert_empty(check.deny) with input as inp +} + +test_deny_default_vpc if { + inp := build_input({"isdefault": {"value": true}}) + + test.assert_equal_message("Default VPC is used.", check.deny) with input as inp +} + +build_input(vpc) := {"aws": {"ec2": {"vpcs": [vpc]}}} diff --git a/checks/cloud/aws/ec2/no_excessive_port_access.go b/checks/cloud/aws/ec2/no_excessive_port_access.go index f1b1e327..84bfff35 100755 --- a/checks/cloud/aws/ec2/no_excessive_port_access.go +++ b/checks/cloud/aws/ec2/no_excessive_port_access.go @@ -34,12 +34,13 @@ var CheckNoExcessivePortAccess = rules.Register( Links: cloudFormationNoExcessivePortAccessLinks, RemediationMarkdown: cloudFormationNoExcessivePortAccessRemediationMarkdown, }, - Severity: severity.Critical, + Severity: severity.Critical, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, acl := range s.AWS.EC2.NetworkACLs { for _, rule := range acl.Rules { - if rule.Action.EqualTo("allow") && (rule.Protocol.EqualTo("-1") || rule.Protocol.EqualTo("all")) { + if rule.Action.EqualTo("allow") && rule.Protocol.EqualTo("-1") || rule.Protocol.EqualTo("all") { results.Add( "Network ACL rule allows access using ALL ports.", rule.Protocol, diff --git a/checks/cloud/aws/ec2/no_excessive_port_access.rego b/checks/cloud/aws/ec2/no_excessive_port_access.rego new file mode 100644 index 00000000..2e5494d9 --- /dev/null +++ b/checks/cloud/aws/ec2/no_excessive_port_access.rego @@ -0,0 +1,43 @@ +# METADATA +# title: An Network ACL rule allows ALL ports. +# description: | +# Ensure access to specific required ports is allowed, and nothing else. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html +# custom: +# id: AVD-AWS-0102 +# avd_id: AVD-AWS-0102 +# provider: aws +# service: ec2 +# severity: CRITICAL +# short_code: no-excessive-port-access +# recommended_action: Set specific allowed ports +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule#to_port +# good_examples: checks/cloud/aws/ec2/no_excessive_port_access.tf.go +# bad_examples: checks/cloud/aws/ec2/no_excessive_port_access.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/ec2/no_excessive_port_access.cf.go +# bad_examples: checks/cloud/aws/ec2/no_excessive_port_access.cf.go +package builtin.aws.ec2.aws0102 + +import rego.v1 + +all_protocols := {"all", "-1"} + +deny contains res if { + some rule in input.aws.ec2.networkacls[_].rules + rule.action.value == "allow" + rule.protocol.value in all_protocols + res := result.new("Network ACL rule allows access using ALL ports.", rule.protocol) +} diff --git a/checks/cloud/aws/ec2/no_excessive_port_access_test.go b/checks/cloud/aws/ec2/no_excessive_port_access_test.go deleted file mode 100644 index 90d623c9..00000000 --- a/checks/cloud/aws/ec2/no_excessive_port_access_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package ec2 - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckNoExcessivePortAccess(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "AWS VPC network ACL rule with protocol set to all", - input: ec2.EC2{ - NetworkACLs: []ec2.NetworkACL{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []ec2.NetworkACLRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Protocol: trivyTypes.String("-1", trivyTypes.NewTestMetadata()), - Action: trivyTypes.String("allow", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "AWS VPC network ACL rule with protocol set to all", - input: ec2.EC2{ - NetworkACLs: []ec2.NetworkACL{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []ec2.NetworkACLRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Protocol: trivyTypes.String("all", trivyTypes.NewTestMetadata()), - Action: trivyTypes.String("allow", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "AWS VPC network ACL rule with tcp protocol", - input: ec2.EC2{ - NetworkACLs: []ec2.NetworkACL{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []ec2.NetworkACLRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Protocol: trivyTypes.String("tcp", trivyTypes.NewTestMetadata()), - Type: trivyTypes.String("egress", trivyTypes.NewTestMetadata()), - Action: trivyTypes.String("allow", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "Deny with protocol set to all", - input: ec2.EC2{ - NetworkACLs: []ec2.NetworkACL{ - { - Metadata: trivyTypes.NewTestMetadata(), - Rules: []ec2.NetworkACLRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - Protocol: trivyTypes.String("all", trivyTypes.NewTestMetadata()), - Type: trivyTypes.String("ingress", trivyTypes.NewTestMetadata()), - Action: trivyTypes.String("deny", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.EC2 = test.input - results := CheckNoExcessivePortAccess.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckNoExcessivePortAccess.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/ec2/no_excessive_port_access_test.rego b/checks/cloud/aws/ec2/no_excessive_port_access_test.rego new file mode 100644 index 00000000..96ed71ce --- /dev/null +++ b/checks/cloud/aws/ec2/no_excessive_port_access_test.rego @@ -0,0 +1,44 @@ +package builtin.aws.ec2.aws0102_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0102 as check +import data.lib.test + +test_deny_rule_allow_all_protocols if { + inp := build_input({ + "action": {"value": "allow"}, + "protocol": {"value": "all"}, + }) + + test.assert_equal_message("Network ACL rule allows access using ALL ports.", check.deny) with input as inp +} + +test_deny_rule_allow_all_protocols_2 if { + inp := build_input({ + "action": {"value": "allow"}, + "protocol": {"value": "-1"}, + }) + + test.assert_equal_message("Network ACL rule allows access using ALL ports.", check.deny) with input as inp +} + +test_allow_rule_with_tcp_protocol if { + inp := build_input({ + "action": {"value": "allow"}, + "protocol": {"value": "tcp"}, + }) + + test.assert_empty(check.deny) with input as inp +} + +test_allow_deny_rule_with_all_protocols if { + inp := build_input({ + "action": {"value": "deny"}, + "protocol": {"value": "all"}, + }) + + test.assert_empty(check.deny) with input as inp +} + +build_input(rule) := {"aws": {"ec2": {"networkacls": [{"rules": [rule]}]}}} diff --git a/checks/cloud/aws/ec2/no_public_egress_sgr.go b/checks/cloud/aws/ec2/no_public_egress_sgr.go index 21c0886c..ef1a1334 100755 --- a/checks/cloud/aws/ec2/no_public_egress_sgr.go +++ b/checks/cloud/aws/ec2/no_public_egress_sgr.go @@ -35,7 +35,8 @@ var CheckNoPublicEgressSgr = rules.Register( Links: cloudFormationNoPublicEgressSgrLinks, RemediationMarkdown: cloudFormationNoPublicEgressSgrRemediationMarkdown, }, - Severity: severity.Critical, + Severity: severity.Critical, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, group := range s.AWS.EC2.SecurityGroups { diff --git a/checks/cloud/aws/ec2/no_public_egress_sgr.rego b/checks/cloud/aws/ec2/no_public_egress_sgr.rego new file mode 100644 index 00000000..42fb9aa3 --- /dev/null +++ b/checks/cloud/aws/ec2/no_public_egress_sgr.rego @@ -0,0 +1,45 @@ +# METADATA +# title: An egress security group rule allows traffic to /0. +# description: | +# Opening up ports to connect out to the public internet is generally to be avoided. You should restrict access to IP addresses or ranges that are explicitly required where possible. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/whitepapers/latest/building-scalable-secure-multi-vpc-network-infrastructure/centralized-egress-to-internet.html +# custom: +# id: AVD-AWS-0104 +# avd_id: AVD-AWS-0104 +# provider: aws +# service: ec2 +# severity: CRITICAL +# short_code: no-public-egress-sgr +# recommended_action: Set a more restrictive cidr range +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group +# good_examples: checks/cloud/aws/ec2/no_public_egress_sgr.tf.go +# bad_examples: checks/cloud/aws/ec2/no_public_egress_sgr.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/ec2/no_public_egress_sgr.cf.go +# bad_examples: checks/cloud/aws/ec2/no_public_egress_sgr.cf.go +package builtin.aws.ec2.aws0104 + +import rego.v1 + +deny contains res if { + some rule in input.aws.ec2.securitygroups[_].egressrules + some block in rule.cidrs + cidr.is_public(block.value) + cidr.count_addresses(block.value) > 1 + res := result.new( + "Security group rule allows egress to multiple public internet addresses.", + block, + ) +} diff --git a/checks/cloud/aws/ec2/no_public_egress_sgr_test.rego b/checks/cloud/aws/ec2/no_public_egress_sgr_test.rego new file mode 100644 index 00000000..f8d3df17 --- /dev/null +++ b/checks/cloud/aws/ec2/no_public_egress_sgr_test.rego @@ -0,0 +1,18 @@ +package builtin.aws.ec2.aws0104_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0104 as check +import data.lib.test + +test_deny_sg_with_public_egress if { + inp := {"aws": {"ec2": {"securitygroups": [{"egressrules": [{"cidrs": [{"value": "0.0.0.0/0"}]}]}]}}} + + test.assert_count(check.deny, 1) with input as inp +} + +test_allow_sg_without_private_egress if { + inp := {"aws": {"ec2": {"securitygroups": [{"egressrules": [{"cidrs": [{"value": "10.0.0.0/16"}]}]}]}}} + + test.assert_empty(check.deny) with input as inp +} diff --git a/checks/cloud/aws/ec2/no_public_ingress_acl.go b/checks/cloud/aws/ec2/no_public_ingress_acl.go index 791ac7b4..b576d684 100755 --- a/checks/cloud/aws/ec2/no_public_ingress_acl.go +++ b/checks/cloud/aws/ec2/no_public_ingress_acl.go @@ -36,7 +36,8 @@ var CheckNoPublicIngress = rules.Register( Links: cloudFormationNoPublicIngressAclLinks, RemediationMarkdown: cloudFormationNoPublicIngressAclRemediationMarkdown, }, - Severity: severity.Critical, + Severity: severity.Critical, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, acl := range s.AWS.EC2.NetworkACLs { diff --git a/checks/cloud/aws/ec2/no_public_ingress_acl.rego b/checks/cloud/aws/ec2/no_public_ingress_acl.rego new file mode 100644 index 00000000..aa6e8b3a --- /dev/null +++ b/checks/cloud/aws/ec2/no_public_ingress_acl.rego @@ -0,0 +1,52 @@ +# METADATA +# title: An ingress Network ACL rule allows specific ports from /0. +# description: | +# Opening up ACLs to the public internet is potentially dangerous. You should restrict access to IP addresses or ranges that explicitly require it where possible. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/vpc/latest/userguide/vpc-network-acls.html +# custom: +# id: AVD-AWS-0105 +# avd_id: AVD-AWS-0105 +# provider: aws +# service: ec2 +# severity: CRITICAL +# short_code: no-public-ingress-acl +# recommended_action: Set a more restrictive cidr range +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/network_acl_rule#cidr_block +# good_examples: checks/cloud/aws/ec2/no_public_ingress_acl.tf.go +# bad_examples: checks/cloud/aws/ec2/no_public_ingress_acl.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/ec2/no_public_ingress_acl.cf.go +# bad_examples: checks/cloud/aws/ec2/no_public_ingress_acl.cf.go +package builtin.aws.ec2.aws0105 + +import rego.v1 + +deny contains res if { + some acl in input.aws.ec2.networkacls + some rule in acl.rules + is_ingress(rule) + is_allow(rule) + some block in rule.cidrs + cidr.is_public(block.value) + cidr.count_addresses(block.value) > 1 + res := result.new( + "Network ACL rule allows ingress from public internet.", + block, + ) +} + +is_ingress(rule) if rule.type.value == "ingress" + +is_allow(rule) if rule.action.value == "allow" diff --git a/checks/cloud/aws/ec2/no_public_ingress_acl_test.rego b/checks/cloud/aws/ec2/no_public_ingress_acl_test.rego new file mode 100644 index 00000000..a6ead488 --- /dev/null +++ b/checks/cloud/aws/ec2/no_public_ingress_acl_test.rego @@ -0,0 +1,26 @@ +package builtin.aws.ec2.aws0105_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0105 as check +import data.lib.test + +test_deny_acl_rule_with_wildcard_address if { + inp := {"aws": {"ec2": {"networkacls": [{"rules": [{ + "type": {"value": "ingress"}, + "action": {"value": "allow"}, + "cidrs": [{"value": "0.0.0.0/0"}], + }]}]}}} + + test.assert_count(check.deny, 1) with input as inp +} + +test_allow_acl_rule_with_specific_address if { + inp := {"aws": {"ec2": {"networkacls": [{"rules": [{ + "type": {"value": "ingress"}, + "action": {"value": "allow"}, + "cidrs": [{"value": "10.0.0.0/16"}], + }]}]}}} + + test.assert_empty(check.deny) with input as inp +} diff --git a/checks/cloud/aws/ec2/no_public_ingress_sgr.go b/checks/cloud/aws/ec2/no_public_ingress_sgr.go index 786ea076..fa2fb6c1 100755 --- a/checks/cloud/aws/ec2/no_public_ingress_sgr.go +++ b/checks/cloud/aws/ec2/no_public_ingress_sgr.go @@ -40,7 +40,8 @@ var CheckNoPublicIngressSgr = rules.Register( Links: cloudFormationNoPublicIngressSgrLinks, RemediationMarkdown: cloudFormationNoPublicIngressSgrRemediationMarkdown, }, - Severity: severity.Critical, + Severity: severity.Critical, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, group := range s.AWS.EC2.SecurityGroups { diff --git a/checks/cloud/aws/ec2/no_public_ingress_sgr.rego b/checks/cloud/aws/ec2/no_public_ingress_sgr.rego new file mode 100644 index 00000000..a402973e --- /dev/null +++ b/checks/cloud/aws/ec2/no_public_ingress_sgr.rego @@ -0,0 +1,50 @@ +# METADATA +# title: An ingress security group rule allows traffic from /0. +# description: | +# Opening up ports to the public internet is generally to be avoided. You should restrict access to IP addresses or ranges that explicitly require it where possible. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/security-group-rules-reference.html +# custom: +# id: AVD-AWS-0107 +# avd_id: AVD-AWS-0107 +# provider: aws +# service: ec2 +# severity: CRITICAL +# short_code: no-public-ingress-sgr +# recommended_action: Set a more restrictive cidr range +# frameworks: +# cis-aws-1.2: +# - "4.1" +# - "4.2" +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule#cidr_blocks +# good_examples: checks/cloud/aws/ec2/no_public_ingress_sgr.tf.go +# bad_examples: checks/cloud/aws/ec2/no_public_ingress_sgr.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/ec2/no_public_ingress_sgr.cf.go +# bad_examples: checks/cloud/aws/ec2/no_public_ingress_sgr.cf.go +package builtin.aws.ec2.aws0107 + +import rego.v1 + +deny contains res if { + some group in input.aws.ec2.securitygroups + some rule in group.ingressrules + some block in rule.cidrs + cidr.is_public(block.value) + cidr.count_addresses(block.value) > 1 + res := result.new( + "Security group rule allows ingress from public internet.", + block, + ) +} diff --git a/checks/cloud/aws/ec2/no_public_ingress_sgr_test.go b/checks/cloud/aws/ec2/no_public_ingress_sgr_test.go deleted file mode 100644 index 066feab5..00000000 --- a/checks/cloud/aws/ec2/no_public_ingress_sgr_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package ec2 - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/stretchr/testify/assert" -) - -func TestCheckNoPublicIngressSgr(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "AWS VPC ingress security group rule with wildcard address", - input: ec2.EC2{ - SecurityGroups: []ec2.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - IngressRules: []ec2.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - CIDRs: []trivyTypes.StringValue{ - trivyTypes.String("0.0.0.0/0", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "AWS VPC ingress security group rule with private address", - input: ec2.EC2{ - SecurityGroups: []ec2.SecurityGroup{ - { - Metadata: trivyTypes.NewTestMetadata(), - IngressRules: []ec2.SecurityGroupRule{ - { - Metadata: trivyTypes.NewTestMetadata(), - CIDRs: []trivyTypes.StringValue{ - trivyTypes.String("10.0.0.0/16", trivyTypes.NewTestMetadata()), - }, - }, - }, - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.EC2 = test.input - results := CheckNoPublicIngressSgr.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckNoPublicIngressSgr.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/ec2/no_public_ingress_sgr_test.rego b/checks/cloud/aws/ec2/no_public_ingress_sgr_test.rego new file mode 100644 index 00000000..0183f187 --- /dev/null +++ b/checks/cloud/aws/ec2/no_public_ingress_sgr_test.rego @@ -0,0 +1,18 @@ +package builtin.aws.ec2.aws0107_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0107 as check +import data.lib.test + +test_deny_ingress_sq_with_wildcard_address if { + inp := {"aws": {"ec2": {"securitygroups": [{"ingressrules": [{"cidrs": [{"value": "0.0.0.0/0"}]}]}]}}} + + test.assert_count(check.deny, 1) with input as inp +} + +test_allow_ingress_sg_with_private_address if { + inp := {"aws": {"ec2": {"securitygroups": [{"ingressrules": [{"cidrs": [{"value": "10.0.0.0/16"}]}]}]}}} + + test.assert_empty(check.deny) with input as inp +} diff --git a/checks/cloud/aws/ec2/no_public_ip.go b/checks/cloud/aws/ec2/no_public_ip.go index 35095f87..66b771fe 100755 --- a/checks/cloud/aws/ec2/no_public_ip.go +++ b/checks/cloud/aws/ec2/no_public_ip.go @@ -34,7 +34,8 @@ var CheckNoPublicIp = rules.Register( Links: cloudFormationNoPublicIpLinks, RemediationMarkdown: cloudFormationNoPublicIpRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, launchConfig := range s.AWS.EC2.LaunchConfigurations { diff --git a/checks/cloud/aws/ec2/no_public_ip.rego b/checks/cloud/aws/ec2/no_public_ip.rego new file mode 100644 index 00000000..b6f49f7b --- /dev/null +++ b/checks/cloud/aws/ec2/no_public_ip.rego @@ -0,0 +1,41 @@ +# METADATA +# title: Launch configuration should not have a public IP address. +# description: | +# You should limit the provision of public IP addresses for resources. Resources should not be exposed on the public internet, but should have access limited to consumers required for the function of your application. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-instance-addressing.html +# custom: +# id: AVD-AWS-0009 +# avd_id: AVD-AWS-0009 +# provider: aws +# service: ec2 +# severity: HIGH +# short_code: no-public-ip +# recommended_action: Set the instance to not be publicly accessible +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_configuration#associate_public_ip_address +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#associate_public_ip_address +# good_examples: checks/cloud/aws/ec2/no_public_ip.tf.go +# bad_examples: checks/cloud/aws/ec2/no_public_ip.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/ec2/no_public_ip.cf.go +# bad_examples: checks/cloud/aws/ec2/no_public_ip.cf.go +package builtin.aws.ec2.aws0009 + +import rego.v1 + +deny contains res if { + some cfg in input.aws.ec2.launchconfigurations + cfg.associatepublicip.value == true + res := result.new("Launch configuration associates public IP address.", cfg.associatepublicip) +} diff --git a/checks/cloud/aws/ec2/no_public_ip_subnet.go b/checks/cloud/aws/ec2/no_public_ip_subnet.go index ac2c053b..3663ce5c 100755 --- a/checks/cloud/aws/ec2/no_public_ip_subnet.go +++ b/checks/cloud/aws/ec2/no_public_ip_subnet.go @@ -34,7 +34,8 @@ var CheckNoPublicIpSubnet = rules.Register( Links: cloudFormationNoPublicIpSubnetLinks, RemediationMarkdown: cloudFormationNoPublicIpSubnetRemediationMarkdown, }, - Severity: severity.High, + Severity: severity.High, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, subnet := range s.AWS.EC2.Subnets { diff --git a/checks/cloud/aws/ec2/no_public_ip_subnet.rego b/checks/cloud/aws/ec2/no_public_ip_subnet.rego new file mode 100644 index 00000000..e5c68043 --- /dev/null +++ b/checks/cloud/aws/ec2/no_public_ip_subnet.rego @@ -0,0 +1,40 @@ +# METADATA +# title: Instances in a subnet should not receive a public IP address by default. +# description: | +# You should limit the provision of public IP addresses for resources. Resources should not be exposed on the public internet, but should have access limited to consumers required for the function of your application. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-instance-addressing.html#concepts-public-addresses +# custom: +# id: AVD-AWS-0164 +# avd_id: AVD-AWS-0164 +# provider: aws +# service: ec2 +# severity: HIGH +# short_code: no-public-ip-subnet +# recommended_action: Set the instance to not be publicly accessible +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet#map_public_ip_on_launch +# good_examples: checks/cloud/aws/ec2/no_public_ip_subnet.tf.go +# bad_examples: checks/cloud/aws/ec2/no_public_ip_subnet.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/ec2/no_public_ip_subnet.cf.go +# bad_examples: checks/cloud/aws/ec2/no_public_ip_subnet.cf.go +package builtin.aws.ec2.aws0164 + +import rego.v1 + +deny contains res if { + some subnet in input.aws.ec2.subnets + subnet.mappubliciponlaunch.value == true + res := result.new("Subnet associates public IP address.", subnet.mappubliciponlaunch) +} diff --git a/checks/cloud/aws/ec2/no_public_ip_subnet_test.go b/checks/cloud/aws/ec2/no_public_ip_subnet_test.go deleted file mode 100644 index 54ad5144..00000000 --- a/checks/cloud/aws/ec2/no_public_ip_subnet_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package ec2 - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/stretchr/testify/assert" -) - -func TestCheckNoPublicIpSubnet(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "Subnet with public access", - input: ec2.EC2{ - Subnets: []ec2.Subnet{ - { - Metadata: trivyTypes.NewTestMetadata(), - MapPublicIpOnLaunch: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "Subnet without public access", - input: ec2.EC2{ - Subnets: []ec2.Subnet{ - { - Metadata: trivyTypes.NewTestMetadata(), - MapPublicIpOnLaunch: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.EC2 = test.input - results := CheckNoPublicIpSubnet.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckNoPublicIpSubnet.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/ec2/no_public_ip_subnet_test.rego b/checks/cloud/aws/ec2/no_public_ip_subnet_test.rego new file mode 100644 index 00000000..07d71596 --- /dev/null +++ b/checks/cloud/aws/ec2/no_public_ip_subnet_test.rego @@ -0,0 +1,18 @@ +package builtin.aws.ec2.aws0164_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0164 as check +import data.lib.test + +test_deny_subnet_with_public_ip if { + inp := {"aws": {"ec2": {"subnets": [{"mappubliciponlaunch": {"value": true}}]}}} + + test.assert_equal_message("Subnet associates public IP address.", check.deny) with input as inp +} + +test_allow_subnet_without_public_ip if { + inp := {"aws": {"ec2": {"subnets": [{"mappubliciponlaunch": {"value": false}}]}}} + + test.assert_empty(check.deny) with input as inp +} diff --git a/checks/cloud/aws/ec2/no_public_ip_test.go b/checks/cloud/aws/ec2/no_public_ip_test.go deleted file mode 100644 index df6555b7..00000000 --- a/checks/cloud/aws/ec2/no_public_ip_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package ec2 - -import ( - "testing" - - trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/stretchr/testify/assert" -) - -func TestCheckNoPublicIp(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "Launch configuration with public access", - input: ec2.EC2{ - LaunchConfigurations: []ec2.LaunchConfiguration{ - { - Metadata: trivyTypes.NewTestMetadata(), - AssociatePublicIP: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: true, - }, - { - name: "Launch configuration without public access", - input: ec2.EC2{ - LaunchConfigurations: []ec2.LaunchConfiguration{ - { - Metadata: trivyTypes.NewTestMetadata(), - AssociatePublicIP: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), - }, - }, - }, - expected: false, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.EC2 = test.input - results := CheckNoPublicIp.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckNoPublicIp.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/ec2/no_public_ip_test.rego b/checks/cloud/aws/ec2/no_public_ip_test.rego new file mode 100644 index 00000000..22398879 --- /dev/null +++ b/checks/cloud/aws/ec2/no_public_ip_test.rego @@ -0,0 +1,18 @@ +package builtin.aws.ec2.aws0009_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0009 as check +import data.lib.test + +test_allow_without_public_ip if { + inp := {"aws": {"ec2": {"launchconfigurations": [{"associatepublicip": {"value": false}}]}}} + + test.assert_empty(check.deny) with input as inp +} + +test_deny_with_public_ip if { + inp := {"aws": {"ec2": {"launchconfigurations": [{"associatepublicip": {"value": true}}]}}} + + test.assert_equal_message("Launch configuration associates public IP address.", check.deny) with input as inp +} diff --git a/checks/cloud/aws/ec2/no_secrets_in_user_data.go b/checks/cloud/aws/ec2/no_secrets_in_user_data.go index 85cfc7ba..c43d15c0 100755 --- a/checks/cloud/aws/ec2/no_secrets_in_user_data.go +++ b/checks/cloud/aws/ec2/no_secrets_in_user_data.go @@ -39,7 +39,8 @@ var CheckNoSecretsInUserData = rules.Register( Links: cloudFormationNoSecretsInUserDataLinks, RemediationMarkdown: cloudFormationNoSecretsInUserDataRemediationMarkdown, }, - Severity: severity.Critical, + Severity: severity.Critical, + Deprecated: true, }, func(s *state.State) (results scan.Results) { for _, instance := range s.AWS.EC2.Instances { diff --git a/checks/cloud/aws/ec2/no_secrets_in_user_data.rego b/checks/cloud/aws/ec2/no_secrets_in_user_data.rego new file mode 100644 index 00000000..5d9a681e --- /dev/null +++ b/checks/cloud/aws/ec2/no_secrets_in_user_data.rego @@ -0,0 +1,45 @@ +# METADATA +# title: User data for EC2 instances must not contain sensitive AWS keys +# description: | +# EC2 instance data is used to pass start up information into the EC2 instance. This userdata must not contain access key credentials. Instead use an IAM Instance Profile assigned to the instance to grant access to other AWS Services. +# scope: package +# schemas: +# - input: schema["cloud"] +# related_resources: +# - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-add-user-data.html +# custom: +# id: AVD-AWS-0029 +# avd_id: AVD-AWS-0029 +# provider: aws +# service: ec2 +# severity: CRITICAL +# short_code: no-secrets-in-user-data +# recommended_action: Remove sensitive data from the EC2 instance user-data +# input: +# selector: +# - type: cloud +# subtypes: +# - service: ec2 +# provider: aws +# terraform: +# links: +# - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#user_data +# good_examples: checks/cloud/aws/ec2/no_secrets_in_user_data.tf.go +# bad_examples: checks/cloud/aws/ec2/no_secrets_in_user_data.tf.go +# cloudformation: +# good_examples: checks/cloud/aws/ec2/no_secrets_in_user_data.cf.go +# bad_examples: checks/cloud/aws/ec2/no_secrets_in_user_data.cf.go +package builtin.aws.ec2.aws0029 + +import rego.v1 + +deny contains res if { + some instance in input.aws.ec2.instances + isManaged(instance) + scan_result := squealer.scan_string(instance.userdata.value) + scan_result.transgressionFound + res := result.new( + sprintf("Sensitive data found in instance user data: %s", [scan_result.description]), + instance.userdata, + ) +} diff --git a/checks/cloud/aws/ec2/no_secrets_in_user_data_test.go b/checks/cloud/aws/ec2/no_secrets_in_user_data_test.go deleted file mode 100644 index ca46615d..00000000 --- a/checks/cloud/aws/ec2/no_secrets_in_user_data_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package ec2 - -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/ec2" - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckNoSecretsInUserData(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "positive result", - input: ec2.EC2{ - Instances: []ec2.Instance{ - { - Metadata: trivyTypes.NewTestMetadata(), - UserData: trivyTypes.String(`< 0 + +has_rules(sg) if count(sg.egressrules) > 0 diff --git a/checks/cloud/aws/ec2/restrict_all_in_default_sg_test.go b/checks/cloud/aws/ec2/restrict_all_in_default_sg_test.go deleted file mode 100644 index 745acaaa..00000000 --- a/checks/cloud/aws/ec2/restrict_all_in_default_sg_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package ec2 - -import ( - "testing" - - "github.com/aquasecurity/trivy/pkg/iac/types" - - "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - - "github.com/aquasecurity/trivy/pkg/iac/state" - - "github.com/aquasecurity/trivy/pkg/iac/scan" - - "github.com/stretchr/testify/assert" -) - -func TestCheckRestrictAllInDefaultSG(t *testing.T) { - tests := []struct { - name string - input ec2.EC2 - expected bool - }{ - { - name: "default sg restricts all", - input: ec2.EC2{ - VPCs: []ec2.VPC{ - { - Metadata: types.NewTestMetadata(), - SecurityGroups: []ec2.SecurityGroup{ - { - Metadata: types.NewTestMetadata(), - IsDefault: types.Bool(true, types.NewTestMetadata()), - IngressRules: nil, - EgressRules: nil, - }, - }, - }, - }, - }, - expected: false, - }, - { - name: "default sg allows ingress", - input: ec2.EC2{ - VPCs: []ec2.VPC{ - { - Metadata: types.NewTestMetadata(), - SecurityGroups: []ec2.SecurityGroup{ - { - Metadata: types.NewTestMetadata(), - IsDefault: types.Bool(true, types.NewTestMetadata()), - IngressRules: []ec2.SecurityGroupRule{ - {}, - }, - EgressRules: nil, - }, - }, - }, - }, - }, - expected: true, - }, - { - name: "default sg allows egress", - input: ec2.EC2{ - VPCs: []ec2.VPC{ - { - Metadata: types.NewTestMetadata(), - SecurityGroups: []ec2.SecurityGroup{ - { - Metadata: types.NewTestMetadata(), - IsDefault: types.Bool(true, types.NewTestMetadata()), - IngressRules: nil, - EgressRules: []ec2.SecurityGroupRule{ - {}, - }, - }, - }, - }, - }, - }, - expected: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var testState state.State - testState.AWS.EC2 = test.input - results := CheckRestrictAllInDefaultSG.Evaluate(&testState) - var found bool - for _, result := range results { - if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckRestrictAllInDefaultSG.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/ec2/restrict_all_in_default_sg_test.rego b/checks/cloud/aws/ec2/restrict_all_in_default_sg_test.rego new file mode 100644 index 00000000..c6d0807b --- /dev/null +++ b/checks/cloud/aws/ec2/restrict_all_in_default_sg_test.rego @@ -0,0 +1,41 @@ +package builtin.aws.ec2.aws0173_test + +import rego.v1 + +import data.builtin.aws.ec2.aws0173 as check +import data.lib.test + +test_allow_default_sg_without_rules if { + inp := build_input({"isdefault": {"value": true}}) + + test.assert_empty(check.deny) with input as inp +} + +test_allow_non_default_sg_with_rules if { + inp := build_input({ + "isdefault": {"value": false}, + "egressrules": [{"cidrs": [{"value": "0.0.0.0/0"}]}], + }) + + test.assert_empty(check.deny) with input as inp +} + +test_deny_default_sg_with_egress_rules if { + inp := build_input({ + "isdefault": {"value": true}, + "egressrules": [{"cidrs": [{"value": "0.0.0.0/0"}]}], + }) + + test.assert_equal_message("Default security group for VPC has ingress or egress rules.", check.deny) with input as inp +} + +test_deny_default_sg_with_ingress_rules if { + inp := build_input({ + "isdefault": {"value": true}, + "ingressrules": [{"cidrs": [{"value": "0.0.0.0/0"}]}], + }) + + test.assert_equal_message("Default security group for VPC has ingress or egress rules.", check.deny) with input as inp +} + +build_input(sg) := {"aws": {"ec2": {"vpcs": [{"securitygroups": [sg]}]}}} diff --git a/test/rego/aws_ec2_test.go b/test/rego/aws_ec2_test.go new file mode 100644 index 00000000..3867ccf8 --- /dev/null +++ b/test/rego/aws_ec2_test.go @@ -0,0 +1,780 @@ +package test + +import ( + "github.com/aquasecurity/trivy/pkg/iac/providers/aws" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" + "github.com/aquasecurity/trivy/pkg/iac/state" + trivyTypes "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func init() { + addTests(awsEc2TestCases) +} + +var awsEc2TestCases = testCases{ + "AVD-AWS-0124": { + { + name: "AWS VPC security group rule has no description", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + SecurityGroups: []ec2.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + IngressRules: []ec2.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Description: trivyTypes.String("", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: true, + }, + { + name: "AWS VPC security group rule has description", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + SecurityGroups: []ec2.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + IngressRules: []ec2.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Description: trivyTypes.String("some description", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0099": { + { + name: "AWS VPC security group with no description provided", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + SecurityGroups: []ec2.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Description: trivyTypes.String("", trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "AWS VPC security group with default description", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + SecurityGroups: []ec2.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Description: trivyTypes.String("Managed by Terraform", trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "AWS VPC security group with proper description", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + SecurityGroups: []ec2.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + Description: trivyTypes.String("some proper description", trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0008": { + { + name: "Autoscaling unencrypted root block device", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + LaunchConfigurations: []ec2.LaunchConfiguration{ + { + Metadata: trivyTypes.NewTestMetadata(), + RootBlockDevice: &ec2.BlockDevice{ + Metadata: trivyTypes.NewTestMetadata(), + Encrypted: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Autoscaling unencrypted EBS block device", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + LaunchConfigurations: []ec2.LaunchConfiguration{ + { + Metadata: trivyTypes.NewTestMetadata(), + EBSBlockDevices: []*ec2.BlockDevice{ + { + Metadata: trivyTypes.NewTestMetadata(), + Encrypted: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Autoscaling encrypted root and EBS block devices", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + LaunchConfigurations: []ec2.LaunchConfiguration{ + { + Metadata: trivyTypes.NewTestMetadata(), + RootBlockDevice: &ec2.BlockDevice{ + Metadata: trivyTypes.NewTestMetadata(), + Encrypted: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + EBSBlockDevices: []*ec2.BlockDevice{ + { + Metadata: trivyTypes.NewTestMetadata(), + Encrypted: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0130": { + { + name: "Launch configuration with optional tokens", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + LaunchConfigurations: []ec2.LaunchConfiguration{ + { + Metadata: trivyTypes.NewTestMetadata(), + MetadataOptions: ec2.MetadataOptions{ + Metadata: trivyTypes.NewTestMetadata(), + HttpTokens: trivyTypes.String("optional", trivyTypes.NewTestMetadata()), + HttpEndpoint: trivyTypes.String("enabled", trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Launch template with optional tokens", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + LaunchTemplates: []ec2.LaunchTemplate{ + { + Metadata: trivyTypes.NewTestMetadata(), + Instance: ec2.Instance{ + Metadata: trivyTypes.NewTestMetadata(), + MetadataOptions: ec2.MetadataOptions{ + Metadata: trivyTypes.NewTestMetadata(), + HttpTokens: trivyTypes.String("optional", trivyTypes.NewTestMetadata()), + HttpEndpoint: trivyTypes.String("enabled", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Launch configuration with required tokens", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + LaunchConfigurations: []ec2.LaunchConfiguration{ + { + Metadata: trivyTypes.NewTestMetadata(), + MetadataOptions: ec2.MetadataOptions{ + Metadata: trivyTypes.NewTestMetadata(), + HttpTokens: trivyTypes.String("required", trivyTypes.NewTestMetadata()), + HttpEndpoint: trivyTypes.String("enabled", trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0129": { + { + name: "Launch template with sensitive info in user data", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + LaunchTemplates: []ec2.LaunchTemplate{ + { + Metadata: trivyTypes.NewTestMetadata(), + Instance: ec2.Instance{ + Metadata: trivyTypes.NewTestMetadata(), + UserData: trivyTypes.String(` + export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE + export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY + export AWS_DEFAULT_REGION=us-west-2 + `, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + { + name: "Launch template with no sensitive info in user data", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + LaunchTemplates: []ec2.LaunchTemplate{ + { + Metadata: trivyTypes.NewTestMetadata(), + Instance: ec2.Instance{ + Metadata: trivyTypes.NewTestMetadata(), + UserData: trivyTypes.String(` + export GREETING=hello + `, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0131": { + { + name: "encrypted block device", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + Instances: []ec2.Instance{ + { + RootBlockDevice: &ec2.BlockDevice{ + Encrypted: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: false, + }, + { + name: "unencrypted block device", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + Instances: []ec2.Instance{ + { + RootBlockDevice: &ec2.BlockDevice{ + Encrypted: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + }, + "AVD-AWS-0026": { + { + name: "unencrypted EBS volume", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + Volumes: []ec2.Volume{ + { + Metadata: trivyTypes.NewTestMetadata(), + Encryption: ec2.Encryption{ + Metadata: trivyTypes.NewTestMetadata(), + Enabled: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + { + name: "encrypted EBS volume", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + Volumes: []ec2.Volume{ + { + Metadata: trivyTypes.NewTestMetadata(), + Encryption: ec2.Encryption{ + Metadata: trivyTypes.NewTestMetadata(), + Enabled: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0027": { + { + name: "EC2 volume missing KMS key", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + Volumes: []ec2.Volume{ + { + Metadata: trivyTypes.NewTestMetadata(), + Encryption: ec2.Encryption{ + Metadata: trivyTypes.NewTestMetadata(), + KMSKeyID: trivyTypes.String("", trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + { + name: "EC2 volume encrypted with KMS key", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + Volumes: []ec2.Volume{ + { + Metadata: trivyTypes.NewTestMetadata(), + Encryption: ec2.Encryption{ + Metadata: trivyTypes.NewTestMetadata(), + KMSKeyID: trivyTypes.String("some-kms-key", trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0028": { + { + name: "positive result", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + Instances: []ec2.Instance{ + { + Metadata: trivyTypes.NewTestMetadata(), + MetadataOptions: ec2.MetadataOptions{ + Metadata: trivyTypes.NewTestMetadata(), + HttpTokens: trivyTypes.String("optional", trivyTypes.NewTestMetadata()), + HttpEndpoint: trivyTypes.String("enabled", trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: true, + }, + { + name: "negative result", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + Instances: []ec2.Instance{ + { + Metadata: trivyTypes.NewTestMetadata(), + MetadataOptions: ec2.MetadataOptions{ + Metadata: trivyTypes.NewTestMetadata(), + HttpTokens: trivyTypes.String("required", trivyTypes.NewTestMetadata()), + HttpEndpoint: trivyTypes.String("disabled", trivyTypes.NewTestMetadata()), + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0101": { + { + name: "default AWS VPC", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + VPCs: []ec2.VPC{ + { + Metadata: trivyTypes.NewTestMetadata(), + IsDefault: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "vpc but not default AWS VPC", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + VPCs: []ec2.VPC{ + { + Metadata: trivyTypes.NewTestMetadata(), + IsDefault: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + { + name: "no default AWS VPC", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{}}}, + expected: false, + }, + }, + "AVD-AWS-0102": { + { + name: "AWS VPC network ACL rule with protocol set to all", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + NetworkACLs: []ec2.NetworkACL{ + { + Metadata: trivyTypes.NewTestMetadata(), + Rules: []ec2.NetworkACLRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Protocol: trivyTypes.String("-1", trivyTypes.NewTestMetadata()), + Action: trivyTypes.String("allow", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: true, + }, + { + name: "AWS VPC network ACL rule with protocol set to all", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + NetworkACLs: []ec2.NetworkACL{ + { + Metadata: trivyTypes.NewTestMetadata(), + Rules: []ec2.NetworkACLRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Protocol: trivyTypes.String("all", trivyTypes.NewTestMetadata()), + Action: trivyTypes.String("allow", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: true, + }, + { + name: "AWS VPC network ACL rule with tcp protocol", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + NetworkACLs: []ec2.NetworkACL{ + { + Metadata: trivyTypes.NewTestMetadata(), + Rules: []ec2.NetworkACLRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Protocol: trivyTypes.String("tcp", trivyTypes.NewTestMetadata()), + Type: trivyTypes.String("egress", trivyTypes.NewTestMetadata()), + Action: trivyTypes.String("allow", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0104": { + { + name: "AWS VPC security group rule with wildcard address", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + SecurityGroups: []ec2.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + EgressRules: []ec2.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + CIDRs: []trivyTypes.StringValue{ + trivyTypes.String("0.0.0.0/0", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}}, + expected: true, + }, + { + name: "AWS VPC security group rule with private address", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + SecurityGroups: []ec2.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + EgressRules: []ec2.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + CIDRs: []trivyTypes.StringValue{ + trivyTypes.String("10.0.0.0/16", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0105": { + { + name: "AWS VPC network ACL rule with wildcard address", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + NetworkACLs: []ec2.NetworkACL{ + { + Metadata: trivyTypes.NewTestMetadata(), + Rules: []ec2.NetworkACLRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String(ec2.TypeIngress, trivyTypes.NewTestMetadata()), + Action: trivyTypes.String(ec2.ActionAllow, trivyTypes.NewTestMetadata()), + CIDRs: []trivyTypes.StringValue{ + trivyTypes.String("0.0.0.0/0", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}}, + expected: true, + }, + { + name: "AWS VPC network ACL rule with private address", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + NetworkACLs: []ec2.NetworkACL{ + { + Metadata: trivyTypes.NewTestMetadata(), + Rules: []ec2.NetworkACLRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + Type: trivyTypes.String(ec2.TypeIngress, trivyTypes.NewTestMetadata()), + Action: trivyTypes.String(ec2.ActionAllow, trivyTypes.NewTestMetadata()), + CIDRs: []trivyTypes.StringValue{ + trivyTypes.String("10.0.0.0/16", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0107": { + { + name: "AWS VPC ingress security group rule with wildcard address", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + SecurityGroups: []ec2.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + IngressRules: []ec2.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + CIDRs: []trivyTypes.StringValue{ + trivyTypes.String("0.0.0.0/0", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}}, + expected: true, + }, + { + name: "AWS VPC ingress security group rule with private address", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + SecurityGroups: []ec2.SecurityGroup{ + { + Metadata: trivyTypes.NewTestMetadata(), + IngressRules: []ec2.SecurityGroupRule{ + { + Metadata: trivyTypes.NewTestMetadata(), + CIDRs: []trivyTypes.StringValue{ + trivyTypes.String("10.0.0.0/16", trivyTypes.NewTestMetadata()), + }, + }, + }, + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0164": { + { + name: "Subnet with public access", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + Subnets: []ec2.Subnet{ + { + Metadata: trivyTypes.NewTestMetadata(), + MapPublicIpOnLaunch: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "Subnet without public access", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + Subnets: []ec2.Subnet{ + { + Metadata: trivyTypes.NewTestMetadata(), + MapPublicIpOnLaunch: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0009": { + { + name: "Launch configuration with public access", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + LaunchConfigurations: []ec2.LaunchConfiguration{ + { + Metadata: trivyTypes.NewTestMetadata(), + AssociatePublicIP: trivyTypes.Bool(true, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: true, + }, + { + name: "Launch configuration without public access", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + LaunchConfigurations: []ec2.LaunchConfiguration{ + { + Metadata: trivyTypes.NewTestMetadata(), + AssociatePublicIP: trivyTypes.Bool(false, trivyTypes.NewTestMetadata()), + }, + }, + }}}, + expected: false, + }, + }, + "AVD-AWS-0029": { + { + name: "positive result", + input: state.State{AWS: aws.AWS{EC2: ec2.EC2{ + Instances: []ec2.Instance{ + { + Metadata: trivyTypes.NewTestMetadata(), + UserData: trivyTypes.String(`<