Skip to content
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

connect explainConnectivity to main cli #326

Merged
merged 12 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
27 changes: 8 additions & 19 deletions cmd/analyzer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"log"
"os"

"github.com/np-guard/vpc-network-config-analyzer/pkg/common"
"github.com/np-guard/vpc-network-config-analyzer/pkg/ibmvpc"
"github.com/np-guard/vpc-network-config-analyzer/pkg/version"
"github.com/np-guard/vpc-network-config-analyzer/pkg/vpcmodel"
Expand Down Expand Up @@ -56,10 +55,17 @@ func analysisTypeToUseCase(inArgs *InArgs) vpcmodel.OutputUseCase {
}

func analysisVPCConfigs(c1, c2 map[string]*vpcmodel.VPCConfig, inArgs *InArgs, outFile string) (string, error) {
var explanationArgs *vpcmodel.ExplanationArgs
if *inArgs.AnalysisType == explainMode {
explanationArgs = vpcmodel.NewExplanationArgs(*inArgs.ESrc, *inArgs.EDst, *inArgs.EProtocol,
*inArgs.ESrcMinPort, *inArgs.ESrcMaxPort, *inArgs.EDstMinPort, *inArgs.EDstMaxPort)
}

olasaadi99 marked this conversation as resolved.
Show resolved Hide resolved
og, err := vpcmodel.NewOutputGenerator(c1, c2,
*inArgs.Grouping,
analysisTypeToUseCase(inArgs),
false)
false,
explanationArgs)
if err != nil {
return "", err
}
Expand All @@ -86,19 +92,6 @@ func vpcConfigsFromFile(fileName string, inArgs *InArgs) (map[string]*vpcmodel.V
return vpcConfigs, nil
}

func translateCDtoConnectionSet(inArgs *InArgs) *common.ConnectionSet {
connection := common.NewConnectionSet(false)
if common.ProtocolStr(*inArgs.QProtocol) == common.ProtocolICMP {
connection.AddICMPConnection(common.MinICMPtype, common.MaxICMPtype,
common.MinICMPcode, common.MaxICMPcode)
} else {
connection.AddTCPorUDPConn(common.ProtocolStr(*inArgs.QProtocol), *inArgs.QSrcMinPort, *inArgs.QSrcMaxPort,
*inArgs.QDstMinPort, *inArgs.QDstMaxPort)
}

return connection
}

// The actual main function
// Takes command-line flags and returns an error rather than exiting, so it can be more easily used in testing
func _main(cmdlineArgs []string) error {
Expand Down Expand Up @@ -140,10 +133,6 @@ func _main(cmdlineArgs []string) error {
}
fmt.Println(vpcAnalysisOutput)

if *inArgs.AnalysisType == explainMode {
_ = translateCDtoConnectionSet(inArgs)
}

return nil
}

Expand Down
93 changes: 57 additions & 36 deletions cmd/analyzer/parse_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ type InArgs struct {
VPC *string
Debug *bool
Version *bool
QProtocol *string
QSrcMinPort *int64
QSrcMaxPort *int64
QDstMinPort *int64
QDstMaxPort *int64
ESrc *string
EDst *string
EProtocol *string
ESrcMinPort *int64
ESrcMaxPort *int64
EDstMinPort *int64
EDstMaxPort *int64
}

// flagHasValue indicates for each input arg if it is expected to have a value in the cli or not
Expand All @@ -38,11 +40,13 @@ var flagHasValue = map[string]bool{
VPC: true,
Debug: false,
Version: false,
QProtocol: true,
QSrcMinPort: true,
QSrcMaxPort: true,
QDstMinPort: true,
QDstMaxPort: true,
ESrc: true,
EDst: true,
EProtocol: true,
ESrcMinPort: true,
ESrcMaxPort: true,
EDstMinPort: true,
EDstMaxPort: true,
}

