diff --git a/cmd/analyzer/main.go b/cmd/analyzer/main.go index aa70b70f8..3811cba96 100644 --- a/cmd/analyzer/main.go +++ b/cmd/analyzer/main.go @@ -10,6 +10,13 @@ import ( "github.com/np-guard/vpc-network-config-analyzer/pkg/vpcmodel" ) +const ( + ParsingErr = "error parsing arguments:" + OutGenerationErr = "output generation error:" + InGenerationErr = "error generating cloud config from input vpc resources file:" + ErrorFormat = "%s %w" +) + func getOutputFormat(inArgs *InArgs) vpcmodel.OutFormat { switch *inArgs.OutputFormat { case TEXTFormat: @@ -36,12 +43,14 @@ func analysisTypeToUseCase(inArgs *InArgs) vpcmodel.OutputUseCase { return vpcmodel.SingleSubnet case allSubnets: return vpcmodel.AllSubnets + case allSubnetsDiff: + return vpcmodel.AllSubnetsDiff } return vpcmodel.AllEndpoints } func analysisPerVPCConfig(c *vpcmodel.VPCConfig, inArgs *InArgs, outFile string) (*vpcmodel.VPCAnalysisOutput, error) { - og, err := vpcmodel.NewOutputGenerator(c, + og, err := vpcmodel.NewOutputGenerator(c, nil, *inArgs.Grouping, analysisTypeToUseCase(inArgs), *inArgs.OutputFormat == ARCHDRAWIOFormat) @@ -57,12 +66,31 @@ func analysisPerVPCConfig(c *vpcmodel.VPCConfig, inArgs *InArgs, outFile string) outFormat := getOutputFormat(inArgs) output, err := og.Generate(outFormat, genOutFile) if err != nil { - return nil, fmt.Errorf("output generation error: %w", err) + return nil, fmt.Errorf(ErrorFormat, OutGenerationErr, err) } return output, nil } +func analysisDiffVPCConfig(c1, c2 *vpcmodel.VPCConfig, inArgs *InArgs, outFile string) (*vpcmodel.VPCAnalysisOutput, error) { + og, err := vpcmodel.NewOutputGenerator(c1, c2, + *inArgs.Grouping, + analysisTypeToUseCase(inArgs), + false) + if err != nil { + return nil, err + } + + var analysisOut *vpcmodel.VPCAnalysisOutput + outFormat := getOutputFormat(inArgs) + analysisOut, err = og.Generate(outFormat, outFile) + if err != nil { + return nil, fmt.Errorf(ErrorFormat, OutGenerationErr, err) + } + + return analysisOut, nil +} + // 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 { @@ -71,7 +99,7 @@ func _main(cmdlineArgs []string) error { return nil } if err != nil { - return fmt.Errorf("error parsing arguments: %w", err) + return fmt.Errorf(ErrorFormat, ParsingErr, err) } rc, err := ibmvpc.ParseResourcesFromFile(*inArgs.InputConfigFile) @@ -81,33 +109,74 @@ func _main(cmdlineArgs []string) error { vpcConfigs, err := ibmvpc.VPCConfigsFromResources(rc, *inArgs.VPC, *inArgs.Debug) if err != nil { - return fmt.Errorf("error generating cloud config from input vpc resources file: %w", err) + return fmt.Errorf(ErrorFormat, InGenerationErr, err) } + outFile := "" if inArgs.OutputFile != nil { outFile = *inArgs.OutputFile } - outputPerVPC := make([]*vpcmodel.VPCAnalysisOutput, len(vpcConfigs)) - i := 0 - for _, vpcConfig := range vpcConfigs { - vpcAnalysisOutput, err2 := analysisPerVPCConfig(vpcConfig, inArgs, outFile) - if err2 != nil { - return err2 + diffAnalysis := *inArgs.AnalysisType == allEndpointsDiff || *inArgs.AnalysisType == allSubnetsDiff + if !diffAnalysis { + outputPerVPC := make([]*vpcmodel.VPCAnalysisOutput, len(vpcConfigs)) + i := 0 + for _, vpcConfig := range vpcConfigs { + vpcAnalysisOutput, err2 := analysisPerVPCConfig(vpcConfig, inArgs, outFile) + if err2 != nil { + return err2 + } + outputPerVPC[i] = vpcAnalysisOutput + i++ + } + + var out string + out, err = vpcmodel.AggregateVPCsOutput(outputPerVPC, getOutputFormat(inArgs), outFile) + if err != nil { + return err } - outputPerVPC[i] = vpcAnalysisOutput - i++ + fmt.Println(out) + } else { + return diffAnalysisMain(inArgs, vpcConfigs, outFile) } + return nil +} - out, err := vpcmodel.AggregateVPCsOutput(outputPerVPC, getOutputFormat(inArgs), outFile) +func diffAnalysisMain(inArgs *InArgs, vpcConfigs map[string]*vpcmodel.VPCConfig, outFile string) error { + // ToDo SM: for diff analysis assume 2 configs only, the 2nd given through vpc-config-second + rc2ndForDiff, err := ibmvpc.ParseResourcesFromFile(*inArgs.InputSecondConfigFile) if err != nil { - return err + return fmt.Errorf(ErrorFormat, ParsingErr, err) } - fmt.Println(out) - + vpc2ndConfigs, err := ibmvpc.VPCConfigsFromResources(rc2ndForDiff, *inArgs.VPC, *inArgs.Debug) + if err != nil { + return fmt.Errorf(ErrorFormat, InGenerationErr, err) + } + // For diff analysis each vpcConfigs have a single element + c1, single1 := getSingleCfg(vpcConfigs) + c2, single2 := getSingleCfg(vpc2ndConfigs) + if !single1 || !single2 { + return fmt.Errorf("for diff mode %v a single configuration should be provided "+ + "for both -vpc-config and -vpc-config-second", *inArgs.AnalysisType) + } + analysisOutput, err2 := analysisDiffVPCConfig(c1, c2, inArgs, outFile) + if err2 != nil { + return err2 + } + fmt.Println(analysisOutput.Output) return nil } +func getSingleCfg(vpcConfigs map[string]*vpcmodel.VPCConfig) (*vpcmodel.VPCConfig, bool) { + if len(vpcConfigs) > 1 { + return nil, false + } + for _, vpcConfig := range vpcConfigs { + return vpcConfig, true + } + return nil, false +} + func main() { err := _main(os.Args[1:]) if err != nil { diff --git a/cmd/analyzer/parse_args.go b/cmd/analyzer/parse_args.go index 663dcaff8..e2ce0dad4 100644 --- a/cmd/analyzer/parse_args.go +++ b/cmd/analyzer/parse_args.go @@ -8,13 +8,14 @@ import ( // InArgs contains the input arguments for the analyzer type InArgs struct { - InputConfigFile *string - OutputFile *string - OutputFormat *string - AnalysisType *string - Grouping *bool - VPC *string - Debug *bool + InputConfigFile *string + InputSecondConfigFile *string + OutputFile *string + OutputFormat *string + AnalysisType *string + Grouping *bool + VPC *string + Debug *bool } const ( @@ -27,9 +28,11 @@ const ( DEBUGFormat = "debug" // connectivity analysis types supported - allEndpoints = "all_endpoints" // vsi to vsi connectivity analysis - allSubnets = "all_subnets" // subnet to subnet connectivity analysis - singleSubnet = "single_subnet" // single subnet connectivity analysis + allEndpoints = "all_endpoints" // vsi to vsi connectivity analysis + allSubnets = "all_subnets" // subnet to subnet connectivity analysis + 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 ) var supportedOutputFormats = map[string]bool{ @@ -41,9 +44,11 @@ var supportedOutputFormats = map[string]bool{ DEBUGFormat: true, } var supportedAnalysisTypes = map[string]bool{ - allEndpoints: true, - allSubnets: true, - singleSubnet: true, + allEndpoints: true, + allSubnets: true, + singleSubnet: true, + allSubnetsDiff: true, + allEndpointsDiff: false, } func getSupportedValuesString(supportedValues map[string]bool) string { @@ -60,6 +65,7 @@ func ParseInArgs(cmdlineArgs []string) (*InArgs, error) { args := InArgs{} flagset := flag.NewFlagSet("vpc-network-config-analyzer", flag.ContinueOnError) args.InputConfigFile = flagset.String("vpc-config", "", "file path to input config") + args.InputSecondConfigFile = flagset.String("vpc-config-second", "", "file path to second input config for semantic diff") args.OutputFile = flagset.String("output-file", "", "file path to store results") args.OutputFormat = flagset.String("format", TEXTFormat, "output format; must be one of "+getSupportedValuesString(supportedOutputFormats)) args.AnalysisType = flagset.String("analysis-type", allEndpoints, @@ -72,32 +78,55 @@ func ParseInArgs(cmdlineArgs []string) (*InArgs, error) { if err != nil { return nil, err } + err = errorInErgs(&args, flagset) + if err != nil { + return nil, err + } + err = notSupportedYetArgs(&args) + if err != nil { + return nil, err + } + return &args, nil +} + +func errorInErgs(args *InArgs, flagset *flag.FlagSet) error { if args.InputConfigFile == nil || *args.InputConfigFile == "" { flagset.PrintDefaults() - return nil, fmt.Errorf("missing parameter: vpc-config") + return fmt.Errorf("missing parameter: vpc-config") } - - if !supportedOutputFormats[*args.OutputFormat] { + if !supportedAnalysisTypes[*args.AnalysisType] { flagset.PrintDefaults() - return nil, fmt.Errorf("wrong output format %s; must be one of %s", *args.OutputFormat, getSupportedValuesString(supportedOutputFormats)) + return fmt.Errorf("wrong analysis type %s; must be one of: %s", *args.AnalysisType, getSupportedValuesString(supportedAnalysisTypes)) } - - if !supportedAnalysisTypes[*args.AnalysisType] { + if !supportedOutputFormats[*args.OutputFormat] { flagset.PrintDefaults() - return nil, fmt.Errorf("wrong analysis type %s; must be one of: %s", *args.AnalysisType, getSupportedValuesString(supportedAnalysisTypes)) + return fmt.Errorf("wrong output format %s; must be one of %s", *args.OutputFormat, getSupportedValuesString(supportedOutputFormats)) + } + diffAnalysis := *args.AnalysisType == allEndpointsDiff || *args.AnalysisType == allSubnetsDiff + fileForDiffSpecified := args.InputSecondConfigFile != nil && *args.InputSecondConfigFile != "" + if fileForDiffSpecified && !diffAnalysis { + return fmt.Errorf("wrong analysis type %s for 2nd file (%v) specified for diff", + *args.AnalysisType, *args.InputSecondConfigFile) + } + if !fileForDiffSpecified && diffAnalysis { + return fmt.Errorf("missing parameter vpc-config-second for diff analysis %s", *args.AnalysisType) } + return nil +} +func notSupportedYetArgs(args *InArgs) error { if *args.AnalysisType != allEndpoints && *args.OutputFormat != TEXTFormat && *args.OutputFormat != JSONFormat { - return nil, fmt.Errorf("currently only txt/json output format supported with %s analysis type", *args.AnalysisType) + return fmt.Errorf("currently only txt/json output format supported with %s analysis type", *args.AnalysisType) } - if *args.AnalysisType == singleSubnet && *args.Grouping { - return nil, fmt.Errorf("currently singleSubnet analysis type does not support grouping") + return fmt.Errorf("currently singleSubnet analysis type does not support grouping") } - if *args.OutputFormat == JSONFormat && *args.Grouping { - return nil, fmt.Errorf("json output format is not supported with grouping") + return fmt.Errorf("json output format is not supported with grouping") } - return &args, nil + if *args.AnalysisType == allSubnetsDiff && *args.OutputFormat != TEXTFormat { + return fmt.Errorf("currently only txt output format supported with diff_all_subnets") + } + return nil } diff --git a/pkg/ibmvpc/analysis_output_test.go b/pkg/ibmvpc/analysis_output_test.go index 27e2026ef..772b80f93 100644 --- a/pkg/ibmvpc/analysis_output_test.go +++ b/pkg/ibmvpc/analysis_output_test.go @@ -33,6 +33,7 @@ const ( type vpcGeneralTest struct { name string // test name inputConfig string // name (relative path) of input config file (json) + inputConfig2nd string // 2nd input file for diff expectedOutput map[vpcmodel.OutputUseCase]string // expected output file path actualOutput map[vpcmodel.OutputUseCase]string // actual output file path useCases []vpcmodel.OutputUseCase // the list of output use cases to test @@ -50,6 +51,7 @@ const ( suffixOutFileDebugSubnet = "_analysisPerSubnetSeparately" suffixOutFileSubnetsLevel = "subnetsBased_withPGW" suffixOutFileSubnetsLevelNoPGW = "subnetsBased_withoutPGW" + suffixOutFileDiffSubnets = "subnetsDiff" txtOutSuffix = ".txt" debugOutSuffix = "_debug.txt" mdOutSuffix = ".md" @@ -93,6 +95,8 @@ func getTestFileName(testName string, res = baseName + suffixOutFileSubnetsLevel case vpcmodel.AllSubnetsNoPGW: res = baseName + suffixOutFileSubnetsLevelNoPGW + case vpcmodel.AllSubnetsDiff: + res = baseName + suffixOutFileDiffSubnets } switch format { case vpcmodel.Text: @@ -120,9 +124,9 @@ func getTestFileName(testName string, // files names (actual and expected), per use case func (tt *vpcGeneralTest) initTest() { tt.inputConfig = inputFilePrefix + tt.name + ".json" + tt.inputConfig2nd = inputFilePrefix + tt.name + "_2nd.json" tt.expectedOutput = map[vpcmodel.OutputUseCase]string{} tt.actualOutput = map[vpcmodel.OutputUseCase]string{} - // init field of expected errs if tt.errPerUseCase == nil { tt.errPerUseCase = map[vpcmodel.OutputUseCase]error{} @@ -331,6 +335,13 @@ var tests = []*vpcGeneralTest{ useCases: []vpcmodel.OutputUseCase{vpcmodel.AllSubnets}, format: vpcmodel.Text, }, + { + name: "acl_testing5", + // TODO: currently for this test, there are 2 connections that only differ in statefulness attribute, and + // are not yet displayed in the diff report (sub1-1-ky => sub1-2-ky , sub1-1-ky => sub1-3-ky) + useCases: []vpcmodel.OutputUseCase{vpcmodel.AllSubnetsDiff}, + format: vpcmodel.Text, + }, } var formatsAvoidComparison = map[vpcmodel.OutFormat]bool{vpcmodel.ARCHDRAWIO: true, vpcmodel.DRAWIO: true} @@ -392,11 +403,23 @@ func (tt *vpcGeneralTest) runTest(t *testing.T) { tt.initTest() // get vpcConfigs obj from parsing + analyzing input config file - vpcConfigs := getVPCConfigs(t, tt) + vpcConfigs := getVPCConfigs(t, tt, true) + var vpcConfigs2nd map[string]*vpcmodel.VPCConfig + diffUseCase := false + for _, useCase := range tt.useCases { + if useCase == vpcmodel.AllSubnetsDiff { + diffUseCase = true + } + } + if diffUseCase { + vpcConfigs2nd = getVPCConfigs(t, tt, false) + } else { // inputConfig2nd should be ignored if not diffUseCase + tt.inputConfig2nd = "" + } // generate actual output for all use cases specified for this test for _, uc := range tt.useCases { - err := runTestPerUseCase(t, tt, vpcConfigs, uc, tt.mode) + err := runTestPerUseCase(t, tt, vpcConfigs, vpcConfigs2nd, uc, tt.mode) require.Equal(t, tt.errPerUseCase[uc], err, "comparing actual err to expected err") } for uc, outFile := range tt.actualOutput { @@ -405,8 +428,14 @@ func (tt *vpcGeneralTest) runTest(t *testing.T) { } // getVPCConfigs returns map[string]*vpcmodel.VPCConfig obj for the input test (config json file) -func getVPCConfigs(t *testing.T, tt *vpcGeneralTest) map[string]*vpcmodel.VPCConfig { - inputConfigFile := filepath.Join(getTestsDir(), tt.inputConfig) +func getVPCConfigs(t *testing.T, tt *vpcGeneralTest, firstCfg bool) map[string]*vpcmodel.VPCConfig { + var inputConfig string + if firstCfg { + inputConfig = tt.inputConfig + } else { + inputConfig = tt.inputConfig2nd + } + inputConfigFile := filepath.Join(getTestsDir(), inputConfig) inputConfigContent, err := os.ReadFile(inputConfigFile) if err != nil { t.Fatalf("err: %s", err) @@ -466,18 +495,23 @@ func initTestFileNames(tt *vpcGeneralTest, // runTestPerUseCase runs the connectivity analysis for the required use case and compares/generates the output func runTestPerUseCase(t *testing.T, tt *vpcGeneralTest, - c map[string]*vpcmodel.VPCConfig, + c1, c2 map[string]*vpcmodel.VPCConfig, uc vpcmodel.OutputUseCase, mode testMode) error { - numConfigs := len(c) + numConfigs := len(c1) allVPCsOutput := make([]*vpcmodel.VPCAnalysisOutput, numConfigs) i := 0 - for _, vpcConfig := range c { + var vpcConfig2nd *vpcmodel.VPCConfig + // note that for diff analysis mode a single vpcConfig in c1 is provided; c2 is assumed to have a single cfg + for _, vpcConfig := range c2 { + vpcConfig2nd = vpcConfig + } + for _, vpcConfig := range c1 { if err := initTestFileNames(tt, uc, numConfigs, vpcConfig.VPC.Name(), false); err != nil { return err } - og, err := vpcmodel.NewOutputGenerator(vpcConfig, tt.grouping, uc, tt.format == vpcmodel.ARCHDRAWIO) + og, err := vpcmodel.NewOutputGenerator(vpcConfig, vpcConfig2nd, tt.grouping, uc, tt.format == vpcmodel.ARCHDRAWIO) if err != nil { return err } diff --git a/pkg/ibmvpc/examples/acl_testing5subnetsBased_withPGW.txt b/pkg/ibmvpc/examples/acl_testing5subnetsBased_withPGW.txt index 07a1043ad..e88fecd49 100644 --- a/pkg/ibmvpc/examples/acl_testing5subnetsBased_withPGW.txt +++ b/pkg/ibmvpc/examples/acl_testing5subnetsBased_withPGW.txt @@ -1,4 +1,4 @@ -Analysis for VPC test-vpc-ky +Analysis for VPC test-vpc-ky1 combined connections between subnets: sub1-1-ky => Public Internet 8.8.8.8/32 : protocol: UDP dst-ports: 53 sub1-1-ky => sub1-2-ky : protocol: TCP diff --git a/pkg/ibmvpc/examples/acl_testing5subnetsDiff.txt b/pkg/ibmvpc/examples/acl_testing5subnetsDiff.txt new file mode 100644 index 000000000..5a3d7510e --- /dev/null +++ b/pkg/ibmvpc/examples/acl_testing5subnetsDiff.txt @@ -0,0 +1,9 @@ +Analysis for diff between VPC test-vpc-ky1 and VPC test-vpc-ky2 +-- sub1-2-ky => sub1-1-ky : missing connection +-- sub1-3-ky => sub1-1-ky : missing connection + +++ sub2-1-ky => Public Internet [8.8.8.0/29] : missing connection +++ sub2-1-ky => Public Internet [8.8.8.10/31] : missing connection +++ sub2-1-ky => Public Internet [8.8.8.12/30] : missing connection +++ sub2-1-ky => Public Internet [8.8.8.8/32] : changed connection protocol: UDP dst-ports: 43 +++ sub2-1-ky => Public Internet [8.8.8.9/32] : missing connection diff --git a/pkg/ibmvpc/examples/input_acl_testing5.json b/pkg/ibmvpc/examples/input_acl_testing5.json index 3bc7f8096..31ef2b356 100644 --- a/pkg/ibmvpc/examples/input_acl_testing5.json +++ b/pkg/ibmvpc/examples/input_acl_testing5.json @@ -53,7 +53,7 @@ }, "href": "href:2", "id": "id:3", - "name": "test-vpc-ky", + "name": "test-vpc-ky1", "resource_group": { "href": "href:15", "id": "id:16", diff --git a/pkg/ibmvpc/examples/input_acl_testing5_2nd.json b/pkg/ibmvpc/examples/input_acl_testing5_2nd.json new file mode 100644 index 000000000..ae534b4ac --- /dev/null +++ b/pkg/ibmvpc/examples/input_acl_testing5_2nd.json @@ -0,0 +1,1716 @@ +{ + "vpcs": [ + { + "classic_access": false, + "created_at": "2023-07-26T08:00:25.000Z", + "crn": "crn:1", + "cse_source_ips": [ + { + "ip": { + "address": "10.16.239.23" + }, + "zone": { + "href": "href:4", + "name": "us-south-1" + } + }, + { + "ip": { + "address": "10.16.246.249" + }, + "zone": { + "href": "href:5", + "name": "us-south-2" + } + }, + { + "ip": { + "address": "10.22.232.160" + }, + "zone": { + "href": "href:6", + "name": "us-south-3" + } + } + ], + "default_network_acl": { + "crn": "crn:7", + "href": "href:8", + "id": "id:9", + "name": "jawed-amicably-prepay-harmonize" + }, + "default_routing_table": { + "href": "href:10", + "id": "id:11", + "name": "emblem-spleen-valise-semisweet", + "resource_type": "routing_table" + }, + "default_security_group": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "matching-treadmill-lunchbox-gigahertz" + }, + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky2", + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "resource_type": "vpc", + "status": "available", + "tags": [] + } + ], + "subnets": [ + { + "available_ipv4_address_count": 251, + "created_at": "2023-07-26T08:01:20.000Z", + "crn": "crn:17", + "href": "href:18", + "id": "id:19", + "ip_version": "ipv4", + "ipv4_cidr_block": "10.240.1.0/24", + "name": "sub1-1-ky", + "network_acl": { + "crn": "crn:20", + "href": "href:21", + "id": "id:22", + "name": "acl1-1-ky" + }, + "public_gateway": { + "crn": "crn:23", + "href": "href:24", + "id": "id:25", + "name": "public-gw1-ky", + "resource_type": "public_gateway" + }, + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "resource_type": "subnet", + "routing_table": { + "href": "href:10", + "id": "id:11", + "name": "emblem-spleen-valise-semisweet", + "resource_type": "routing_table" + }, + "status": "available", + "total_ipv4_address_count": 256, + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:4", + "name": "us-south-1" + }, + "reserved_ips": [ + { + "address": "10.240.1.0", + "auto_delete": false, + "created_at": "2023-07-26T08:01:20.000Z", + "href": "href:26", + "id": "id:27", + "lifecycle_state": "stable", + "name": "ibm-network-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.1.1", + "auto_delete": false, + "created_at": "2023-07-26T08:01:20.000Z", + "href": "href:28", + "id": "id:29", + "lifecycle_state": "stable", + "name": "ibm-default-gateway", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.1.2", + "auto_delete": false, + "created_at": "2023-07-26T08:01:20.000Z", + "href": "href:30", + "id": "id:31", + "lifecycle_state": "stable", + "name": "ibm-dns-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.1.3", + "auto_delete": false, + "created_at": "2023-07-26T08:01:20.000Z", + "href": "href:32", + "id": "id:33", + "lifecycle_state": "stable", + "name": "ibm-reserved-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.1.255", + "auto_delete": false, + "created_at": "2023-07-26T08:01:20.000Z", + "href": "href:34", + "id": "id:35", + "lifecycle_state": "stable", + "name": "ibm-broadcast-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + } + ], + "tags": [] + }, + { + "available_ipv4_address_count": 251, + "created_at": "2023-07-26T08:01:08.000Z", + "crn": "crn:36", + "href": "href:37", + "id": "id:38", + "ip_version": "ipv4", + "ipv4_cidr_block": "10.240.2.0/24", + "name": "sub1-2-ky", + "network_acl": { + "crn": "crn:39", + "href": "href:40", + "id": "id:41", + "name": "acl1-2-ky" + }, + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "resource_type": "subnet", + "routing_table": { + "href": "href:10", + "id": "id:11", + "name": "emblem-spleen-valise-semisweet", + "resource_type": "routing_table" + }, + "status": "available", + "total_ipv4_address_count": 256, + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:4", + "name": "us-south-1" + }, + "reserved_ips": [ + { + "address": "10.240.2.0", + "auto_delete": false, + "created_at": "2023-07-26T08:01:08.000Z", + "href": "href:42", + "id": "id:43", + "lifecycle_state": "stable", + "name": "ibm-network-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.2.1", + "auto_delete": false, + "created_at": "2023-07-26T08:01:08.000Z", + "href": "href:44", + "id": "id:45", + "lifecycle_state": "stable", + "name": "ibm-default-gateway", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.2.2", + "auto_delete": false, + "created_at": "2023-07-26T08:01:08.000Z", + "href": "href:46", + "id": "id:47", + "lifecycle_state": "stable", + "name": "ibm-dns-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.2.3", + "auto_delete": false, + "created_at": "2023-07-26T08:01:08.000Z", + "href": "href:48", + "id": "id:49", + "lifecycle_state": "stable", + "name": "ibm-reserved-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.2.255", + "auto_delete": false, + "created_at": "2023-07-26T08:01:08.000Z", + "href": "href:50", + "id": "id:51", + "lifecycle_state": "stable", + "name": "ibm-broadcast-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + } + ], + "tags": [] + }, + { + "available_ipv4_address_count": 251, + "created_at": "2023-07-26T08:01:08.000Z", + "crn": "crn:52", + "href": "href:53", + "id": "id:54", + "ip_version": "ipv4", + "ipv4_cidr_block": "10.240.64.0/24", + "name": "sub2-1-ky", + "network_acl": { + "crn": "crn:55", + "href": "href:56", + "id": "id:57", + "name": "acl2-1-ky" + }, + "public_gateway": { + "crn": "crn:58", + "href": "href:59", + "id": "id:60", + "name": "public-gw2-ky", + "resource_type": "public_gateway" + }, + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "resource_type": "subnet", + "routing_table": { + "href": "href:10", + "id": "id:11", + "name": "emblem-spleen-valise-semisweet", + "resource_type": "routing_table" + }, + "status": "available", + "total_ipv4_address_count": 256, + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:5", + "name": "us-south-2" + }, + "reserved_ips": [ + { + "address": "10.240.64.0", + "auto_delete": false, + "created_at": "2023-07-26T08:01:08.000Z", + "href": "href:61", + "id": "id:62", + "lifecycle_state": "stable", + "name": "ibm-network-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.64.1", + "auto_delete": false, + "created_at": "2023-07-26T08:01:08.000Z", + "href": "href:63", + "id": "id:64", + "lifecycle_state": "stable", + "name": "ibm-default-gateway", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.64.2", + "auto_delete": false, + "created_at": "2023-07-26T08:01:08.000Z", + "href": "href:65", + "id": "id:66", + "lifecycle_state": "stable", + "name": "ibm-dns-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.64.3", + "auto_delete": false, + "created_at": "2023-07-26T08:01:08.000Z", + "href": "href:67", + "id": "id:68", + "lifecycle_state": "stable", + "name": "ibm-reserved-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.64.255", + "auto_delete": false, + "created_at": "2023-07-26T08:01:08.000Z", + "href": "href:69", + "id": "id:70", + "lifecycle_state": "stable", + "name": "ibm-broadcast-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + } + ], + "tags": [] + }, + { + "available_ipv4_address_count": 251, + "created_at": "2023-07-26T08:00:55.000Z", + "crn": "crn:71", + "href": "href:72", + "id": "id:73", + "ip_version": "ipv4", + "ipv4_cidr_block": "10.240.3.0/24", + "name": "sub1-3-ky", + "network_acl": { + "crn": "crn:39", + "href": "href:40", + "id": "id:41", + "name": "acl1-2-ky" + }, + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "resource_type": "subnet", + "routing_table": { + "href": "href:10", + "id": "id:11", + "name": "emblem-spleen-valise-semisweet", + "resource_type": "routing_table" + }, + "status": "available", + "total_ipv4_address_count": 256, + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:4", + "name": "us-south-1" + }, + "reserved_ips": [ + { + "address": "10.240.3.0", + "auto_delete": false, + "created_at": "2023-07-26T08:00:55.000Z", + "href": "href:74", + "id": "id:75", + "lifecycle_state": "stable", + "name": "ibm-network-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.3.1", + "auto_delete": false, + "created_at": "2023-07-26T08:00:55.000Z", + "href": "href:76", + "id": "id:77", + "lifecycle_state": "stable", + "name": "ibm-default-gateway", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.3.2", + "auto_delete": false, + "created_at": "2023-07-26T08:00:55.000Z", + "href": "href:78", + "id": "id:79", + "lifecycle_state": "stable", + "name": "ibm-dns-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.3.3", + "auto_delete": false, + "created_at": "2023-07-26T08:00:55.000Z", + "href": "href:80", + "id": "id:81", + "lifecycle_state": "stable", + "name": "ibm-reserved-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.3.255", + "auto_delete": false, + "created_at": "2023-07-26T08:00:55.000Z", + "href": "href:82", + "id": "id:83", + "lifecycle_state": "stable", + "name": "ibm-broadcast-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + } + ], + "tags": [] + }, + { + "available_ipv4_address_count": 251, + "created_at": "2023-07-26T08:00:55.000Z", + "crn": "crn:84", + "href": "href:85", + "id": "id:86", + "ip_version": "ipv4", + "ipv4_cidr_block": "10.240.65.0/24", + "name": "sub2-2-ky", + "network_acl": { + "crn": "crn:87", + "href": "href:88", + "id": "id:89", + "name": "acl2-2-ky" + }, + "public_gateway": { + "crn": "crn:58", + "href": "href:59", + "id": "id:60", + "name": "public-gw2-ky", + "resource_type": "public_gateway" + }, + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "resource_type": "subnet", + "routing_table": { + "href": "href:10", + "id": "id:11", + "name": "emblem-spleen-valise-semisweet", + "resource_type": "routing_table" + }, + "status": "available", + "total_ipv4_address_count": 256, + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:5", + "name": "us-south-2" + }, + "reserved_ips": [ + { + "address": "10.240.65.0", + "auto_delete": false, + "created_at": "2023-07-26T08:00:55.000Z", + "href": "href:90", + "id": "id:91", + "lifecycle_state": "stable", + "name": "ibm-network-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.65.1", + "auto_delete": false, + "created_at": "2023-07-26T08:00:55.000Z", + "href": "href:92", + "id": "id:93", + "lifecycle_state": "stable", + "name": "ibm-default-gateway", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.65.2", + "auto_delete": false, + "created_at": "2023-07-26T08:00:55.000Z", + "href": "href:94", + "id": "id:95", + "lifecycle_state": "stable", + "name": "ibm-dns-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.65.3", + "auto_delete": false, + "created_at": "2023-07-26T08:00:55.000Z", + "href": "href:96", + "id": "id:97", + "lifecycle_state": "stable", + "name": "ibm-reserved-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.65.255", + "auto_delete": false, + "created_at": "2023-07-26T08:00:55.000Z", + "href": "href:98", + "id": "id:99", + "lifecycle_state": "stable", + "name": "ibm-broadcast-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + } + ], + "tags": [] + }, + { + "available_ipv4_address_count": 251, + "created_at": "2023-07-26T08:00:49.000Z", + "crn": "crn:100", + "href": "href:101", + "id": "id:102", + "ip_version": "ipv4", + "ipv4_cidr_block": "10.240.128.0/24", + "name": "sub3-1-ky", + "network_acl": { + "crn": "crn:103", + "href": "href:104", + "id": "id:105", + "name": "acl3-1-ky" + }, + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "resource_type": "subnet", + "routing_table": { + "href": "href:10", + "id": "id:11", + "name": "emblem-spleen-valise-semisweet", + "resource_type": "routing_table" + }, + "status": "available", + "total_ipv4_address_count": 256, + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:6", + "name": "us-south-3" + }, + "reserved_ips": [ + { + "address": "10.240.128.0", + "auto_delete": false, + "created_at": "2023-07-26T08:00:49.000Z", + "href": "href:106", + "id": "id:107", + "lifecycle_state": "stable", + "name": "ibm-network-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.128.1", + "auto_delete": false, + "created_at": "2023-07-26T08:00:49.000Z", + "href": "href:108", + "id": "id:109", + "lifecycle_state": "stable", + "name": "ibm-default-gateway", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.128.2", + "auto_delete": false, + "created_at": "2023-07-26T08:00:49.000Z", + "href": "href:110", + "id": "id:111", + "lifecycle_state": "stable", + "name": "ibm-dns-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.128.3", + "auto_delete": false, + "created_at": "2023-07-26T08:00:49.000Z", + "href": "href:112", + "id": "id:113", + "lifecycle_state": "stable", + "name": "ibm-reserved-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.128.255", + "auto_delete": false, + "created_at": "2023-07-26T08:00:49.000Z", + "href": "href:114", + "id": "id:115", + "lifecycle_state": "stable", + "name": "ibm-broadcast-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + } + ], + "tags": [] + } + ], + "public_gateways": [ + { + "created_at": "2023-07-26T08:00:43.000Z", + "crn": "crn:23", + "floating_ip": { + "address": "52.116.140.187", + "crn": "crn:116", + "href": "href:117", + "id": "id:118", + "name": "public-gw1-ky" + }, + "href": "href:24", + "id": "id:25", + "name": "public-gw1-ky", + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "resource_type": "public_gateway", + "status": "available", + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:4", + "name": "us-south-1" + }, + "tags": [] + }, + { + "created_at": "2023-07-26T08:00:42.000Z", + "crn": "crn:58", + "floating_ip": { + "address": "52.118.211.246", + "crn": "crn:119", + "href": "href:120", + "id": "id:121", + "name": "public-gw2-ky" + }, + "href": "href:59", + "id": "id:60", + "name": "public-gw2-ky", + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "resource_type": "public_gateway", + "status": "available", + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:5", + "name": "us-south-2" + }, + "tags": [] + } + ], + "floating_ips": [ + { + "address": "52.118.211.246", + "created_at": "2023-07-26T08:00:42.000Z", + "crn": "crn:119", + "href": "href:120", + "id": "id:121", + "name": "public-gw2-ky", + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "status": "available", + "target": { + "href": "href:59", + "id": "id:60", + "name": "public-gw2-ky", + "resource_type": "public_gateway", + "crn": "crn:58" + }, + "zone": { + "href": "href:5", + "name": "us-south-2" + }, + "tags": [] + }, + { + "address": "52.116.140.187", + "created_at": "2023-07-26T08:00:42.000Z", + "crn": "crn:116", + "href": "href:117", + "id": "id:118", + "name": "public-gw1-ky", + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "status": "available", + "target": { + "href": "href:24", + "id": "id:25", + "name": "public-gw1-ky", + "resource_type": "public_gateway", + "crn": "crn:23" + }, + "zone": { + "href": "href:4", + "name": "us-south-1" + }, + "tags": [] + } + ], + "network_acls": [ + { + "created_at": "2023-07-26T08:00:42.000Z", + "crn": "crn:39", + "href": "href:40", + "id": "id:41", + "name": "acl1-2-ky", + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "rules": [ + { + "action": "allow", + "before": { + "href": "href:126", + "id": "id:127", + "name": "i1" + }, + "created_at": "2023-07-26T08:00:43.000Z", + "destination": "10.240.2.0/23", + "direction": "outbound", + "href": "href:124", + "id": "id:125", + "ip_version": "ipv4", + "name": "o2", + "source": "10.240.2.0/23", + "destination_port_max": 65535, + "destination_port_min": 1, + "protocol": "tcp", + "source_port_max": 65535, + "source_port_min": 1 + }, + { + "action": "allow", + "before": { + "href": "href:128", + "id": "id:129", + "name": "i2" + }, + "created_at": "2023-07-26T08:00:44.000Z", + "destination": "10.240.2.0/23", + "direction": "inbound", + "href": "href:126", + "id": "id:127", + "ip_version": "ipv4", + "name": "i1", + "source": "10.240.1.0/24", + "destination_port_max": 65535, + "destination_port_min": 1, + "protocol": "tcp", + "source_port_max": 65535, + "source_port_min": 1 + }, + { + "action": "allow", + "created_at": "2023-07-26T08:00:44.000Z", + "destination": "10.240.2.0/23", + "direction": "inbound", + "href": "href:128", + "id": "id:129", + "ip_version": "ipv4", + "name": "i2", + "source": "10.240.2.0/23", + "destination_port_max": 65535, + "destination_port_min": 1, + "protocol": "tcp", + "source_port_max": 65535, + "source_port_min": 1 + } + ], + "subnets": [ + { + "crn": "crn:36", + "href": "href:37", + "id": "id:38", + "name": "sub1-2-ky", + "resource_type": "subnet" + }, + { + "crn": "crn:71", + "href": "href:72", + "id": "id:73", + "name": "sub1-3-ky", + "resource_type": "subnet" + } + ], + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "tags": [] + }, + { + "created_at": "2023-07-26T08:00:42.000Z", + "crn": "crn:55", + "href": "href:56", + "id": "id:57", + "name": "acl2-1-ky", + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "rules": [ + { + "action": "allow", + "before": { + "href": "href:132", + "id": "id:133", + "name": "o2" + }, + "created_at": "2023-07-26T08:00:43.000Z", + "destination": "8.8.8.8/28", + "direction": "outbound", + "href": "href:130", + "id": "id:131", + "ip_version": "ipv4", + "name": "o1", + "source": "10.240.64.0/24", + "destination_port_max": 53, + "destination_port_min": 53, + "protocol": "udp", + "source_port_max": 65535, + "source_port_min": 1 + }, + { + "action": "allow", + "before": { + "href": "href:132", + "id": "id:133", + "name": "o2" + }, + "created_at": "2023-07-26T08:00:43.000Z", + "destination": "8.8.8.8/32", + "direction": "outbound", + "href": "href:130", + "id": "id:131", + "ip_version": "ipv4", + "name": "o1", + "source": "10.240.64.0/24", + "destination_port_max": 43, + "destination_port_min": 43, + "protocol": "udp", + "source_port_max": 65535, + "source_port_min": 1 + }, + { + "action": "allow", + "before": { + "href": "href:134", + "id": "id:135", + "name": "o3" + }, + "created_at": "2023-07-26T08:00:44.000Z", + "destination": "10.240.65.0/24", + "direction": "outbound", + "href": "href:132", + "id": "id:133", + "ip_version": "ipv4", + "name": "o2", + "source": "10.240.64.0/24", + "protocol": "all" + }, + { + "action": "allow", + "before": { + "href": "href:136", + "id": "id:137", + "name": "o4" + }, + "created_at": "2023-07-26T08:00:44.000Z", + "destination": "10.240.128.0/24", + "direction": "outbound", + "href": "href:134", + "id": "id:135", + "ip_version": "ipv4", + "name": "o3", + "source": "10.240.64.0/24", + "destination_port_max": 65535, + "destination_port_min": 1, + "protocol": "tcp", + "source_port_max": 443, + "source_port_min": 443 + }, + { + "action": "allow", + "before": { + "href": "href:138", + "id": "id:139", + "name": "i1" + }, + "created_at": "2023-07-26T08:00:45.000Z", + "destination": "10.240.128.0/24", + "direction": "outbound", + "href": "href:136", + "id": "id:137", + "ip_version": "ipv4", + "name": "o4", + "source": "10.240.64.0/24", + "code": 0, + "protocol": "icmp", + "type": 0 + }, + { + "action": "allow", + "before": { + "href": "href:140", + "id": "id:141", + "name": "i2" + }, + "created_at": "2023-07-26T08:00:45.000Z", + "destination": "10.240.64.0/24", + "direction": "inbound", + "href": "href:138", + "id": "id:139", + "ip_version": "ipv4", + "name": "i1", + "source": "8.8.8.8/32", + "destination_port_max": 65535, + "destination_port_min": 1, + "protocol": "udp", + "source_port_max": 53, + "source_port_min": 53 + }, + { + "action": "allow", + "before": { + "href": "href:142", + "id": "id:143", + "name": "i3" + }, + "created_at": "2023-07-26T08:00:45.000Z", + "destination": "10.240.64.0/24", + "direction": "inbound", + "href": "href:140", + "id": "id:141", + "ip_version": "ipv4", + "name": "i2", + "source": "10.240.65.0/24", + "protocol": "all" + }, + { + "action": "allow", + "before": { + "href": "href:144", + "id": "id:145", + "name": "i4" + }, + "created_at": "2023-07-26T08:00:46.000Z", + "destination": "10.240.64.0/24", + "direction": "inbound", + "href": "href:142", + "id": "id:143", + "ip_version": "ipv4", + "name": "i3", + "source": "10.240.128.0/24", + "destination_port_max": 443, + "destination_port_min": 443, + "protocol": "tcp", + "source_port_max": 65535, + "source_port_min": 1 + }, + { + "action": "allow", + "created_at": "2023-07-26T08:00:46.000Z", + "destination": "10.240.64.0/24", + "direction": "inbound", + "href": "href:144", + "id": "id:145", + "ip_version": "ipv4", + "name": "i4", + "source": "10.240.128.0/24", + "code": 0, + "protocol": "icmp", + "type": 0 + } + ], + "subnets": [ + { + "crn": "crn:52", + "href": "href:53", + "id": "id:54", + "name": "sub2-1-ky", + "resource_type": "subnet" + } + ], + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "tags": [] + }, + { + "created_at": "2023-07-26T08:00:42.000Z", + "crn": "crn:20", + "href": "href:21", + "id": "id:22", + "name": "acl1-1-ky", + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "rules": [ + { + "action": "allow", + "before": { + "href": "href:148", + "id": "id:149", + "name": "o2" + }, + "created_at": "2023-07-26T08:00:42.000Z", + "destination": "8.8.8.8/32", + "direction": "outbound", + "href": "href:146", + "id": "id:147", + "ip_version": "ipv4", + "name": "o1", + "source": "10.240.1.0/24", + "destination_port_max": 53, + "destination_port_min": 53, + "protocol": "udp", + "source_port_max": 65535, + "source_port_min": 1 + }, + { + "action": "allow", + "before": { + "href": "href:150", + "id": "id:151", + "name": "o3" + }, + "created_at": "2023-07-26T08:00:43.000Z", + "destination": "10.240.2.0/23", + "direction": "outbound", + "href": "href:148", + "id": "id:149", + "ip_version": "ipv4", + "name": "o2", + "source": "10.240.1.0/24", + "destination_port_max": 65535, + "destination_port_min": 1, + "protocol": "tcp", + "source_port_max": 65535, + "source_port_min": 1 + }, + { + "action": "allow", + "before": { + "href": "href:152", + "id": "id:153", + "name": "i1" + }, + "created_at": "2023-07-26T08:00:43.000Z", + "destination": "10.240.128.0/24", + "direction": "outbound", + "href": "href:150", + "id": "id:151", + "ip_version": "ipv4", + "name": "o3", + "source": "10.240.1.0/24", + "code": 0, + "protocol": "icmp", + "type": 0 + }, + { + "action": "allow", + "before": { + "href": "href:154", + "id": "id:155", + "name": "i2" + }, + "created_at": "2023-07-26T08:00:44.000Z", + "destination": "10.240.1.0/24", + "direction": "inbound", + "href": "href:152", + "id": "id:153", + "ip_version": "ipv4", + "name": "i1", + "source": "8.8.8.8/32", + "destination_port_max": 65535, + "destination_port_min": 1, + "protocol": "udp", + "source_port_max": 53, + "source_port_min": 53 + }, + { + "action": "allow", + "before": { + "href": "href:156", + "id": "id:157", + "name": "i3" + }, + "created_at": "2023-07-26T08:00:45.000Z", + "destination": "10.240.1.0/24", + "direction": "inbound", + "href": "href:154", + "id": "id:155", + "ip_version": "ipv4", + "name": "i2", + "source": "10.240.2.0/23", + "destination_port_max": 65535, + "destination_port_min": 1, + "protocol": "tcp", + "source_port_max": 65535, + "source_port_min": 1 + }, + { + "action": "allow", + "created_at": "2023-07-26T08:00:45.000Z", + "destination": "10.240.1.0/24", + "direction": "inbound", + "href": "href:156", + "id": "id:157", + "ip_version": "ipv4", + "name": "i3", + "source": "10.240.128.0/24", + "code": 0, + "protocol": "icmp", + "type": 0 + } + ], + "subnets": [ + { + "crn": "crn:17", + "href": "href:18", + "id": "id:19", + "name": "sub1-1-ky", + "resource_type": "subnet" + } + ], + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "tags": [] + }, + { + "created_at": "2023-07-26T08:00:42.000Z", + "crn": "crn:87", + "href": "href:88", + "id": "id:89", + "name": "acl2-2-ky", + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "rules": [ + { + "action": "allow", + "before": { + "href": "href:160", + "id": "id:161", + "name": "i1" + }, + "created_at": "2023-07-26T08:00:43.000Z", + "destination": "10.240.64.0/24", + "direction": "outbound", + "href": "href:158", + "id": "id:159", + "ip_version": "ipv4", + "name": "o1", + "source": "10.240.65.0/24", + "protocol": "all" + }, + { + "action": "allow", + "created_at": "2023-07-26T08:00:43.000Z", + "destination": "10.240.65.0/24", + "direction": "inbound", + "href": "href:160", + "id": "id:161", + "ip_version": "ipv4", + "name": "i1", + "source": "10.240.64.0/24", + "protocol": "all" + } + ], + "subnets": [ + { + "crn": "crn:84", + "href": "href:85", + "id": "id:86", + "name": "sub2-2-ky", + "resource_type": "subnet" + } + ], + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "tags": [] + }, + { + "created_at": "2023-07-26T08:00:42.000Z", + "crn": "crn:103", + "href": "href:104", + "id": "id:105", + "name": "acl3-1-ky", + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "rules": [ + { + "action": "allow", + "before": { + "href": "href:164", + "id": "id:165", + "name": "o2" + }, + "created_at": "2023-07-26T08:00:43.000Z", + "destination": "10.240.64.0/24", + "direction": "outbound", + "href": "href:162", + "id": "id:163", + "ip_version": "ipv4", + "name": "o1", + "source": "10.240.128.0/24", + "destination_port_max": 443, + "destination_port_min": 443, + "protocol": "tcp", + "source_port_max": 65535, + "source_port_min": 1 + }, + { + "action": "allow", + "before": { + "href": "href:166", + "id": "id:167", + "name": "o3" + }, + "created_at": "2023-07-26T08:00:44.000Z", + "destination": "10.240.64.0/24", + "direction": "outbound", + "href": "href:164", + "id": "id:165", + "ip_version": "ipv4", + "name": "o2", + "source": "10.240.128.0/24", + "code": 0, + "protocol": "icmp", + "type": 0 + }, + { + "action": "allow", + "before": { + "href": "href:168", + "id": "id:169", + "name": "o4" + }, + "created_at": "2023-07-26T08:00:44.000Z", + "destination": "10.240.1.0/24", + "direction": "outbound", + "href": "href:166", + "id": "id:167", + "ip_version": "ipv4", + "name": "o3", + "source": "10.240.128.0/24", + "code": 0, + "protocol": "icmp", + "type": 0 + }, + { + "action": "allow", + "before": { + "href": "href:170", + "id": "id:171", + "name": "i1" + }, + "created_at": "2023-07-26T08:00:45.000Z", + "destination": "10.240.64.0/24", + "direction": "outbound", + "href": "href:168", + "id": "id:169", + "ip_version": "ipv4", + "name": "o4", + "source": "10.240.128.0/24", + "destination_port_max": 443, + "destination_port_min": 443, + "protocol": "tcp", + "source_port_max": 65535, + "source_port_min": 1 + }, + { + "action": "allow", + "before": { + "href": "href:172", + "id": "id:173", + "name": "i2" + }, + "created_at": "2023-07-26T08:00:45.000Z", + "destination": "10.240.128.0/24", + "direction": "inbound", + "href": "href:170", + "id": "id:171", + "ip_version": "ipv4", + "name": "i1", + "source": "10.240.64.0/24", + "destination_port_max": 65535, + "destination_port_min": 1, + "protocol": "tcp", + "source_port_max": 443, + "source_port_min": 443 + }, + { + "action": "allow", + "before": { + "href": "href:174", + "id": "id:175", + "name": "i3" + }, + "created_at": "2023-07-26T08:00:46.000Z", + "destination": "10.240.128.0/24", + "direction": "inbound", + "href": "href:172", + "id": "id:173", + "ip_version": "ipv4", + "name": "i2", + "source": "10.240.64.0/24", + "code": 0, + "protocol": "icmp", + "type": 0 + }, + { + "action": "allow", + "before": { + "href": "href:176", + "id": "id:177", + "name": "i4" + }, + "created_at": "2023-07-26T08:00:46.000Z", + "destination": "10.240.128.0/24", + "direction": "inbound", + "href": "href:174", + "id": "id:175", + "ip_version": "ipv4", + "name": "i3", + "source": "10.240.1.0/24", + "code": 0, + "protocol": "icmp", + "type": 0 + }, + { + "action": "allow", + "created_at": "2023-07-26T08:00:47.000Z", + "destination": "10.240.128.0/24", + "direction": "inbound", + "href": "href:176", + "id": "id:177", + "ip_version": "ipv4", + "name": "i4", + "source": "10.240.64.0/24", + "destination_port_max": 65535, + "destination_port_min": 1, + "protocol": "tcp", + "source_port_max": 443, + "source_port_min": 443 + } + ], + "subnets": [ + { + "crn": "crn:100", + "href": "href:101", + "id": "id:102", + "name": "sub3-1-ky", + "resource_type": "subnet" + } + ], + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "tags": [] + }, + { + "created_at": "2023-07-26T08:00:25.000Z", + "crn": "crn:7", + "href": "href:8", + "id": "id:9", + "name": "jawed-amicably-prepay-harmonize", + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "rules": [ + { + "action": "allow", + "before": { + "href": "href:180", + "id": "id:181", + "name": "allow-outbound" + }, + "created_at": "2023-07-26T08:00:25.000Z", + "destination": "0.0.0.0/0", + "direction": "inbound", + "href": "href:178", + "id": "id:179", + "ip_version": "ipv4", + "name": "allow-inbound", + "source": "0.0.0.0/0", + "protocol": "all" + }, + { + "action": "allow", + "created_at": "2023-07-26T08:00:25.000Z", + "destination": "0.0.0.0/0", + "direction": "outbound", + "href": "href:180", + "id": "id:181", + "ip_version": "ipv4", + "name": "allow-outbound", + "source": "0.0.0.0/0", + "protocol": "all" + } + ], + "subnets": [], + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "tags": [] + } + ], + "security_groups": [ + { + "created_at": "2023-07-26T08:00:42.000Z", + "crn": "crn:182", + "href": "href:183", + "id": "id:184", + "name": "sg1-ky", + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "rules": [ + { + "direction": "outbound", + "href": "href:185", + "id": "id:186", + "ip_version": "ipv4", + "remote": { + "cidr_block": "0.0.0.0/0" + }, + "protocol": "all" + }, + { + "direction": "inbound", + "href": "href:187", + "id": "id:188", + "ip_version": "ipv4", + "remote": { + "cidr_block": "0.0.0.0/0" + }, + "protocol": "all" + } + ], + "targets": [], + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "tags": [] + }, + { + "created_at": "2023-07-26T08:00:25.000Z", + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "matching-treadmill-lunchbox-gigahertz", + "resource_group": { + "href": "href:15", + "id": "id:16", + "name": "anonymous" + }, + "rules": [ + { + "direction": "outbound", + "href": "href:189", + "id": "id:190", + "ip_version": "ipv4", + "remote": { + "cidr_block": "0.0.0.0/0" + }, + "protocol": "all" + }, + { + "direction": "inbound", + "href": "href:191", + "id": "id:192", + "ip_version": "ipv4", + "remote": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "matching-treadmill-lunchbox-gigahertz" + }, + "protocol": "all" + } + ], + "targets": [], + "vpc": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "test-vpc-ky", + "resource_type": "vpc" + }, + "tags": [] + } + ], + "endpoint_gateways": [], + "instances": [], + "routing_tables": [ + { + "accept_routes_from": [ + { + "resource_type": "vpn_gateway" + }, + { + "resource_type": "vpn_server" + } + ], + "created_at": "2023-07-26T08:00:25.000Z", + "href": "href:10", + "id": "id:11", + "is_default": true, + "lifecycle_state": "stable", + "name": "emblem-spleen-valise-semisweet", + "resource_type": "routing_table", + "route_direct_link_ingress": false, + "route_internet_ingress": false, + "route_transit_gateway_ingress": false, + "route_vpc_zone_ingress": false, + "subnets": [ + { + "crn": "crn:17", + "href": "href:18", + "id": "id:19", + "name": "sub1-1-ky", + "resource_type": "subnet" + }, + { + "crn": "crn:36", + "href": "href:37", + "id": "id:38", + "name": "sub1-2-ky", + "resource_type": "subnet" + }, + { + "crn": "crn:52", + "href": "href:53", + "id": "id:54", + "name": "sub2-1-ky", + "resource_type": "subnet" + }, + { + "crn": "crn:71", + "href": "href:72", + "id": "id:73", + "name": "sub1-3-ky", + "resource_type": "subnet" + }, + { + "crn": "crn:84", + "href": "href:85", + "id": "id:86", + "name": "sub2-2-ky", + "resource_type": "subnet" + }, + { + "crn": "crn:100", + "href": "href:101", + "id": "id:102", + "name": "sub3-1-ky", + "resource_type": "subnet" + } + ], + "routes": [] + } + ], + "load_balancers": [], + "iks_worker_nodes": [] +} diff --git a/pkg/vpcmodel/debugOutput.go b/pkg/vpcmodel/debugOutput.go index bdc46d47a..bccd04daf 100644 --- a/pkg/vpcmodel/debugOutput.go +++ b/pkg/vpcmodel/debugOutput.go @@ -3,13 +3,14 @@ package vpcmodel type DebugOutputFormatter struct { } -func (t *DebugOutputFormatter) WriteOutput(c *VPCConfig, +func (t *DebugOutputFormatter) WriteOutput(c1, c2 *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, + subnetsDiff *DiffBetweenSubnets, outFile string, grouping bool, uc OutputUseCase) (*VPCAnalysisOutput, error) { - out := headerOfAnalyzedVPC(c.VPC.Name()) + out := headerOfAnalyzedVPC(c1.VPC.Name(), "") switch uc { case AllEndpoints: // TODO: add a flag of whether to include grouped output or not @@ -18,8 +19,8 @@ func (t *DebugOutputFormatter) WriteOutput(c *VPCConfig, case AllSubnets: out = subnetsConn.String() case SingleSubnet: - out = c.GetConnectivityOutputPerEachSubnetSeparately() + out = c1.GetConnectivityOutputPerEachSubnetSeparately() } _, err := WriteToFile(out, outFile) - return &VPCAnalysisOutput{Output: out, VPCName: c.VPC.Name(), format: Debug}, err + return &VPCAnalysisOutput{Output: out, VPCName: c1.VPC.Name(), format: Debug}, err } diff --git a/pkg/vpcmodel/drawioOutput.go b/pkg/vpcmodel/drawioOutput.go index 78b093526..493088604 100644 --- a/pkg/vpcmodel/drawioOutput.go +++ b/pkg/vpcmodel/drawioOutput.go @@ -109,16 +109,17 @@ func (d *DrawioOutputFormatter) createEdges() { } } -func (d *DrawioOutputFormatter) WriteOutput(c *VPCConfig, +func (d *DrawioOutputFormatter) WriteOutput(c1, c2 *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, + subnetsDiff *DiffBetweenSubnets, outFile string, grouping bool, uc OutputUseCase) (*VPCAnalysisOutput, error) { var err error switch uc { case AllEndpoints: - d.init(c, conn) + d.init(c1, conn) d.createDrawioTree() err = drawio.CreateDrawioConnectivityMapFile(d.gen.Network(), outFile) case AllSubnets, SingleSubnet: @@ -136,17 +137,18 @@ type ArchDrawioOutputFormatter struct { DrawioOutputFormatter } -func (d *ArchDrawioOutputFormatter) WriteOutput(c *VPCConfig, +func (d *ArchDrawioOutputFormatter) WriteOutput(c1, c2 *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, + subnetsDiff *DiffBetweenSubnets, outFile string, grouping bool, uc OutputUseCase) (*VPCAnalysisOutput, error) { switch uc { case AllEndpoints: - return d.DrawioOutputFormatter.WriteOutput(c, nil, nil, outFile, grouping, uc) + return d.DrawioOutputFormatter.WriteOutput(c1, c2, nil, nil, nil, outFile, grouping, uc) case AllSubnets, SingleSubnet: - return d.DrawioOutputFormatter.WriteOutput(nil, nil, nil, outFile, grouping, uc) + return d.DrawioOutputFormatter.WriteOutput(nil, c2, nil, nil, nil, outFile, grouping, uc) } return &VPCAnalysisOutput{}, nil } diff --git a/pkg/vpcmodel/jsonOutput.go b/pkg/vpcmodel/jsonOutput.go index 32ab69cd0..0c88219af 100644 --- a/pkg/vpcmodel/jsonOutput.go +++ b/pkg/vpcmodel/jsonOutput.go @@ -11,9 +11,10 @@ import ( type JSONoutputFormatter struct { } -func (j *JSONoutputFormatter) WriteOutput(c *VPCConfig, +func (j *JSONoutputFormatter) WriteOutput(c1, c2 *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, + subnetsDiff *DiffBetweenSubnets, outFile string, grouping bool, uc OutputUseCase) (*VPCAnalysisOutput, error) { @@ -27,7 +28,7 @@ func (j *JSONoutputFormatter) WriteOutput(c *VPCConfig, return nil, errors.New("DebugSubnet use case not supported for JSON format currently ") } outStr, err := writeJSON(all, outFile) - return &VPCAnalysisOutput{Output: outStr, VPCName: c.VPC.Name(), format: JSON, jsonStruct: all}, err + return &VPCAnalysisOutput{Output: outStr, VPCName: c1.VPC.Name(), format: JSON, jsonStruct: all}, err } type connLine struct { diff --git a/pkg/vpcmodel/mdOutput.go b/pkg/vpcmodel/mdOutput.go index 6c3c8971d..82afca332 100644 --- a/pkg/vpcmodel/mdOutput.go +++ b/pkg/vpcmodel/mdOutput.go @@ -15,14 +15,15 @@ const ( mdHeader = "| src | dst | conn |\n|-----|-----|------|" ) -func (m *MDoutputFormatter) WriteOutput(c *VPCConfig, +func (m *MDoutputFormatter) WriteOutput(c1, c2 *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, + subnetsDiff *DiffBetweenSubnets, outFile string, grouping bool, uc OutputUseCase) (*VPCAnalysisOutput, error) { // get output by analysis type - out := "# " + headerOfAnalyzedVPC(c.VPC.Name()) + out := "# " + headerOfAnalyzedVPC(c1.VPC.Name(), "") switch uc { case AllEndpoints: lines := []string{mdTitle, mdHeader} @@ -38,7 +39,7 @@ func (m *MDoutputFormatter) WriteOutput(c *VPCConfig, } _, err := WriteToFile(out, outFile) - return &VPCAnalysisOutput{Output: out, VPCName: c.VPC.Name(), format: MD}, err + return &VPCAnalysisOutput{Output: out, VPCName: c1.VPC.Name(), format: MD}, err } func (m *MDoutputFormatter) getGroupedOutput(conn *VPCConnectivity) []string { diff --git a/pkg/vpcmodel/nodesConnectivity.go b/pkg/vpcmodel/nodesConnectivity.go index b28fc268c..0741216c2 100644 --- a/pkg/vpcmodel/nodesConnectivity.go +++ b/pkg/vpcmodel/nodesConnectivity.go @@ -131,7 +131,7 @@ func (c *VPCConfig) getAllowedConnsPerDirection(isIngress bool, capturedNode Nod allLayersRes[peerNode] = NoConns() continue } - // ibm-config: appliedFilters are either both nacl and sg (for pgw) or only sg (for fip) + // ibm-config1: appliedFilters are either both nacl and sg (for pgw) or only sg (for fip) // TODO: consider moving to pkg ibm-vpc appliedFilters := appliedRouter.AppliedFiltersKinds() for layer := range appliedFilters { diff --git a/pkg/vpcmodel/output.go b/pkg/vpcmodel/output.go index b3161604e..12e5ca41f 100644 --- a/pkg/vpcmodel/output.go +++ b/pkg/vpcmodel/output.go @@ -24,46 +24,58 @@ const ( type OutputUseCase int +// ToDo SM: subnets connectivity "only nacl" relevant to diff? const ( AllEndpoints OutputUseCase = iota // connectivity between network interfaces and external ip-blocks SingleSubnet // connectivity per single subnet with nacl AllSubnets // connectivity between subnets (consider nacl + pgw) + AllSubnetsDiff // diff between two subnets connectivity (consider nacl + pgw) AllSubnetsNoPGW // connectivity between subnets (consider nacl only) ) -// OutputGenerator captures one vpc config with its connectivity analysis results, and implements +// OutputGenerator captures one vpc config1 with its connectivity analysis results, and implements // the functionality to generate the analysis output in various formats, for that vpc type OutputGenerator struct { - config *VPCConfig + config1 *VPCConfig + config2 *VPCConfig // specified only when analysis is diff outputGrouping bool useCase OutputUseCase nodesConn *VPCConnectivity subnetsConn *VPCsubnetConnectivity + subnetsDiff *DiffBetweenSubnets } -func NewOutputGenerator(c *VPCConfig, grouping bool, uc OutputUseCase, archOnly bool) (*OutputGenerator, error) { +func NewOutputGenerator(c1, c2 *VPCConfig, grouping bool, uc OutputUseCase, archOnly bool) (*OutputGenerator, error) { res := &OutputGenerator{ - config: c, + config1: c1, + config2: c2, outputGrouping: grouping, useCase: uc, } if !archOnly { if uc == AllEndpoints { - nodesConn, err := c.GetVPCNetworkConnectivity(grouping) + nodesConn, err := c1.GetVPCNetworkConnectivity(grouping) if err != nil { return nil, err } res.nodesConn = nodesConn } if uc == AllSubnets { - subnetsConn, err := c.GetSubnetsConnectivity(true, grouping) + subnetsConn, err := c1.GetSubnetsConnectivity(true, grouping) if err != nil { return nil, err } res.subnetsConn = subnetsConn } + if uc == AllSubnetsDiff { + configsForDiff := &ConfigsForDiff{c1, c2} + subnetsDiff, err := configsForDiff.GetSubnetsDiff(grouping) + if err != nil { + return nil, err + } + res.subnetsDiff = subnetsDiff + } } - // todo: add for diff return res, nil } @@ -95,12 +107,12 @@ func (o *OutputGenerator) Generate(f OutFormat, outFile string) (*VPCAnalysisOut return nil, errors.New("unsupported output format") } - return formatter.WriteOutput(o.config, o.nodesConn, o.subnetsConn, outFile, o.outputGrouping, o.useCase) + return formatter.WriteOutput(o.config1, o.config2, o.nodesConn, o.subnetsConn, o.subnetsDiff, outFile, o.outputGrouping, o.useCase) } type OutputFormatter interface { - WriteOutput(c *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, outFile string, - grouping bool, uc OutputUseCase) (*VPCAnalysisOutput, error) + WriteOutput(c1, c2 *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, subnetsDiff *DiffBetweenSubnets, + outFile string, grouping bool, uc OutputUseCase) (*VPCAnalysisOutput, error) } func WriteToFile(content, fileName string) (string, error) { diff --git a/pkg/vpcmodel/semanticDiffSubnets.go b/pkg/vpcmodel/semanticDiffSubnets.go index 0ec6b77a7..3adf63c42 100644 --- a/pkg/vpcmodel/semanticDiffSubnets.go +++ b/pkg/vpcmodel/semanticDiffSubnets.go @@ -2,6 +2,8 @@ package vpcmodel import ( "fmt" + "sort" + "strings" "github.com/np-guard/vpc-network-config-analyzer/pkg/common" ) @@ -38,18 +40,18 @@ type SubnetConfigConnectivity struct { subnetConnectivity SubnetConnectivityMap } -type diffBetweenSubnets struct { +type DiffBetweenSubnets struct { subnet1Subtract2 SubnetsDiff subnet2Subtract1 SubnetsDiff } -func (configs ConfigsForDiff) GetSubnetsDiff(grouping bool) (*diffBetweenSubnets, error) { +func (configs ConfigsForDiff) GetSubnetsDiff(grouping bool) (*DiffBetweenSubnets, error) { // 1. compute connectivity for each of the subnets - subnetsConn1, err := configs.config1.GetSubnetsConnectivity(true, false) + subnetsConn1, err := configs.config1.GetSubnetsConnectivity(true, grouping) if err != nil { return nil, err } - subnetsConn2, err := configs.config2.GetSubnetsConnectivity(true, false) + subnetsConn2, err := configs.config2.GetSubnetsConnectivity(true, grouping) if err != nil { return nil, err } @@ -75,7 +77,7 @@ func (configs ConfigsForDiff) GetSubnetsDiff(grouping bool) (*diffBetweenSubnets // 3. ToDo: grouping, see comment at the end of this file - res := &diffBetweenSubnets{ + res := &DiffBetweenSubnets{ subnet1Subtract2: subnet1Subtract2, subnet2Subtract1: subnet2Subtract1} return res, nil @@ -171,8 +173,15 @@ func getDiffType(src, srcInOther, dst, dstInOther EndpointElem) DiffType { // EnhancedString ToDo: likely the current printing functionality will no longer be needed once the grouping is added // anyways the diff print will be worked on before the final merge + +func (diff *DiffBetweenSubnets) String() string { + return diff.subnet1Subtract2.EnhancedString(true) + "\n" + + diff.subnet2Subtract1.EnhancedString(false) +} + func (subnetDiff *SubnetsDiff) EnhancedString(thisMinusOther bool) string { - var diffDirection, printDiff string + var diffDirection string + strList := []string{} if thisMinusOther { diffDirection = "--" } else { @@ -184,11 +193,14 @@ func (subnetDiff *SubnetsDiff) EnhancedString(thisMinusOther bool) string { if connDiff.ConnectionSet != nil { connectionSetDiff = connDiff.ConnectionSet.EnhancedString() } - printDiff += fmt.Sprintf("%s %s => %s : %s %s\n", diffDirection, src.Name(), dst.Name(), + printDiff := fmt.Sprintf("%s %s => %s : %s %s\n", diffDirection, src.Name(), dst.Name(), diffDescription(connDiff.diff), connectionSetDiff) + strList = append(strList, printDiff) } } - return printDiff + sort.Strings(strList) + res := strings.Join(strList, "") + return res } func diffDescription(diff DiffType) string { @@ -531,7 +543,7 @@ func externalNodeToIPBlock(external Node) (ipBlock *common.IPBlock, err error) { //} // //// todo: instead of adding functionality to grouping, I plan to have more generic connectivity items that will be grouped -//// encode the SubnetsDiff into this generic item as well as the other entities we are grouping +//// encode the subnetsDiff into this generic item as well as the other entities we are grouping //// and then decode in the printing //// the idea is to use instead of *common.ConnectionSet in the grouped entity a string which will encode the connection //// and also the diff where relevant diff --git a/pkg/vpcmodel/textOutput.go b/pkg/vpcmodel/textOutput.go index 520d26192..75527e083 100644 --- a/pkg/vpcmodel/textOutput.go +++ b/pkg/vpcmodel/textOutput.go @@ -7,18 +7,27 @@ type TextOutputFormatter struct { const asteriskDetails = "\n\nconnections are stateful unless marked with *\n" -func headerOfAnalyzedVPC(vpcName string) string { - return fmt.Sprintf("Analysis for VPC %s\n", vpcName) +func headerOfAnalyzedVPC(vpcName, vpc2Name string) string { + if vpc2Name == "" { + return fmt.Sprintf("Analysis for VPC %s\n", vpcName) + } + // 2nd cfg given - the analysis is a semantic diff and concerns a single cfg + return fmt.Sprintf("Analysis for diff between VPC %s and VPC %s\n", vpcName, vpc2Name) } -func (t *TextOutputFormatter) WriteOutput(c *VPCConfig, +func (t *TextOutputFormatter) WriteOutput(c1, c2 *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, + subnetsDiff *DiffBetweenSubnets, outFile string, grouping bool, uc OutputUseCase) (*VPCAnalysisOutput, error) { + vpc2Name := "" + if c2 != nil { + vpc2Name = c2.VPC.Name() + } // header line - specify the VPC analyzed - out := headerOfAnalyzedVPC(c.VPC.Name()) + out := headerOfAnalyzedVPC(c1.VPC.Name(), vpc2Name) // get output by analysis type switch uc { case AllEndpoints: @@ -26,9 +35,11 @@ func (t *TextOutputFormatter) WriteOutput(c *VPCConfig, case AllSubnets: out += subnetsConn.String() case SingleSubnet: - out += c.GetConnectivityOutputPerEachSubnetSeparately() + out += c1.GetConnectivityOutputPerEachSubnetSeparately() + case AllSubnetsDiff: + out += subnetsDiff.String() } // write output to file and return the output string _, err := WriteToFile(out, outFile) - return &VPCAnalysisOutput{Output: out, VPCName: c.VPC.Name(), format: Text}, err + return &VPCAnalysisOutput{Output: out, VPCName: c1.VPC.Name(), format: Text}, err } diff --git a/pkg/vpcmodel/vpcConnectivity.go b/pkg/vpcmodel/vpcConnectivity.go index f585b84f3..4e32a7deb 100644 --- a/pkg/vpcmodel/vpcConnectivity.go +++ b/pkg/vpcmodel/vpcConnectivity.go @@ -4,7 +4,7 @@ import ( "github.com/np-guard/vpc-network-config-analyzer/pkg/common" ) -// VPCConnectivity holds detailed representation of allowed connectivity considering all resources in a vpc config instance +// VPCConnectivity holds detailed representation of allowed connectivity considering all resources in a vpc config1 instance type VPCConnectivity struct { // computed for each layer separately its allowed connections (ingress and egress separately) AllowedConnsPerLayer map[Node]map[string]*ConnectivityResult @@ -66,7 +66,7 @@ func NewIPbasedConnectivityResult() *IPbasedConnectivityResult { } } -// ConfigBasedConnectivityResults is used to capture allowed connectivity to/from elements in the vpc config (subnets / external ip-blocks) +// ConfigBasedConnectivityResults is used to capture allowed connectivity to/from elements in the vpc config1 (subnets / external ip-blocks) // It is associated with a subnet when analyzing connectivity of subnets based on NACL resources type ConfigBasedConnectivityResults struct { IngressAllowedConns map[EndpointElem]*common.ConnectionSet