diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go b/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go index ac05f8f7b263..01fb201768e9 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go @@ -272,6 +272,57 @@ Resources: }, }, }, + { + name: "security group with ingress and egress rules", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MySecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupName: MySecurityGroup + GroupDescription: MySecurityGroup + InboundRule: + Type: AWS::EC2::SecurityGroupIngress + Properties: + GroupId: !Ref MySecurityGroup + Description: Inbound + CidrIp: 0.0.0.0/0 + OutboundRule: + Type: AWS::EC2::SecurityGroupEgress + Properties: + GroupId: !GetAtt MySecurityGroup.GroupId + Description: Outbound + CidrIp: 0.0.0.0/0 + RuleWithoutGroup: + Type: AWS::EC2::SecurityGroupIngress + Properties: + CidrIpv6: ::/0 + Description: Inbound +`, + expected: ec2.EC2{ + SecurityGroups: []ec2.SecurityGroup{ + { + Description: types.StringTest("MySecurityGroup"), + IngressRules: []ec2.SecurityGroupRule{ + { + Description: types.StringTest("Inbound"), + CIDRs: []types.StringValue{ + types.StringTest("0.0.0.0/0"), + }, + }, + }, + EgressRules: []ec2.SecurityGroupRule{ + { + Description: types.StringTest("Outbound"), + CIDRs: []types.StringValue{ + types.StringTest("0.0.0.0/0"), + }, + }, + }, + }, + }, + }, + }, } for _, tt := range tests { diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go b/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go index 72546aa116e0..6de47f302682 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go @@ -1,12 +1,16 @@ package ec2 import ( + "golang.org/x/exp/maps" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getSecurityGroups(ctx parser.FileContext) (groups []ec2.SecurityGroup) { +func getSecurityGroups(ctx parser.FileContext) []ec2.SecurityGroup { + mGroups := make(map[string]ec2.SecurityGroup) + for _, r := range ctx.GetResourcesByType("AWS::EC2::SecurityGroup") { group := ec2.SecurityGroup{ Metadata: r.Metadata(), @@ -17,52 +21,69 @@ func getSecurityGroups(ctx parser.FileContext) (groups []ec2.SecurityGroup) { VPCID: r.GetStringProperty("VpcId"), } - groups = append(groups, group) + mGroups[r.ID()] = group + } + + for _, r := range ctx.GetResourcesByType("AWS::EC2::SecurityGroupIngress") { + groupID := r.GetProperty("GroupId").AsString() + + if group, ok := mGroups[groupID]; ok { + group.IngressRules = append(group.IngressRules, adaptRule(r)) + mGroups[groupID] = group + } } - return groups + + for _, r := range ctx.GetResourcesByType("AWS::EC2::SecurityGroupEgress") { + groupID := r.GetProperty("GroupId").AsString() + + if group, ok := mGroups[groupID]; ok { + group.EgressRules = append(group.EgressRules, adaptRule(r)) + mGroups[groupID] = group + } + } + + if len(mGroups) > 0 { + return maps.Values(mGroups) + } + return nil } func getIngressRules(r *parser.Resource) (sgRules []ec2.SecurityGroupRule) { if ingressProp := r.GetProperty("SecurityGroupIngress"); ingressProp.IsList() { for _, ingress := range ingressProp.AsList() { - rule := ec2.SecurityGroupRule{ - Metadata: ingress.Metadata(), - Description: ingress.GetStringProperty("Description"), - CIDRs: nil, - } - v4Cidr := ingress.GetProperty("CidrIp") - if v4Cidr.IsString() && v4Cidr.AsStringValue().IsNotEmpty() { - rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v4Cidr.AsString(), v4Cidr.Metadata())) - } - v6Cidr := ingress.GetProperty("CidrIpv6") - if v6Cidr.IsString() && v6Cidr.AsStringValue().IsNotEmpty() { - rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v6Cidr.AsString(), v6Cidr.Metadata())) - } - - sgRules = append(sgRules, rule) + sgRules = append(sgRules, adaptRule(ingress)) } } + return sgRules } func getEgressRules(r *parser.Resource) (sgRules []ec2.SecurityGroupRule) { if egressProp := r.GetProperty("SecurityGroupEgress"); egressProp.IsList() { for _, egress := range egressProp.AsList() { - rule := ec2.SecurityGroupRule{ - Metadata: egress.Metadata(), - Description: egress.GetStringProperty("Description"), - } - v4Cidr := egress.GetProperty("CidrIp") - if v4Cidr.IsString() && v4Cidr.AsStringValue().IsNotEmpty() { - rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v4Cidr.AsString(), v4Cidr.Metadata())) - } - v6Cidr := egress.GetProperty("CidrIpv6") - if v6Cidr.IsString() && v6Cidr.AsStringValue().IsNotEmpty() { - rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v6Cidr.AsString(), v6Cidr.Metadata())) - } - - sgRules = append(sgRules, rule) + sgRules = append(sgRules, adaptRule(egress)) } } return sgRules } + +func adaptRule(r interface { + GetProperty(string) *parser.Property + Metadata() types.Metadata + GetStringProperty(string, ...string) types.StringValue +}) ec2.SecurityGroupRule { + rule := ec2.SecurityGroupRule{ + Metadata: r.Metadata(), + Description: r.GetStringProperty("Description"), + } + v4Cidr := r.GetProperty("CidrIp") + if v4Cidr.IsString() && v4Cidr.AsStringValue().IsNotEmpty() { + rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v4Cidr.AsString(), v4Cidr.Metadata())) + } + v6Cidr := r.GetProperty("CidrIpv6") + if v6Cidr.IsString() && v6Cidr.AsStringValue().IsNotEmpty() { + rule.CIDRs = append(rule.CIDRs, types.StringExplicit(v6Cidr.AsString(), v6Cidr.Metadata())) + } + + return rule +}