const (
Expand All @@ -56,11 +60,13 @@ const (
VPC = "vpc"
Debug = "debug"
Version = "version"
QProtocol = "q-protocol"
QSrcMinPort = "q-src-min-port"
QSrcMaxPort = "q-src-max-port"
QDstMinPort = "q-dst-min-port"
QDstMaxPort = "q-dst-max-port"
ESrc = "src"
EDst = "dst"
EProtocol = "protocol"
ESrcMinPort = "src-min-port"
ESrcMaxPort = "src-max-port"
EDstMinPort = "dst-min-port"
EDstMaxPort = "dst-max-port"

// output formats supported
JSONFormat = "json"
Expand Down Expand Up @@ -180,11 +186,13 @@ func ParseInArgs(cmdlineArgs []string) (*InArgs, error) {
args.VPC = flagset.String(VPC, "", "CRN of the VPC to analyze")
args.Debug = flagset.Bool(Debug, false, "Run in debug mode")
args.Version = flagset.Bool(Version, false, "Prints the release version number")
args.QProtocol = flagset.String(QProtocol, "", "Protocol for connection description")
args.QSrcMinPort = flagset.Int64(QSrcMinPort, common.MinPort, "SrcMinPort for connection description")
args.QSrcMaxPort = flagset.Int64(QSrcMaxPort, common.MaxPort, "SrcMaxPort for connection description")
args.QDstMinPort = flagset.Int64(QDstMinPort, common.MinPort, "DstMinPort for connection description")
args.QDstMaxPort = flagset.Int64(QDstMaxPort, common.MaxPort, "DstMaxPort for connection description")
args.ESrc = flagset.String(ESrc, "", "Src name for network_interface or an external ip to be explained")
args.EDst = flagset.String(EDst, "", "Dst name for network_interface or an external ip to be explained")
args.EProtocol = flagset.String(EProtocol, "", "Protocol for connection description")
args.ESrcMinPort = flagset.Int64(ESrcMinPort, common.MinPort, "minimum source port for connection description")
args.ESrcMaxPort = flagset.Int64(ESrcMaxPort, common.MaxPort, "maximum source port for connection description")
args.EDstMinPort = flagset.Int64(EDstMinPort, common.MinPort, "minimum destination port for connection description")
args.EDstMaxPort = flagset.Int64(EDstMaxPort, common.MaxPort, "maximum destination port for connection description")

// calling parseCmdLine prior to flagset.Parse to ensure that excessive and unsupported arguments are handled
// for example, flagset.Parse() ignores input args missing the `-`
Expand Down Expand Up @@ -223,13 +231,15 @@ func wasFlagSpecified(name string, flagset *flag.FlagSet) bool {
return found
}

func wereExplainParamsSpecified(flagset *flag.FlagSet) bool {
if wasFlagSpecified(QProtocol, flagset) || wasFlagSpecified(QSrcMinPort, flagset) || wasFlagSpecified(QSrcMaxPort, flagset) ||
wasFlagSpecified(QDstMinPort, flagset) || wasFlagSpecified(QDstMaxPort, flagset) {
return true
func wereExplainParamsSpecified(flagset *flag.FlagSet, flagNames []string) bool {
specified := false
for i := 0; i < len(flagNames); i++ {
if wasFlagSpecified(flagNames[i], flagset) {
specified = true
}
}

return false
return specified
}

func PortInRange(port int64) bool {
Expand All @@ -249,39 +259,50 @@ func minMaxValidity(minPort, maxPort int64, minPortName, maxPortName string) err
}

func validRangeConnectionExplainMode(args *InArgs) error {
err := minMaxValidity(*args.QSrcMinPort, *args.QSrcMaxPort, QSrcMinPort, QSrcMaxPort)
err := minMaxValidity(*args.ESrcMinPort, *args.ESrcMaxPort, ESrcMinPort, ESrcMaxPort)
if err != nil {
return err
}
err = minMaxValidity(*args.QDstMinPort, *args.QDstMaxPort, QDstMinPort, QDstMaxPort)
err = minMaxValidity(*args.EDstMinPort, *args.EDstMaxPort, EDstMinPort, EDstMaxPort)
if err != nil {
return err
}

if !PortInRange(*args.QSrcMinPort) || !PortInRange(*args.QSrcMaxPort) ||
!PortInRange(*args.QDstMinPort) || !PortInRange(*args.QDstMaxPort) {
if !PortInRange(*args.ESrcMinPort) || !PortInRange(*args.ESrcMaxPort) ||
!PortInRange(*args.EDstMinPort) || !PortInRange(*args.EDstMaxPort) {
return fmt.Errorf("%s, %s, %s and %s must be in ranges [%d, %d]",
QSrcMinPort, QSrcMaxPort, QDstMinPort, QDstMaxPort, common.MinPort, common.MaxPort)
ESrcMinPort, ESrcMaxPort, EDstMinPort, EDstMaxPort, common.MinPort, common.MaxPort)
}

return nil
}

func invalidArgsExplainMode(args *InArgs, flagset *flag.FlagSet) error {
if *args.AnalysisType != explainMode && wereExplainParamsSpecified(flagset) {
return fmt.Errorf("%s, %s, %s, %s and %s can be specified only when analysis-type is %s",
QProtocol, QSrcMinPort, QSrcMaxPort, QDstMinPort, QDstMaxPort, explainMode)
if *args.AnalysisType != explainMode {
if wereExplainParamsSpecified(flagset, []string{ESrc, EDst, EProtocol, ESrcMinPort, ESrcMaxPort, EDstMinPort, EDstMaxPort, explainMode}) {
return fmt.Errorf("explainability related params %s, %s, %s, %s, %s, %s and %s"+
"can be specified only in explain mode: analysis-type equals %s",
ESrc, EDst, EProtocol, ESrcMinPort, ESrcMaxPort, EDstMinPort, EDstMaxPort, explainMode)
}
return nil
}

if *args.AnalysisType != explainMode {
if *args.ESrc == "" || *args.EDst == "" {
return fmt.Errorf("please specify %s and %s network_interface / external ip you want to explain connectivity for", ESrc, EDst)
}

if *args.EProtocol == "" {
if wereExplainParamsSpecified(flagset, []string{EProtocol, ESrcMinPort, ESrcMaxPort, EDstMinPort, EDstMaxPort, explainMode}) {
return fmt.Errorf("protocol must be specified when querying a specific connection")
}
return nil
}

protocol := strings.ToUpper(*args.QProtocol)
protocol := strings.ToUpper(*args.EProtocol)
if protocol != string(common.ProtocolTCP) && protocol != string(common.ProtocolUDP) && protocol != string(common.ProtocolICMP) {
return fmt.Errorf("wrong connection description protocol '%s'; must be one of: 'TCP, UDP, ICMP'", protocol)
}
args.QProtocol = &protocol
args.EProtocol = &protocol

return validRangeConnectionExplainMode(args)
}
Expand Down
44 changes: 41 additions & 3 deletions pkg/ibmvpc/analysis_output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ type vpcGeneralTest struct {
grouping bool
format vpcmodel.OutFormat
vpc string
ESrc string
EDst string
EProtocol string
ESrcMinPort int64
ESrcMaxPort int64
EDstMinPort int64
EDstMaxPort int64
}

const (
Expand All @@ -52,6 +59,7 @@ const (
suffixOutFileSubnetsLevelNoPGW = "subnetsBased_withoutPGW"
suffixOutFileDiffSubnets = "subnetsDiff"
suffixOutFileDiffEndpoints = "endpointsDiff"
suffixOutFileExplain = "explain"
txtOutSuffix = ".txt"
debugOutSuffix = "_debug.txt"
mdOutSuffix = ".md"
Expand Down Expand Up @@ -93,6 +101,8 @@ func getTestFileName(testName string,
res = baseName + suffixOutFileDiffSubnets
case vpcmodel.EndpointsDiff:
res = baseName + suffixOutFileDiffEndpoints
case vpcmodel.Explain:
res = baseName + suffixOutFileExplain
}
if grouping {
res += suffixOutFileWithGrouping
Expand Down Expand Up @@ -184,6 +194,23 @@ var tests = []*vpcGeneralTest{
useCases: []vpcmodel.OutputUseCase{vpcmodel.AllEndpoints, vpcmodel.SingleSubnet},
format: vpcmodel.Text,
},
{
name: "acl_testing3",
useCases: []vpcmodel.OutputUseCase{vpcmodel.Explain},
format: vpcmodel.Text,
ESrc: "vsi1-ky[10.240.10.4]",
EDst: "vsi2-ky[10.240.20.4]",
},
{
name: "sg_testing1_new",
useCases: []vpcmodel.OutputUseCase{vpcmodel.Explain},
format: vpcmodel.Text,
ESrc: "vsi1-ky[10.240.10.4]",
EDst: "vsi2-ky[10.240.20.4]",
EProtocol: "TCP",
ESrcMinPort: 1,
ESrcMaxPort: 5,
olasaadi99 marked this conversation as resolved.
Show resolved Hide resolved
},
{
name: "sg_testing1_new",
useCases: []vpcmodel.OutputUseCase{vpcmodel.AllEndpoints, vpcmodel.SingleSubnet, vpcmodel.AllSubnets},
Expand Down Expand Up @@ -493,20 +520,30 @@ func (tt *vpcGeneralTest) runTest(t *testing.T) {
vpcConfigs := getVPCConfigs(t, tt, true)
var vpcConfigs2nd map[string]*vpcmodel.VPCConfig
diffUseCase := false
explainUseCase := false
for _, useCase := range tt.useCases {
if useCase == vpcmodel.SubnetsDiff || useCase == vpcmodel.EndpointsDiff {
diffUseCase = true
}
if useCase == vpcmodel.Explain {
explainUseCase = true
}
}
if diffUseCase {
vpcConfigs2nd = getVPCConfigs(t, tt, false)
} else { // inputConfig2nd should be ignored if not diffUseCase
tt.inputConfig2nd = ""
}

var explanationArgs *vpcmodel.ExplanationArgs
if explainUseCase {
explanationArgs = vpcmodel.NewExplanationArgs(tt.ESrc, tt.EDst, tt.EProtocol,
tt.ESrcMinPort, tt.ESrcMaxPort, tt.EDstMinPort, tt.EDstMaxPort)
}

// generate actual output for all use cases specified for this test
for _, uc := range tt.useCases {
err := runTestPerUseCase(t, tt, vpcConfigs, vpcConfigs2nd, uc, tt.mode)
err := runTestPerUseCase(t, tt, vpcConfigs, vpcConfigs2nd, uc, tt.mode, explanationArgs)
require.Equal(t, tt.errPerUseCase[uc], err, "comparing actual err to expected err")
}
for uc, outFile := range tt.actualOutput {
Expand Down Expand Up @@ -579,11 +616,12 @@ func runTestPerUseCase(t *testing.T,
tt *vpcGeneralTest,
c1, c2 map[string]*vpcmodel.VPCConfig,
uc vpcmodel.OutputUseCase,
mode testMode) error {
mode testMode,
explanationArgs *vpcmodel.ExplanationArgs) error {
if err := initTestFileNames(tt, uc, "", true); err != nil {
return err
}
og, err := vpcmodel.NewOutputGenerator(c1, c2, tt.grouping, uc, tt.format == vpcmodel.ARCHDRAWIO)
og, err := vpcmodel.NewOutputGenerator(c1, c2, tt.grouping, uc, tt.format == vpcmodel.ARCHDRAWIO, explanationArgs)
if err != nil {
return err
}
Expand Down
15 changes: 15 additions & 0 deletions pkg/ibmvpc/examples/acl_testing3_all_vpcs_explain.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Connectivity explanation between vsi1-ky[10.240.10.4] and vsi2-ky[10.240.20.4]
The following connection exists between vsi1-ky[10.240.10.4] and vsi2-ky[10.240.20.4]: protocol: TCP,UDP; its enabled by
Egress Rules:
~~~~~~~~~~~~~
SecurityGroupLayer Rules
------------------------
enabling rules from sg1-ky:
index: 0, direction: outbound, protocol: all, cidr: 0.0.0.0/0
Ingress Rules:
~~~~~~~~~~~~~~
SecurityGroupLayer Rules
------------------------
enabling rules from sg1-ky:
index: 1, direction: inbound, protocol: all, cidr: 0.0.0.0/0

9 changes: 9 additions & 0 deletions pkg/ibmvpc/examples/sg_testing1_new_all_vpcs_explain.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Connectivity explanation between vsi1-ky[10.240.10.4] and vsi2-ky[10.240.20.4]
There is no connection "protocol: TCP src-ports: 1-5 dst-ports: 0" between vsi1-ky[10.240.10.4] and vsi2-ky[10.240.20.4]; connection blocked by egress
Ingress Rules:
~~~~~~~~~~~~~~
SecurityGroupLayer Rules
------------------------
enabling rules from sg2-ky:
index: 4, direction: inbound, protocol: all, cidr: 10.240.10.4/32

Loading