Skip to content

Commit

Permalink
Support locals other than 0.0.0.0/32 (#225)
Browse files Browse the repository at this point in the history
  • Loading branch information
YairSlobodin1 authored Dec 19, 2024
1 parent f82ccd5 commit 8c830d1
Show file tree
Hide file tree
Showing 30 changed files with 421 additions and 155 deletions.
2 changes: 2 additions & 0 deletions pkg/io/commonSG.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func makeSGHeader() [][]string {
return [][]string{{
"SG",
"Direction",
"Local",
"Remote type",
"Remote",
"Protocol",
Expand Down Expand Up @@ -69,6 +70,7 @@ func makeSGRow(rule *ir.SGRule, sgName ir.SGName) ([]string, error) {
return []string{
string(sgName),
direction(rule.Direction),
rule.Local.String(),
remoteType,
remote,
printProtocolName(rule.Protocol),
Expand Down
35 changes: 12 additions & 23 deletions pkg/io/confio/parse_sgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ func ReadSGs(filename string) (*ir.SGCollection, error) {

result := ir.NewSGCollection()
for i, sg := range config.SecurityGroupList {
inbound, outbound, err := translateSGRules(&sg.SecurityGroup)
if err != nil {
return nil, err
}
if sg.Name == nil || sg.VPC == nil || sg.VPC.Name == nil {
log.Printf("Warning: missing SG/VPC name in sg at index %d\n", i)
continue
}
inbound, outbound, err := translateSGRules(&sg.SecurityGroup)
if err != nil {
return nil, err
}
sgName := ir.SGName(*sg.Name)
vpcName := *sg.VPC.Name
if result.SGs[vpcName] == nil {
Expand All @@ -52,16 +52,19 @@ func ReadSGs(filename string) (*ir.SGCollection, error) {
}

// parse security rules, splitted into ingress and egress rules
func translateSGRules(sg *vpcv1.SecurityGroup) (ingressRules, egressRules []*ir.SGRule, err error) {
func translateSGRules(sg *vpcv1.SecurityGroup) (ingressRules, egressRules map[string][]*ir.SGRule, err error) {
ingressRules = make(map[string][]*ir.SGRule)
egressRules = make(map[string][]*ir.SGRule)
for index := range sg.Rules {
rule, err := translateSGRule(sg, index)
if err != nil {
return nil, nil, err
}
local := rule.Local.String()
if rule.Direction == ir.Inbound {
ingressRules = append(ingressRules, rule)
ingressRules[local] = append(ingressRules[local], rule)
} else {
egressRules = append(egressRules, rule)
egressRules[local] = append(egressRules[local], rule)
}
}
return ingressRules, egressRules, nil
Expand Down Expand Up @@ -136,19 +139,13 @@ func translateRemote(remote vpcv1.SecurityGroupRuleRemoteIntf) (ir.RemoteType, e
}

func translateLocal(local vpcv1.SecurityGroupRuleLocalIntf) (*netset.IPBlock, error) {
var err error
var ipAddrs *netset.IPBlock
if l, ok := local.(*vpcv1.SecurityGroupRuleLocal); ok {
if l.CIDRBlock != nil {
ipAddrs, err = netset.IPBlockFromCidr(*l.CIDRBlock)
return netset.IPBlockFromCidr(*l.CIDRBlock)
}
if l.Address != nil {
ipAddrs, err = netset.IPBlockFromIPAddress(*l.CIDRBlock)
}
if err != nil {
return nil, err
return netset.IPBlockFromIPAddress(*l.Address)
}
return verifyLocalValue(ipAddrs)
}
return nil, fmt.Errorf("error parsing Local field")
}
Expand All @@ -169,14 +166,6 @@ func translateTargets(sg *vpcv1.SecurityGroup) []string {
return res
}

// temporary - first version of optimization requires local = 0.0.0.0/32
func verifyLocalValue(ipAddrs *netset.IPBlock) (*netset.IPBlock, error) {
if !ipAddrs.Equal(netset.GetCidrAll()) {
return nil, fmt.Errorf("only 0.0.0.0/32 CIDR block is supported for local values")
}
return ipAddrs, nil
}

func translateProtocolTCPUDP(rule *vpcv1.SecurityGroupRuleSecurityGroupRuleProtocolTcpudp) (netp.Protocol, error) {
isTCP := *rule.Protocol == vpcv1.SecurityGroupRuleSecurityGroupRuleProtocolTcpudpProtocolTCPConst
minDstPort := utils.GetProperty(rule.PortMin, netp.MinPort)
Expand Down
1 change: 1 addition & 0 deletions pkg/io/tfio/sg.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ func sgRule(rule *ir.SGRule, sgName ir.SGName, i int) (tf.Block, error) {
Arguments: []tf.Argument{
{Name: "group", Value: group},
{Name: "direction", Value: quote(direction(rule.Direction))},
{Name: "local", Value: quote(rule.Local.String())},
{Name: "remote", Value: remote},
},
Blocks: sgProtocol(rule.Protocol),
Expand Down
28 changes: 20 additions & 8 deletions pkg/ir/sg.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ type (

SG struct {
SGName SGName
InboundRules []*SGRule
OutboundRules []*SGRule
InboundRules map[string][]*SGRule // the key is the locals value
OutboundRules map[string][]*SGRule // the key is the locals value
Targets []ID
}

Expand Down Expand Up @@ -75,7 +75,10 @@ func NewSGRule(direction Direction, remote RemoteType, p netp.Protocol, local *n
}

func NewSG(sgName SGName) *SG {
return &SG{SGName: sgName, InboundRules: []*SGRule{}, OutboundRules: []*SGRule{}, Targets: []ID{}}
return &SG{SGName: sgName,
InboundRules: make(map[string][]*SGRule),
OutboundRules: make(map[string][]*SGRule),
}
}

func NewSGCollection() *SGCollection {
Expand All @@ -96,16 +99,25 @@ func (c *SGCollection) LookupOrCreate(name SGName) *SG {
}

func (a *SG) Add(rule *SGRule) {
if rule.Direction == Outbound && !rule.isRedundant(a.OutboundRules) {
a.OutboundRules = append(a.OutboundRules, rule)
local := rule.Local.String()
if rule.Direction == Outbound && !rule.isRedundant(a.OutboundRules[local]) {
a.OutboundRules[local] = append(a.OutboundRules[local], rule)
}
if rule.Direction == Inbound && !rule.isRedundant(a.InboundRules) {
a.InboundRules = append(a.InboundRules, rule)

if rule.Direction == Inbound && !rule.isRedundant(a.InboundRules[local]) {
a.InboundRules[local] = append(a.InboundRules[local], rule)
}
}

func (a *SG) AllRules() []*SGRule {
return slices.Concat(a.InboundRules, a.OutboundRules)
res := make([]*SGRule, 0)
for _, key := range utils.SortedMapKeys(a.InboundRules) {
res = slices.Concat(res, a.InboundRules[key])
}
for _, key := range utils.SortedMapKeys(a.OutboundRules) {
res = slices.Concat(res, a.OutboundRules[key])
}
return res
}

func (c *SGCollection) VpcNames() []string {
Expand Down
31 changes: 16 additions & 15 deletions pkg/optimize/sg/ipCubesToRules.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ import (

// any protocol IP-segments, represented by a single ipblock that will be decomposed
// into cidrs. Each cidr will be a remote of a single SG rule
func anyProtocolIPCubesToRules(cubes *netset.IPBlock, direction ir.Direction) []*ir.SGRule {
func anyProtocolIPCubesToRules(cubes *netset.IPBlock, direction ir.Direction, l *netset.IPBlock) []*ir.SGRule {
result := make([]*ir.SGRule, 0)
for _, cidr := range cubes.SplitToCidrs() {
result = append(result, ir.NewSGRule(direction, cidr, netp.AnyProtocol{}, netset.GetCidrAll(), ""))
result = append(result, ir.NewSGRule(direction, cidr, netp.AnyProtocol{}, l, ""))
}
return result
}

// tcpudpIPCubesToRules converts cubes representing tcp or udp protocol rules to SG rules
func tcpudpIPCubesToRules(cubes []ds.Pair[*netset.IPBlock, *netset.PortSet], anyProtocolCubes *netset.IPBlock, direction ir.Direction,
isTCP bool) []*ir.SGRule {
isTCP bool, l *netset.IPBlock) []*ir.SGRule {
if len(cubes) == 0 {
return []*ir.SGRule{}
}
Expand All @@ -40,7 +40,7 @@ func tcpudpIPCubesToRules(cubes []ds.Pair[*netset.IPBlock, *netset.PortSet], any
for i := range cubes {
// if it is not possible to continue the rule between the cubes, generate all existing rules
if i > 0 && uncoveredHole(cubes[i-1], cubes[i], anyProtocolCubes) {
res = slices.Concat(res, createActiveRules(activeRules, cubes[i-1].Left.LastIPAddressObject(), direction))
res = slices.Concat(res, createActiveRules(activeRules, cubes[i-1].Left.LastIPAddressObject(), direction, l))
activeRules = make(map[*netset.IPBlock]netp.Protocol)
}

Expand All @@ -50,7 +50,7 @@ func tcpudpIPCubesToRules(cubes []ds.Pair[*netset.IPBlock, *netset.PortSet], any
for startIP, protocol := range activeRules {
tcpudp, _ := protocol.(netp.TCPUDP) // already checked
if !tcpudp.DstPorts().ToSet().IsSubset(cubes[i].Right) {
res = slices.Concat(res, createNewRules(protocol, startIP, cubes[i-1].Left.LastIPAddressObject(), direction))
res = slices.Concat(res, createNewRules(protocol, startIP, cubes[i-1].Left.LastIPAddressObject(), direction, l))
delete(activeRules, startIP)
} else {
activePorts.AddInterval(tcpudp.DstPorts())
Expand All @@ -66,12 +66,12 @@ func tcpudpIPCubesToRules(cubes []ds.Pair[*netset.IPBlock, *netset.PortSet], any
}
}
// generate all existing rules
return slices.Concat(res, createActiveRules(activeRules, cubes[len(cubes)-1].Left.LastIPAddressObject(), direction))
return slices.Concat(res, createActiveRules(activeRules, cubes[len(cubes)-1].Left.LastIPAddressObject(), direction, l))
}

// icmpIPCubesToRules converts cubes representing icmp protocol rules to SG rules
func icmpIPCubesToRules(cubes []ds.Pair[*netset.IPBlock, *netset.ICMPSet], anyProtocolCubes *netset.IPBlock,
direction ir.Direction) []*ir.SGRule {
func icmpIPCubesToRules(cubes []ds.Pair[*netset.IPBlock, *netset.ICMPSet], anyProtocolCubes *netset.IPBlock, direction ir.Direction,
l *netset.IPBlock) []*ir.SGRule {
if len(cubes) == 0 {
return []*ir.SGRule{}
}
Expand All @@ -82,7 +82,7 @@ func icmpIPCubesToRules(cubes []ds.Pair[*netset.IPBlock, *netset.ICMPSet], anyPr
for i := range cubes {
// if it is not possible to continue the rule between the cubes, generate all existing rules
if i > 0 && uncoveredHole(cubes[i-1], cubes[i], anyProtocolCubes) {
res = slices.Concat(res, createActiveRules(activeRules, cubes[i-1].Left.LastIPAddressObject(), direction))
res = slices.Concat(res, createActiveRules(activeRules, cubes[i-1].Left.LastIPAddressObject(), direction, l))
activeRules = make(map[*netset.IPBlock]netp.Protocol)
}

Expand All @@ -93,7 +93,7 @@ func icmpIPCubesToRules(cubes []ds.Pair[*netset.IPBlock, *netset.ICMPSet], anyPr
icmp, _ := protocol.(netp.ICMP)
ruleIcmpSet := optimize.IcmpRuleToIcmpSet(icmp)
if !ruleIcmpSet.IsSubset(cubes[i].Right) {
res = slices.Concat(res, createNewRules(protocol, startIP, cubes[i-1].Left.LastIPAddressObject(), direction))
res = slices.Concat(res, createNewRules(protocol, startIP, cubes[i-1].Left.LastIPAddressObject(), direction, l))
delete(activeRules, startIP)
} else {
activeICMP.Union(ruleIcmpSet)
Expand All @@ -109,7 +109,7 @@ func icmpIPCubesToRules(cubes []ds.Pair[*netset.IPBlock, *netset.ICMPSet], anyPr
}

// generate all existing rules
return slices.Concat(res, createActiveRules(activeRules, cubes[len(cubes)-1].Left.LastIPAddressObject(), direction))
return slices.Concat(res, createActiveRules(activeRules, cubes[len(cubes)-1].Left.LastIPAddressObject(), direction, l))
}

// uncoveredHole returns true if the rules can not be continued between the two cubes
Expand All @@ -128,20 +128,21 @@ func uncoveredHole[T ds.Set[T]](prevPair, currPair ds.Pair[*netset.IPBlock, T],
}

// creates sgRules from SG active rules
func createActiveRules(activeRules map[*netset.IPBlock]netp.Protocol, lastIP *netset.IPBlock, direction ir.Direction) []*ir.SGRule {
func createActiveRules(activeRules map[*netset.IPBlock]netp.Protocol, lastIP *netset.IPBlock,
direction ir.Direction, l *netset.IPBlock) []*ir.SGRule {
res := make([]*ir.SGRule, 0)
for firstIP, protocol := range activeRules {
res = slices.Concat(res, createNewRules(protocol, firstIP, lastIP, direction))
res = slices.Concat(res, createNewRules(protocol, firstIP, lastIP, direction, l))
}
return res
}

// createNewRules breaks the startIP-endIP ip range into cidrs and creates SG rules
func createNewRules(protocol netp.Protocol, startIP, endIP *netset.IPBlock, direction ir.Direction) []*ir.SGRule {
func createNewRules(protocol netp.Protocol, startIP, endIP *netset.IPBlock, direction ir.Direction, l *netset.IPBlock) []*ir.SGRule {
res := make([]*ir.SGRule, 0)
ipRange, _ := netset.IPBlockFromIPRange(startIP, endIP)
for _, cidr := range ipRange.SplitToCidrs() {
res = append(res, ir.NewSGRule(direction, cidr, protocol, netset.GetCidrAll(), ""))
res = append(res, ir.NewSGRule(direction, cidr, protocol, l, ""))
}
return res
}
48 changes: 27 additions & 21 deletions pkg/optimize/sg/sg.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,23 @@ func (s *sgOptimizer) optimizeSG(sg *ir.SG) {
reducedRules := 0

// reduce inbound rules first
newInboundRules := s.reduceSGRules(sg.InboundRules, ir.Inbound)
if len(sg.InboundRules) > len(newInboundRules) {
reducedRules += len(sg.InboundRules) - len(newInboundRules)
sg.InboundRules = newInboundRules
for l, rules := range sg.InboundRules {
local, _ := netset.IPBlockFromCidr(l)
newInboundRules := s.reduceSGRules(rules, ir.Inbound, local)
if len(rules) > len(newInboundRules) {
reducedRules += len(rules) - len(newInboundRules)
sg.InboundRules[l] = newInboundRules
}
}

// reduce outbound rules second
newOutboundRules := s.reduceSGRules(sg.OutboundRules, ir.Outbound)
if len(sg.OutboundRules) > len(newOutboundRules) {
reducedRules += len(sg.OutboundRules) - len(newOutboundRules)
sg.OutboundRules = newOutboundRules
for l, rules := range sg.OutboundRules {
local, _ := netset.IPBlockFromCidr(l)
newOutboundRules := s.reduceSGRules(rules, ir.Outbound, local)
if len(rules) > len(newOutboundRules) {
reducedRules += len(rules) - len(newOutboundRules)
sg.OutboundRules[l] = newOutboundRules
}
}

// print a message to the log
Expand All @@ -117,19 +123,19 @@ func (s *sgOptimizer) optimizeSG(sg *ir.SG) {
}

// reduceSGRules attempts to reduce the number of rules with different remote types separately
func (s *sgOptimizer) reduceSGRules(rules []*ir.SGRule, direction ir.Direction) []*ir.SGRule {
func (s *sgOptimizer) reduceSGRules(rules []*ir.SGRule, direction ir.Direction, l *netset.IPBlock) []*ir.SGRule {
// separate all rules to groups of protocol X remote ([tcp, udp, icmp, protocolAll] X [ip, sg])
ruleGroups := divideSGRules(rules)

// rules with SG as a remote
optimizedRulesToSG := reduceRulesSGRemote(rulesToSGCubes(ruleGroups.sgRemoteRules), direction)
optimizedRulesToSG := reduceRulesSGRemote(rulesToSGCubes(ruleGroups.sgRemoteRules), direction, l)
originalRulesToSG := ruleGroups.sgRemoteRules.allRules()
if len(originalRulesToSG) <= len(optimizedRulesToSG) { // failed to reduce number of rules
optimizedRulesToSG = originalRulesToSG
}

// rules with IPBlock as a remote
optimizedRulesToIPAddrs := reduceRulesIPRemote(rulesToIPCubes(ruleGroups.ipRemoteRules), direction)
optimizedRulesToIPAddrs := reduceRulesIPRemote(rulesToIPCubes(ruleGroups.ipRemoteRules), direction, l)
originalRulesToIPAddrs := ruleGroups.ipRemoteRules.allRules()
if len(originalRulesToIPAddrs) <= len(optimizedRulesToSG) { // failed to reduce number of rules
optimizedRulesToIPAddrs = originalRulesToIPAddrs
Expand All @@ -138,27 +144,27 @@ func (s *sgOptimizer) reduceSGRules(rules []*ir.SGRule, direction ir.Direction)
return slices.Concat(optimizedRulesToSG, optimizedRulesToIPAddrs)
}

func reduceRulesSGRemote(cubes *sgCubesPerProtocol, direction ir.Direction) []*ir.SGRule {
func reduceRulesSGRemote(cubes *sgCubesPerProtocol, direction ir.Direction, l *netset.IPBlock) []*ir.SGRule {
reduceCubesWithSGRemote(cubes)

// cubes to SG rules
tcpRules := tcpudpSGCubesToRules(cubes.tcp, direction, true)
udpRules := tcpudpSGCubesToRules(cubes.udp, direction, false)
icmpRules := icmpSGCubesToRules(cubes.icmp, direction)
anyProtocolRules := anyProtocolCubesToRules(cubes.anyProtocol, direction)
tcpRules := tcpudpSGCubesToRules(cubes.tcp, direction, true, l)
udpRules := tcpudpSGCubesToRules(cubes.udp, direction, false, l)
icmpRules := icmpSGCubesToRules(cubes.icmp, direction, l)
anyProtocolRules := anyProtocolCubesToRules(cubes.anyProtocol, direction, l)

// return all rules
return slices.Concat(tcpRules, udpRules, icmpRules, anyProtocolRules)
}

func reduceRulesIPRemote(cubes *ipCubesPerProtocol, direction ir.Direction) []*ir.SGRule {
func reduceRulesIPRemote(cubes *ipCubesPerProtocol, direction ir.Direction, l *netset.IPBlock) []*ir.SGRule {
reduceIPCubes(cubes)

// cubes to SG rules
tcpRules := tcpudpIPCubesToRules(cubes.tcp, cubes.anyProtocol, direction, true)
udpRules := tcpudpIPCubesToRules(cubes.udp, cubes.anyProtocol, direction, false)
icmpRules := icmpIPCubesToRules(cubes.icmp, cubes.anyProtocol, direction)
anyProtocolRules := anyProtocolIPCubesToRules(cubes.anyProtocol, direction)
tcpRules := tcpudpIPCubesToRules(cubes.tcp, cubes.anyProtocol, direction, true, l)
udpRules := tcpudpIPCubesToRules(cubes.udp, cubes.anyProtocol, direction, false, l)
icmpRules := icmpIPCubesToRules(cubes.icmp, cubes.anyProtocol, direction, l)
anyProtocolRules := anyProtocolIPCubesToRules(cubes.anyProtocol, direction, l)

// return all rules
return slices.Concat(tcpRules, udpRules, icmpRules, anyProtocolRules)
Expand Down
12 changes: 6 additions & 6 deletions pkg/optimize/sg/sgCubesToRules.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,33 @@ import (
)

// cubes (SGName X portSet) to SG rules
func tcpudpSGCubesToRules(cubes map[ir.SGName]*netset.PortSet, direction ir.Direction, isTCP bool) []*ir.SGRule {
func tcpudpSGCubesToRules(cubes map[ir.SGName]*netset.PortSet, direction ir.Direction, isTCP bool, l *netset.IPBlock) []*ir.SGRule {
result := make([]*ir.SGRule, 0)
for sgName, portSet := range cubes {
for _, dstPorts := range portSet.Intervals() {
p, _ := netp.NewTCPUDP(isTCP, netp.MinPort, netp.MaxPort, int(dstPorts.Start()), int(dstPorts.End()))
result = append(result, ir.NewSGRule(direction, sgName, p, netset.GetCidrAll(), ""))
result = append(result, ir.NewSGRule(direction, sgName, p, l, ""))
}
}
return result
}

// cubes (SGName X icmpset) to SG rules
func icmpSGCubesToRules(cubes map[ir.SGName]*netset.ICMPSet, direction ir.Direction) []*ir.SGRule {
func icmpSGCubesToRules(cubes map[ir.SGName]*netset.ICMPSet, direction ir.Direction, l *netset.IPBlock) []*ir.SGRule {
result := make([]*ir.SGRule, 0)
for sgName, icmpSet := range cubes {
for _, icmp := range optimize.IcmpsetPartitions(icmpSet) {
result = append(result, ir.NewSGRule(direction, sgName, icmp, netset.GetCidrAll(), ""))
result = append(result, ir.NewSGRule(direction, sgName, icmp, l, ""))
}
}
return result
}

// slice of remote SGs to SG rules
func anyProtocolCubesToRules(remoteSG []ir.SGName, direction ir.Direction) []*ir.SGRule {
func anyProtocolCubesToRules(remoteSG []ir.SGName, direction ir.Direction, l *netset.IPBlock) []*ir.SGRule {
result := make([]*ir.SGRule, len(remoteSG))
for i, sgName := range remoteSG {
result[i] = ir.NewSGRule(direction, sgName, netp.AnyProtocol{}, netset.GetCidrAll(), "")
result[i] = ir.NewSGRule(direction, sgName, netp.AnyProtocol{}, l, "")
}
return result
}
Loading

0 comments on commit 8c830d1

Please sign in to comment.