diff --git a/cmd/analyzer/main.go b/cmd/analyzer/main.go index d41996c6a..5f13ce761 100644 --- a/cmd/analyzer/main.go +++ b/cmd/analyzer/main.go @@ -6,6 +6,7 @@ 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" @@ -48,6 +49,8 @@ func analysisTypeToUseCase(inArgs *InArgs) vpcmodel.OutputUseCase { return vpcmodel.SubnetsDiff case allEndpointsDiff: return vpcmodel.EndpointsDiff + case explainMode: + return vpcmodel.Explain } return vpcmodel.AllEndpoints } @@ -83,6 +86,19 @@ 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 { @@ -123,6 +139,11 @@ func _main(cmdlineArgs []string) error { return err2 } fmt.Println(vpcAnalysisOutput) + + if *inArgs.AnalysisType == explainMode { + _ = translateCDtoConnectionSet(inArgs) + } + return nil } diff --git a/cmd/analyzer/parse_args.go b/cmd/analyzer/parse_args.go index a0e24d541..fbc1f7c05 100644 --- a/cmd/analyzer/parse_args.go +++ b/cmd/analyzer/parse_args.go @@ -5,6 +5,8 @@ import ( "fmt" "slices" "strings" + + "github.com/np-guard/vpc-network-config-analyzer/pkg/common" ) // InArgs contains the input arguments for the analyzer @@ -18,6 +20,11 @@ type InArgs struct { VPC *string Debug *bool Version *bool + QProtocol *string + QSrcMinPort *int64 + QSrcMaxPort *int64 + QDstMinPort *int64 + QDstMaxPort *int64 } // flagHasValue indicates for each input arg if it is expected to have a value in the cli or not @@ -31,6 +38,11 @@ var flagHasValue = map[string]bool{ VPC: true, Debug: false, Version: false, + QProtocol: true, + QSrcMinPort: true, + QSrcMaxPort: true, + QDstMinPort: true, + QDstMaxPort: true, } const ( @@ -44,6 +56,11 @@ 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" // output formats supported JSONFormat = "json" @@ -59,6 +76,7 @@ const ( singleSubnet = "single_subnet" // single subnet connectivity analysis allEndpointsDiff = "diff_all_endpoints" // semantic diff of allEndpoints analysis between two configurations allSubnetsDiff = "diff_all_subnets" // semantic diff of allSubnets analysis between two configurations + explainMode = "explain" // explain specified connectivity, given src,dst and connection // separator separator = ", " @@ -80,6 +98,7 @@ var supportedAnalysisTypesMap = map[string][]string{ singleSubnet: {TEXTFormat}, allEndpointsDiff: {TEXTFormat, MDFormat}, allSubnetsDiff: {TEXTFormat, MDFormat}, + explainMode: {TEXTFormat}, } // supportedOutputFormatsList is an ordered list of supported output formats (usage details presented in this order) @@ -99,6 +118,7 @@ var supportedAnalysisTypesList = []string{ singleSubnet, allEndpointsDiff, allSubnetsDiff, + explainMode, } func getSupportedAnalysisTypesMapString() string { @@ -160,6 +180,11 @@ 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") // calling parseCmdLine prior to flagset.Parse to ensure that excessive and unsupported arguments are handled // for example, flagset.Parse() ignores input args missing the `-` @@ -180,9 +205,87 @@ func ParseInArgs(cmdlineArgs []string) (*InArgs, error) { if err != nil { return nil, err } + err = invalidArgsExplainMode(&args, flagset) + if err != nil { + return nil, err + } return &args, nil } + +func wasFlagSpecified(name string, flagset *flag.FlagSet) bool { + found := false + flagset.Visit(func(f *flag.Flag) { + if f.Name == name { + found = true + } + }) + 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 + } + + return false +} + +func PortInRange(port int64) bool { + if port > common.MaxPort || port < common.MinPort { + return false + } + + return true +} + +func minMaxValidity(minPort, maxPort int64, minPortName, maxPortName string) error { + if minPort > maxPort { + return fmt.Errorf("%s %d must not be larger than %s %d", minPortName, minPort, maxPortName, maxPort) + } + + return nil +} + +func validRangeConnectionExplainMode(args *InArgs) error { + err := minMaxValidity(*args.QSrcMinPort, *args.QSrcMaxPort, QSrcMinPort, QSrcMaxPort) + if err != nil { + return err + } + err = minMaxValidity(*args.QDstMinPort, *args.QDstMaxPort, QDstMinPort, QDstMaxPort) + if err != nil { + return err + } + + if !PortInRange(*args.QSrcMinPort) || !PortInRange(*args.QSrcMaxPort) || + !PortInRange(*args.QDstMinPort) || !PortInRange(*args.QDstMaxPort) { + return fmt.Errorf("%s, %s, %s and %s must be in ranges [%d, %d]", + QSrcMinPort, QSrcMaxPort, QDstMinPort, QDstMaxPort, 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 { + return nil + } + + protocol := strings.ToUpper(*args.QProtocol) + 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 + + return validRangeConnectionExplainMode(args) +} + func errorInErgs(args *InArgs, flagset *flag.FlagSet) error { if !*args.Version && (args.InputConfigFile == nil || *args.InputConfigFile == "") { flagset.PrintDefaults() diff --git a/pkg/vpcmodel/output.go b/pkg/vpcmodel/output.go index 7ed253bcd..6e03a35fa 100644 --- a/pkg/vpcmodel/output.go +++ b/pkg/vpcmodel/output.go @@ -38,6 +38,7 @@ const ( AllSubnetsNoPGW // connectivity between subnets (consider nacl only) SubnetsDiff // diff between subnets connectivity of two cfgs (consider nacl + pgw) EndpointsDiff // diff between vsis connectivity of two cfgs + Explain // explain specified connectivity, given src,dst and connection ) // OutputGenerator captures one vpc config1 with its connectivity analysis results, and implements