Skip to content

504 lint overlap subnets #700

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Jul 21, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,682 changes: 5,682 additions & 0 deletions pkg/ibmvpc/examples/input/input_tgw_larger_example_partly_overlap.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion pkg/ibmvpc/examples/out/lint_out/basic_acl3_Lint
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
"Firewall rules implying different connectivity for different endpoints within a subnet" issues:
------------------------------------------------------------------------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In VPC test-vpc1-ky, network ACL acl3-ky rule's indexed 1 splits subnet subnet3-ky (10.240.30.0/24). Splitting rule details: index: 1, direction: outbound , src: 10.240.30.0/31 , dst: 10.240.20.0/24, conn: all, action: allow
In VPC test-vpc1-ky, network ACL acl3-ky rule's indexed 3 splits subnet subnet3-ky (10.240.30.0/24). Splitting rule details: index: 3, direction: inbound , src: 10.240.20.0/24 , dst: 10.240.30.0/31, conn: all, action: allow
________________________________________________________________________________________________________________________________________________________________________________________________________

no lint "Overlapping CIDR ranges between different subnets" issues
5 changes: 4 additions & 1 deletion pkg/ibmvpc/examples/out/lint_out/basic_sg1_Lint
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"Firewall rules implying different connectivity for different endpoints within a subnet" issues:
------------------------------------------------------------------------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In VPC test-vpc1-ky, security group sg1-ky rule's indexed 1 splits subnet subnet1-ky (10.240.10.0/24). Splitting rule details: index: 1, direction: inbound, conns: protocol: all, remote: sg1-ky (10.240.10.4/32), local: 0.0.0.0/0
In VPC test-vpc1-ky, security group sg1-ky rule's indexed 3 splits subnets subnet2-ky (10.240.20.0/24), subnet3-ky (10.240.30.0/24). Splitting rule details: index: 3, direction: inbound, conns: protocol: all, remote: sg2-ky (10.240.20.4/32,10.240.30.4/32), local: 0.0.0.0/0
In VPC test-vpc1-ky, security group sg1-ky rule's indexed 4 splits subnet subnet3-ky (10.240.30.0/24). Splitting rule details: index: 4, direction: inbound, conns: protocol: all, remote: sg3-ky (10.240.30.5/32,10.240.30.6/32), local: 0.0.0.0/0
In VPC test-vpc1-ky, security group sg2-ky rule's indexed 4 splits subnet subnet1-ky (10.240.10.0/24). Splitting rule details: index: 4, direction: inbound, conns: protocol: all, remote: sg1-ky (10.240.10.4/32), local: 0.0.0.0/0
In VPC test-vpc1-ky, security group sg2-ky rule's indexed 6 splits subnets subnet2-ky (10.240.20.0/24), subnet3-ky (10.240.30.0/24). Splitting rule details: index: 6, direction: outbound, conns: protocol: tcp, dstPorts: 1-65535, remote: sg2-ky (10.240.20.4/32,10.240.30.4/32), local: 0.0.0.0/0
In VPC test-vpc1-ky, security group sg2-ky rule's indexed 7 splits subnets subnet2-ky (10.240.20.0/24), subnet3-ky (10.240.30.0/24). Splitting rule details: index: 7, direction: inbound, conns: protocol: tcp, dstPorts: 1-65535, remote: sg2-ky (10.240.20.4/32,10.240.30.4/32), local: 0.0.0.0/0
________________________________________________________________________________________________________________________________________________________________________________________________________

no lint "Overlapping CIDR ranges between different subnets" issues
5 changes: 5 additions & 0 deletions pkg/ibmvpc/examples/out/lint_out/multivpc_Lint
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
"Overlapping CIDR ranges between different subnets" issues:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
VPC test-vpc2-ky's subnet subnet21-ky of cidr 10.240.64.0/24 and VPC zn-vpc2's subnet zn-vpc2-net1 of cidr 10.240.64.0/24 overlap in the entire subnets' CIDR range
________________________________________________________________________________________________________________________________________________________________________________________________________

no lint "Firewall rules implying different connectivity for different endpoints within a subnet" issues
6 changes: 6 additions & 0 deletions pkg/ibmvpc/examples/out/lint_out/multivpc_partly_overlap_Lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"Overlapping CIDR ranges between different subnets" issues:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
VPC test-vpc2-ky's subnet subnet21-ky of cidr 10.240.64.0/28 and VPC zn-vpc2's subnet zn-vpc2-net1 of cidr 10.240.64.0/24 overlap in 10.240.64.0/28
________________________________________________________________________________________________________________________________________________________________________________________________________

