Skip to content
This repository has been archived by the owner on Jan 23, 2025. It is now read-only.

Commit

Permalink
feat: add remaining cis aws 1.4 rules (#916)
Browse files Browse the repository at this point in the history
* feat: add remaining cis aws 1.4 rules

* fix linting

* fix failing tests

* add missing docs
  • Loading branch information
liamg authored Sep 2, 2022
1 parent 56e0322 commit 3896451
Show file tree
Hide file tree
Showing 18 changed files with 403 additions and 48 deletions.
19 changes: 19 additions & 0 deletions avd_docs/aws/cloudwatch/AVD-AWS-0174/docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@


Monitoring AWS Organizations changes can help you prevent any unwanted, accidental or
intentional modifications that may lead to unauthorized access or other security breaches.
This monitoring technique helps you to ensure that any unexpected changes performed
within your AWS Organizations can be investigated and any unwanted changes can be
rolled back.


### Impact
Lack of observability into critical organisation changes

<!-- DO NOT CHANGE -->
{{ remediationActions }}

### Links
- https://docs.aws.amazon.com/organizations/latest/userguide/orgs_security_incident-response.html


17 changes: 17 additions & 0 deletions avd_docs/aws/ec2/AVD-AWS-0173/docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@


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

<!-- DO NOT CHANGE -->
{{ remediationActions }}

### Links
- https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/default-custom-security-groups.html


10 changes: 9 additions & 1 deletion internal/adapters/cloud/aws/ec2/ec2.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (a *adapter) Adapt(root *aws2.RootAdapter, state *state.State) error {
return err
}

state.AWS.EC2.DefaultVPCs, err = a.getDefaultVPCs()
state.AWS.EC2.VPCs, err = a.getVPCs()
if err != nil {
return err
}
Expand All @@ -68,6 +68,14 @@ func (a *adapter) Adapt(root *aws2.RootAdapter, state *state.State) error {
return err
}

for i, vpc := range state.AWS.EC2.VPCs {
for _, group := range state.AWS.EC2.SecurityGroups {
if group.VPCID.EqualTo(vpc.ID.Value()) {
state.AWS.EC2.VPCs[i].SecurityGroups = append(state.AWS.EC2.VPCs[i].SecurityGroups, group)
}
}
}

return nil
}

Expand Down
41 changes: 21 additions & 20 deletions internal/adapters/cloud/aws/ec2/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,27 +55,27 @@ func (a *adapter) getNetworkACLs() (nacls []ec2.NetworkACL, err error) {
return concurrency.Adapt(apiNetworkACLs, a.RootAdapter, a.adaptNetworkACL), nil
}

func (a *adapter) getDefaultVPCs() (defaultVpcs []ec2.DefaultVPC, err error) {
func (a *adapter) getVPCs() (defaultVpcs []ec2.VPC, err error) {

a.Tracker().SetServiceLabel("Discovering default VPCs...")
var apiDefaultVPCs []types.Vpc
a.Tracker().SetServiceLabel("Discovering VPCs...")
var apiVPCs []types.Vpc
var input ec2api.DescribeVpcsInput

for {
output, err := a.client.DescribeVpcs(a.Context(), &input)
if err != nil {
return nil, err
}
apiDefaultVPCs = append(apiDefaultVPCs, output.Vpcs...)
a.Tracker().SetTotalResources(len(apiDefaultVPCs))
apiVPCs = append(apiVPCs, output.Vpcs...)
a.Tracker().SetTotalResources(len(apiVPCs))
if output.NextToken == nil {
break
}
input.NextToken = output.NextToken
}

a.Tracker().SetServiceLabel("Adapting default VPCs...")
return concurrency.Adapt(apiDefaultVPCs, a.RootAdapter, a.adaptVPC), nil
a.Tracker().SetServiceLabel("Adapting VPCs...")
return concurrency.Adapt(apiVPCs, a.RootAdapter, a.adaptVPC), nil
}

func (a *adapter) adaptSecurityGroup(apiSecurityGroup types.SecurityGroup) (*ec2.SecurityGroup, error) {
Expand All @@ -84,7 +84,13 @@ func (a *adapter) adaptSecurityGroup(apiSecurityGroup types.SecurityGroup) (*ec2

sg := &ec2.SecurityGroup{
Metadata: sgMetadata,
IsDefault: defsecTypes.BoolDefault(apiSecurityGroup.GroupName != nil && *apiSecurityGroup.GroupName == "default", sgMetadata),
Description: defsecTypes.String(aws.ToString(apiSecurityGroup.Description), sgMetadata),
VPCID: defsecTypes.StringDefault("", sgMetadata),
}

if apiSecurityGroup.VpcId != nil {
sg.VPCID = defsecTypes.String(*apiSecurityGroup.VpcId, sgMetadata)
}

for _, ingress := range apiSecurityGroup.IpPermissions {
Expand Down Expand Up @@ -139,17 +145,12 @@ func (a *adapter) adaptNetworkACL(apiNacl types.NetworkAcl) (*ec2.NetworkACL, er
return nacl, nil
}

func (a *adapter) adaptVPC(v types.Vpc) (*ec2.DefaultVPC, error) {

if aws.ToBool(v.IsDefault) {

vpcMetadata := a.CreateMetadata("vpc/" + *v.VpcId)

return &ec2.DefaultVPC{
Metadata: vpcMetadata,
}, nil
}

return nil, nil

func (a *adapter) adaptVPC(v types.Vpc) (*ec2.VPC, error) {
vpcMetadata := a.CreateMetadata("vpc/" + *v.VpcId)
return &ec2.VPC{
Metadata: vpcMetadata,
ID: defsecTypes.String(*v.VpcId, vpcMetadata),
IsDefault: defsecTypes.Bool(v.IsDefault != nil && *v.IsDefault, vpcMetadata),
SecurityGroups: nil, // we link these up afterwards
}, nil
}
2 changes: 1 addition & 1 deletion internal/adapters/cloudformation/aws/ec2/ec2.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ func Adapt(cfFile parser.FileContext) ec2.EC2 {
LaunchConfigurations: getLaunchConfigurations(cfFile),
LaunchTemplates: getLaunchTemplates(cfFile),
Instances: getInstances(cfFile),
DefaultVPCs: nil,
VPCs: nil,
NetworkACLs: getNetworkACLs(cfFile),
SecurityGroups: getSecurityGroups(cfFile),
Subnets: getSubnets(cfFile),
Expand Down
2 changes: 2 additions & 0 deletions internal/adapters/cloudformation/aws/ec2/security_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ func getSecurityGroups(ctx parser.FileContext) (groups []ec2.SecurityGroup) {
Description: r.GetStringProperty("GroupDescription"),
IngressRules: getIngressRules(r),
EgressRules: getEgressRules(r),
IsDefault: types.Bool(r.GetStringProperty("GroupName").EqualTo("default"), r.Metadata()),
VPCID: r.GetStringProperty("VpcId"),
}

groups = append(groups, group)
Expand Down
2 changes: 1 addition & 1 deletion internal/adapters/terraform/aws/ec2/adapt.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func Adapt(modules terraform.Modules) ec2.EC2 {

return ec2.EC2{
Instances: getInstances(modules),
DefaultVPCs: adaptDefaultVPCs(modules),
VPCs: adaptVPCs(modules),
SecurityGroups: sgAdapter.adaptSecurityGroups(modules),
Subnets: adaptSubnets(modules),
NetworkACLs: naclAdapter.adaptNetworkACLs(modules),
Expand Down
25 changes: 20 additions & 5 deletions internal/adapters/terraform/aws/ec2/vpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,27 @@ type sgAdapter struct {
sgRuleIDs terraform.ResourceIDResolutions
}

func adaptDefaultVPCs(modules terraform.Modules) []ec2.DefaultVPC {
var defaultVPCs []ec2.DefaultVPC
func adaptVPCs(modules terraform.Modules) []ec2.VPC {
var vpcs []ec2.VPC
for _, module := range modules {
for _, resource := range module.GetResourcesByType("aws_default_vpc") {
defaultVPCs = append(defaultVPCs, ec2.DefaultVPC{
Metadata: resource.GetMetadata(),
vpcs = append(vpcs, ec2.VPC{
Metadata: resource.GetMetadata(),
ID: defsecTypes.StringUnresolvable(resource.GetMetadata()),
IsDefault: defsecTypes.Bool(true, resource.GetMetadata()),
SecurityGroups: nil,
})
}
for _, resource := range module.GetResourcesByType("aws_vpc") {
vpcs = append(vpcs, ec2.VPC{
Metadata: resource.GetMetadata(),
ID: defsecTypes.StringUnresolvable(resource.GetMetadata()),
IsDefault: defsecTypes.Bool(false, resource.GetMetadata()),
SecurityGroups: nil,
})
}
}
return defaultVPCs
return vpcs
}

func (a *sgAdapter) adaptSecurityGroups(modules terraform.Modules) []ec2.SecurityGroup {
Expand All @@ -38,6 +49,8 @@ func (a *sgAdapter) adaptSecurityGroups(modules terraform.Modules) []ec2.Securit
Description: defsecTypes.StringDefault("", defsecTypes.NewUnmanagedMetadata()),
IngressRules: nil,
EgressRules: nil,
IsDefault: defsecTypes.BoolUnresolvable(defsecTypes.NewUnmanagedMetadata()),
VPCID: defsecTypes.StringUnresolvable(defsecTypes.NewUnmanagedMetadata()),
}
for _, sgRule := range orphanResources {
if sgRule.GetAttribute("type").Equals("ingress") {
Expand Down Expand Up @@ -108,6 +121,8 @@ func (a *sgAdapter) adaptSecurityGroup(resource *terraform.Block, module terrafo
Description: descriptionVal,
IngressRules: ingressRules,
EgressRules: egressRules,
IsDefault: defsecTypes.Bool(false, defsecTypes.NewUnmanagedMetadata()),
VPCID: resource.GetAttribute("vpc_id").AsStringValueOrDefault("", resource),
}
}

Expand Down
19 changes: 15 additions & 4 deletions internal/adapters/terraform/aws/ec2/vpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,24 @@ func Test_AdaptVPC(t *testing.T) {
}
`,
expected: ec2.EC2{
DefaultVPCs: []ec2.DefaultVPC{
VPCs: []ec2.VPC{
{
Metadata: defsecTypes.NewTestMetadata(),
Metadata: defsecTypes.NewTestMetadata(),
IsDefault: defsecTypes.Bool(true, defsecTypes.NewTestMetadata()),
ID: defsecTypes.String("", defsecTypes.NewTestMetadata()),
},
{
Metadata: defsecTypes.NewTestMetadata(),
IsDefault: defsecTypes.Bool(false, defsecTypes.NewTestMetadata()),
ID: defsecTypes.String("", defsecTypes.NewTestMetadata()),
},
},
SecurityGroups: []ec2.SecurityGroup{
{
Metadata: defsecTypes.NewTestMetadata(),
Description: defsecTypes.String("Allow inbound HTTP traffic", defsecTypes.NewTestMetadata()),
IsDefault: defsecTypes.Bool(false, defsecTypes.NewTestMetadata()),
VPCID: defsecTypes.String("", defsecTypes.NewTestMetadata()),
IngressRules: []ec2.SecurityGroupRule{
{
Metadata: defsecTypes.NewTestMetadata(),
Expand Down Expand Up @@ -151,6 +160,8 @@ func Test_AdaptVPC(t *testing.T) {
{
Metadata: defsecTypes.NewTestMetadata(),
Description: defsecTypes.String("Managed by Terraform", defsecTypes.NewTestMetadata()),
IsDefault: defsecTypes.Bool(false, defsecTypes.NewTestMetadata()),
VPCID: defsecTypes.String("", defsecTypes.NewTestMetadata()),
IngressRules: []ec2.SecurityGroupRule{
{
Metadata: defsecTypes.NewTestMetadata(),
Expand Down Expand Up @@ -244,11 +255,11 @@ func TestVPCLines(t *testing.T) {
modules := tftestutil.CreateModulesFromSource(t, src, ".tf")
adapted := Adapt(modules)

require.Len(t, adapted.DefaultVPCs, 1)
require.Len(t, adapted.VPCs, 2)
require.Len(t, adapted.SecurityGroups, 1)
require.Len(t, adapted.NetworkACLs, 1)

defaultVPC := adapted.DefaultVPCs[0]
defaultVPC := adapted.VPCs[0]
securityGroup := adapted.SecurityGroups[0]
networkACL := adapted.NetworkACLs[0]

Expand Down
47 changes: 47 additions & 0 deletions internal/rules/aws/cloudwatch/require_org_changes_alarm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package cloudwatch

import (
"github.com/aquasecurity/defsec/internal/rules"
"github.com/aquasecurity/defsec/pkg/framework"
"github.com/aquasecurity/defsec/pkg/providers"
"github.com/aquasecurity/defsec/pkg/scan"
"github.com/aquasecurity/defsec/pkg/severity"
"github.com/aquasecurity/defsec/pkg/state"
"github.com/aquasecurity/defsec/pkg/types"
)

var CheckRequireOrgChangesAlarm = rules.Register(
scan.Rule{
AVDID: "AVD-AWS-0174",
Provider: providers.AWSProvider,
Service: "cloudwatch",
ShortCode: "require-org-changes-alarm",
Summary: "Ensure a log metric filter and alarm exist for organisation changes",
Impact: "Lack of observability into critical organisation changes",
Resolution: "Create an alarm to alert on organisation changes",
Frameworks: map[framework.Framework][]string{
framework.CIS_AWS_1_4: {
"4.15",
},
},
Explanation: `
Monitoring AWS Organizations changes can help you prevent any unwanted, accidental or
intentional modifications that may lead to unauthorized access or other security breaches.
This monitoring technique helps you to ensure that any unexpected changes performed
within your AWS Organizations can be investigated and any unwanted changes can be
rolled back.
`,
Links: []string{
"https://docs.aws.amazon.com/organizations/latest/userguide/orgs_security_incident-response.html",
},
Severity: severity.Low,
},
func(s *state.State) (results scan.Results) {
if metricAlarm := s.AWS.CloudWatch.GetAlarmByMetricName("OrganizationEvents"); metricAlarm == nil {
results.Add("CloudWatch has no alarm associated with organisation events", types.NewUnmanagedMetadata())
} else {
results.AddPassed(metricAlarm)
}
return
},
)
56 changes: 56 additions & 0 deletions internal/rules/aws/cloudwatch/require_org_changes_alarm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package cloudwatch

import (
"testing"

defsecTypes "github.com/aquasecurity/defsec/pkg/types"

"github.com/aquasecurity/defsec/pkg/providers/aws/cloudwatch"
"github.com/aquasecurity/defsec/pkg/scan"
"github.com/aquasecurity/defsec/pkg/state"
"github.com/stretchr/testify/assert"
)

func TestCheckRequireOrgChangesAlarm(t *testing.T) {
tests := []struct {
name string
cloudwatch cloudwatch.CloudWatch
expected bool
}{
{
name: "alarm exists",
cloudwatch: cloudwatch.CloudWatch{
Alarms: []cloudwatch.Alarm{
{
Metadata: defsecTypes.NewTestMetadata(),
MetricName: defsecTypes.String("OrganizationEvents", defsecTypes.NewTestMetadata()),
},
},
},
expected: false,
},
{
name: "alarm does not exist",
cloudwatch: cloudwatch.CloudWatch{},
expected: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var testState state.State
testState.AWS.CloudWatch = test.cloudwatch
results := CheckRequireOrgChangesAlarm.Evaluate(&testState)
var found bool
for _, result := range results {
if result.Status() == scan.StatusFailed && result.Rule().LongID() == CheckRequireOrgChangesAlarm.Rule().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")
}
})
}
}
Loading

0 comments on commit 3896451

Please sign in to comment.