no lint "Firewall rules implying different connectivity for different endpoints within a subnet" issues
4 changes: 4 additions & 0 deletions pkg/ibmvpc/lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ var lintTests = []*vpcGeneralTest{
name: "multivpc",
inputConfig: "tgw_larger_example",
},
{
name: "multivpc_partly_overlap",
inputConfig: "tgw_larger_example_partly_overlap",
},
}

func TestAllLint(t *testing.T) {
Expand Down
24 changes: 10 additions & 14 deletions pkg/linter/lintFilterRuleSplitSubnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import (

const splitRuleSubnetName = "rules-splitting-subnets"

// filterRuleSplitSubnet: rules of filters that are inconsistent w.r.t. subnets.
type filterRuleSplitSubnet struct {
// filterRuleSplitSubnetLint: rules of filters that are inconsistent w.r.t. subnets.
type filterRuleSplitSubnetLint struct {
basicLinter
}

Expand All @@ -29,17 +29,17 @@ type splitRuleSubnet struct {
}

// /////////////////////////////////////////////////////////
// lint interface implementation for filterRuleSplitSubnet
// lint interface implementation for filterRuleSplitSubnetLint
// ////////////////////////////////////////////////////////
func (lint *filterRuleSplitSubnet) lintName() string {
func (lint *filterRuleSplitSubnetLint) lintName() string {
return splitRuleSubnetName
}

func (lint *filterRuleSplitSubnet) lintDescription() string {
func (lint *filterRuleSplitSubnetLint) lintDescription() string {
return "Firewall rules implying different connectivity for different endpoints within a subnet"
}

func (lint *filterRuleSplitSubnet) check() error {
func (lint *filterRuleSplitSubnetLint) check() error {
for _, config := range lint.configs {
if config.IsMultipleVPCsConfig {
continue // no use in executing lint on dummy vpcs
Expand Down Expand Up @@ -83,8 +83,8 @@ func ruleSplitSubnet(subnet vpcmodel.Subnet, ruleIPBlocks []*ipblock.IPBlock) bo
// finding interface implementation for splitRuleSubnet
//////////////////////////////////////////////////////////

func (finding *splitRuleSubnet) vpc() string {
return finding.vpcName
func (finding *splitRuleSubnet) vpc() []string {
return []string{finding.vpcName}
}

func (finding *splitRuleSubnet) string() string {
Expand All @@ -100,7 +100,8 @@ func (finding *splitRuleSubnet) string() string {
subnetStr = "subnet " + subnetStr
}
return fmt.Sprintf("In VPC %s, %s %s rule's indexed %d splits %s. Splitting rule details: %s",
finding.vpc(), finding.rule.LayerName, rule.FilterName, rule.RuleIndex, subnetStr, rule.RuleDesc)
finding.vpc()[0], finding.rule.LayerName, rule.FilterName, rule.RuleIndex, subnetStr,
strings.ReplaceAll(rule.RuleDesc, "\n", ""))
}

// for json: a rule with the list of subnets it splits
Expand All @@ -110,11 +111,6 @@ type splitRuleSubnetJSON struct {
SplitSubnets []subnetJSON `json:"splitted_subnets"`
}

type subnetJSON struct {
Name string `json:"name"`
CIDR string `json:"cidr"`
}

func (finding *splitRuleSubnet) toJSON() any {
rule := finding.rule
splitSubnetsJSON := make([]subnetJSON, len(finding.splitSubnets))
Expand Down
116 changes: 116 additions & 0 deletions pkg/linter/lintOverlappingSubnets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
Copyright 2023- IBM Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package linter

import (
"fmt"

"github.com/np-guard/models/pkg/ipblock"
"github.com/np-guard/vpc-network-config-analyzer/pkg/vpcmodel"
)

const overlappingSubnetsName = "overlapping-subnets"

// overlapSubnets: overlapping subnet ranges (relevant mostly for the multiple VPCs use case)
type overlappingSubnetsLint struct {
basicLinter
}

// a couple of overlapping subnets
type overlapSubnets struct {
overlapSubnets [2]vpcmodel.Subnet
overlapIPBlocks *ipblock.IPBlock
}

// /////////////////////////////////////////////////////////
// lint interface implementation for overlapSubnets
// ////////////////////////////////////////////////////////
func (lint *overlappingSubnetsLint) lintName() string {
return overlappingSubnetsName
}

func (lint *overlappingSubnetsLint) lintDescription() string {
return "Overlapping CIDR ranges between different subnets"
}

func (lint *overlappingSubnetsLint) check() error {
allSubnets := []vpcmodel.Subnet{}
for _, config := range lint.configs {
if config.IsMultipleVPCsConfig {
continue
}
allSubnets = append(allSubnets, config.Subnets...)
}
for i, subnet1 := range allSubnets {
subnet1IPBlock, err1 := ipblock.FromCidr(subnet1.CIDR())
if err1 != nil {
return err1
}
for _, subnet2 := range allSubnets[i+1:] {
subnet2IPBlock, err2 := ipblock.FromCidr(subnet2.CIDR())
if err2 != nil {
return err2
}
intersectIPBlock := subnet1IPBlock.Intersect(subnet2IPBlock)
if !intersectIPBlock.IsEmpty() {
// to make the content of the overlapSubnets struct deterministic
if subnetStr(subnet1) > subnetStr(subnet2) {
subnet1, subnet2 = subnet2, subnet1
}
lint.addFinding(&overlapSubnets{overlapSubnets: [2]vpcmodel.Subnet{subnet1, subnet2}, overlapIPBlocks: intersectIPBlock})
}
}
}
return nil
}

///////////////////////////////////////////////////////////
// finding interface implementation for overlapSubnets
//////////////////////////////////////////////////////////

func (finding *overlapSubnets) vpc() []string {
return []string{finding.overlapSubnets[0].VPC().Name(), finding.overlapSubnets[1].VPC().Name()}
}

func (finding *overlapSubnets) string() string {
subnet1 := finding.overlapSubnets[0]
subnet2 := finding.overlapSubnets[1]
overlapStr := ""
if finding.overlapIPBlocks.String() == subnet1.CIDR() && subnet1.CIDR() == subnet2.CIDR() {
overlapStr = " overlap in the entire subnets' CIDR range"
} else {
overlapStr = fmt.Sprintf(" overlap in %s", finding.overlapIPBlocks.String())
}

return fmt.Sprintf("VPC %s's %s and VPC %s's %s ", subnet1.VPC().Name(), subnetStr(subnet1),
subnet2.VPC().Name(), subnetStr(subnet2)) + overlapStr
}

func subnetStr(subnet vpcmodel.Subnet) string {
return fmt.Sprintf("subnet %s of cidr %s", subnet.Name(), subnet.CIDR())
}

// for json: details of overlapping subnets
type overlapSubnetsJSON struct {
OverlapSubnets []subnetJSON `json:"couple_overlap_subnets"`
OverlapCidr string `json:"overlap_cidr"`
}

type subnetJSON struct {
Name string `json:"name"`
CIDR string `json:"cidr"`
VpcName string `json:"vpc_name,omitempty"`
}

func (finding *overlapSubnets) toJSON() any {
overlapsSubnetsJSON := make([]subnetJSON, 2)
for i, overlapSubnet := range finding.overlapSubnets {
overlapsSubnetsJSON[i] = subnetJSON{Name: overlapSubnet.Name(), VpcName: overlapSubnet.VPC().Name(), CIDR: overlapSubnet.CIDR()}
}
res := overlapSubnetsJSON{OverlapSubnets: overlapsSubnetsJSON, OverlapCidr: finding.overlapIPBlocks.String()}
return res
}
7 changes: 5 additions & 2 deletions pkg/linter/linterExecute.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
)

const issues = "issues:"
const delimBetweenLintsChars = 200

// LinterExecute executes linters one by one
// todo: mechanism for disabling/enabling lint checks
Expand All @@ -23,7 +24,8 @@ func LinterExecute(configs map[string]*vpcmodel.VPCConfig) (issueFound bool, res
configs: configs,
}
linters := []linter{
&filterRuleSplitSubnet{basicLinter: blinter},
&filterRuleSplitSubnetLint{basicLinter: blinter},
&overlappingSubnetsLint{basicLinter: blinter},
}
strPerLint := []string{}
for _, thisLinter := range linters {
Expand All @@ -42,7 +44,8 @@ func LinterExecute(configs map[string]*vpcmodel.VPCConfig) (issueFound bool, res
strPerLint = append(strPerLint, thisLintStr)
}
sort.Strings(strPerLint)
resString = strings.Join(strPerLint, "")
delimBetweenLints := strings.Repeat("_", delimBetweenLintsChars)
resString = strings.Join(strPerLint, "\n"+delimBetweenLints+"\n\n")
fmt.Println(resString)
return issueFound, resString, nil
}
6 changes: 3 additions & 3 deletions pkg/linter/linterTypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type linter interface {
}

type finding interface {
vpc() string
vpc() []string
string() string
toJSON() any
}
Expand All @@ -50,8 +50,8 @@ func (lint *basicLinter) string(lintDesc string) string {
}
sort.Strings(findingsRes)
header := fmt.Sprintf("%q %s\n", lintDesc, issues) +
strings.Repeat("-", len(lintDesc)+len(issues)+3) + "\n"
return header + strings.Join(findingsRes, "")
strings.Repeat("~", len(lintDesc)+len(issues)+3) + "\n"
return header + strings.Join(findingsRes, "\n")
}

func (lint *basicLinter) toJSON() []any {
Expand Down