diff --git a/cmd/analyzer/main.go b/cmd/analyzer/main.go index 3811cba96..30f0bd4df 100644 --- a/cmd/analyzer/main.go +++ b/cmd/analyzer/main.go @@ -44,7 +44,9 @@ func analysisTypeToUseCase(inArgs *InArgs) vpcmodel.OutputUseCase { case allSubnets: return vpcmodel.AllSubnets case allSubnetsDiff: - return vpcmodel.AllSubnetsDiff + return vpcmodel.SubnetsDiff + case allEndpointsDiff: + return vpcmodel.EndpointsDiff } return vpcmodel.AllEndpoints } diff --git a/cmd/analyzer/parse_args.go b/cmd/analyzer/parse_args.go index e2ce0dad4..c5fbb2494 100644 --- a/cmd/analyzer/parse_args.go +++ b/cmd/analyzer/parse_args.go @@ -48,7 +48,7 @@ var supportedAnalysisTypes = map[string]bool{ allSubnets: true, singleSubnet: true, allSubnetsDiff: true, - allEndpointsDiff: false, + allEndpointsDiff: true, } func getSupportedValuesString(supportedValues map[string]bool) string { diff --git a/pkg/ibmvpc/analysis_output_test.go b/pkg/ibmvpc/analysis_output_test.go index 772b80f93..d7beef312 100644 --- a/pkg/ibmvpc/analysis_output_test.go +++ b/pkg/ibmvpc/analysis_output_test.go @@ -52,6 +52,7 @@ const ( suffixOutFileSubnetsLevel = "subnetsBased_withPGW" suffixOutFileSubnetsLevelNoPGW = "subnetsBased_withoutPGW" suffixOutFileDiffSubnets = "subnetsDiff" + suffixOutFileDiffEndpoints = "endpointsDiff" txtOutSuffix = ".txt" debugOutSuffix = "_debug.txt" mdOutSuffix = ".md" @@ -95,29 +96,38 @@ func getTestFileName(testName string, res = baseName + suffixOutFileSubnetsLevel case vpcmodel.AllSubnetsNoPGW: res = baseName + suffixOutFileSubnetsLevelNoPGW - case vpcmodel.AllSubnetsDiff: + case vpcmodel.SubnetsDiff: res = baseName + suffixOutFileDiffSubnets + case vpcmodel.EndpointsDiff: + res = baseName + suffixOutFileDiffEndpoints } + suffix, suffixErr := getTestFileSuffix(format) + if suffixErr != nil { + return "", "", suffixErr + } + res += suffix + expectedFileName = res + actualFileName = actualOutFilePrefix + res + return expectedFileName, actualFileName, nil +} + +func getTestFileSuffix(format vpcmodel.OutFormat) (suffix string, err error) { switch format { case vpcmodel.Text: - res += txtOutSuffix + return txtOutSuffix, nil case vpcmodel.Debug: - res += debugOutSuffix + return debugOutSuffix, nil case vpcmodel.MD: - res += mdOutSuffix + return mdOutSuffix, nil case vpcmodel.JSON: - res += jsonOutSuffix + return jsonOutSuffix, nil case vpcmodel.DRAWIO: - res += drawioOutSuffix + return drawioOutSuffix, nil case vpcmodel.ARCHDRAWIO: - res += archDrawioOutSuffix + return archDrawioOutSuffix, nil default: - return "", "", errors.New("unexpected out format") + return "", errors.New("unexpected out format") } - - expectedFileName = res - actualFileName = actualOutFilePrefix + res - return expectedFileName, actualFileName, nil } // initTest: based on the test name, set the input config file name, and the output @@ -336,10 +346,13 @@ var tests = []*vpcGeneralTest{ 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}, + name: "acl_testing5", + useCases: []vpcmodel.OutputUseCase{vpcmodel.SubnetsDiff}, + format: vpcmodel.Text, + }, + { + name: "acl_testing3", + useCases: []vpcmodel.OutputUseCase{vpcmodel.EndpointsDiff}, format: vpcmodel.Text, }, } @@ -347,24 +360,25 @@ var tests = []*vpcGeneralTest{ var formatsAvoidComparison = map[vpcmodel.OutFormat]bool{vpcmodel.ARCHDRAWIO: true, vpcmodel.DRAWIO: true} // uncomment the function below to run for updating the expected output -/*var formatsAvoidOutputGeneration = map[vpcmodel.OutFormat]bool{vpcmodel.ARCHDRAWIO: true, vpcmodel.DRAWIO: true} -func TestAllWithGeneration(t *testing.T) { - // tests is the list of tests to run - for testIdx := range tests { - tt := tests[testIdx] - // todo - remove the following if when drawio is stable - if formatsAvoidOutputGeneration[tt.format] { - tt.mode = outputIgnore - } else { - tt.mode = outputGeneration - } - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - tt.runTest(t) - }) - } - fmt.Println("done") -}*/ +// var formatsAvoidOutputGeneration = map[vpcmodel.OutFormat]bool{vpcmodel.ARCHDRAWIO: true, vpcmodel.DRAWIO: true} +// +// func TestAllWithGeneration(t *testing.T) { +// // tests is the list of tests to run +// for testIdx := range tests { +// tt := tests[testIdx] +// // todo - remove the following if when drawio is stable +// if formatsAvoidOutputGeneration[tt.format] { +// tt.mode = outputIgnore +// } else { +// tt.mode = outputGeneration +// } +// t.Run(tt.name, func(t *testing.T) { +// t.Parallel() +// tt.runTest(t) +// }) +// } +// fmt.Println("done") +//} func TestAllWithComparison(t *testing.T) { // tests is the list of tests to run @@ -407,7 +421,7 @@ func (tt *vpcGeneralTest) runTest(t *testing.T) { var vpcConfigs2nd map[string]*vpcmodel.VPCConfig diffUseCase := false for _, useCase := range tt.useCases { - if useCase == vpcmodel.AllSubnetsDiff { + if useCase == vpcmodel.SubnetsDiff || useCase == vpcmodel.EndpointsDiff { diffUseCase = true } } diff --git a/pkg/ibmvpc/examples/acl_testing3endpointsDiff.txt b/pkg/ibmvpc/examples/acl_testing3endpointsDiff.txt new file mode 100644 index 000000000..643b336ab --- /dev/null +++ b/pkg/ibmvpc/examples/acl_testing3endpointsDiff.txt @@ -0,0 +1,3 @@ +Analysis for diff between VPC test-vpc1-ky and VPC test-vpc2-ky +diff-type: removed, source: vsi1-ky[10.240.10.4], destination: Public Internet [161.26.0.0/16], config1: protocol: UDP *, config2: No connection, vsis-diff-info: +diff-type: removed, source: vsi1-ky[10.240.10.4], destination: vsi2-ky[10.240.20.4], config1: protocol: TCP,UDP, config2: No connection, vsis-diff-info: diff --git a/pkg/ibmvpc/examples/input_acl_testing3_2nd.json b/pkg/ibmvpc/examples/input_acl_testing3_2nd.json new file mode 100644 index 000000000..9d1458311 --- /dev/null +++ b/pkg/ibmvpc/examples/input_acl_testing3_2nd.json @@ -0,0 +1,1910 @@ +{ + "endpoint_gateways": [ + { + "created_at": "2023-03-13T12:08:03.000Z", + "crn": "crn:1", + "health_state": "ok", + "href": "href:2", + "id": "id:3", + "ips": [ + { + "address": "10.240.30.7", + "href": "href:4", + "id": "id:5", + "name": "vpe-for-etcd-db-ky", + "resource_type": "subnet_reserved_ip" + } + ], + "lifecycle_state": "stable", + "name": "db-endpoint-gateway-ky", + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "resource_type": "endpoint_gateway", + "security_groups": [ + { + "crn": "crn:8", + "href": "href:9", + "id": "id:10", + "name": "sg1-ky" + } + ], + "service_endpoint": "ttt", + "service_endpoints": [ + "ttt" + ], + "tags": [], + "target": { + "crn": "crn:11", + "resource_type": "provider_cloud_service" + }, + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky" + } + } + ], + "floating_ips": [ + { + "address": "52.118.145.114", + "created_at": "2023-03-13T11:51:29Z", + "crn": "crn:15", + "href": "href:16", + "id": "id:17", + "name": "floating-ip-ky", + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "status": "available", + "tags": [], + "target": { + "href": "href:18", + "id": "id:19", + "name": "yarn-canary-guileless-deftly", + "primary_ip": { + "address": "10.240.20.4", + "href": "href:20", + "id": "id:21", + "name": "precision-grudge-daylight-married", + "resource_type": "subnet_reserved_ip" + }, + "resource_type": "network_interface" + }, + "zone": { + "href": "href:22", + "name": "us-south-1" + } + }, + { + "address": "52.116.129.150", + "created_at": "2023-03-13T11:50:34Z", + "crn": "crn:23", + "href": "href:24", + "id": "id:25", + "name": "public-gw-ky", + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "status": "available", + "tags": [], + "target": { + "crn": "crn:26", + "href": "href:27", + "id": "id:28", + "name": "public-gw-ky", + "resource_type": "public_gateway" + }, + "zone": { + "href": "href:22", + "name": "us-south-1" + } + } + ], + "instances": [ + { + "availability_policy": { + "host_failure": "restart" + }, + "bandwidth": 4000, + "boot_volume_attachment": { + "device": { + "id": "id:34" + }, + "href": "href:32", + "id": "id:33", + "name": "craziness-boa-ionic-zestfully", + "volume": { + "crn": "crn:35", + "href": "href:36", + "id": "id:37", + "name": "recharger-refinery-trace-hatchery" + } + }, + "created_at": "2023-03-13T11:51:16Z", + "crn": "crn:29", + "disks": [], + "href": "href:30", + "id": "id:31", + "image": { + "crn": "crn:38", + "href": "href:39", + "id": "id:40", + "name": "ibm-centos-7-9-minimal-amd64-8" + }, + "lifecycle_reasons": [], + "lifecycle_state": "stable", + "memory": 4, + "metadata_service": { + "enabled": false, + "protocol": "http", + "response_hop_limit": 1 + }, + "name": "vsi1-ky", + "network_interfaces": [ + { + "allow_ip_spoofing": false, + "created_at": "2023-03-13T11:51:16Z", + "floating_ips": [], + "href": "href:41", + "id": "id:42", + "name": "cycling-juvenile-traipse-paramount", + "port_speed": 3000, + "primary_ip": { + "address": "10.240.10.4", + "href": "href:43", + "id": "id:44", + "name": "swiftly-running-ounce-chrome", + "resource_type": "subnet_reserved_ip" + }, + "resource_type": "network_interface", + "security_groups": [ + { + "crn": "crn:8", + "href": "href:9", + "id": "id:10", + "name": "sg1-ky" + } + ], + "status": "available", + "subnet": { + "crn": "crn:45", + "href": "href:46", + "id": "id:47", + "name": "subnet1-ky", + "resource_type": "subnet" + }, + "type": "primary" + } + ], + "numa_count": 1, + "primary_network_interface": { + "href": "href:41", + "id": "id:42", + "name": "cycling-juvenile-traipse-paramount", + "primary_ip": { + "address": "10.240.10.4", + "href": "href:43", + "id": "id:44", + "name": "swiftly-running-ounce-chrome", + "resource_type": "subnet_reserved_ip" + }, + "resource_type": "network_interface", + "subnet": { + "crn": "crn:45", + "href": "href:46", + "id": "id:47", + "name": "subnet1-ky", + "resource_type": "subnet" + } + }, + "profile": { + "href": "href:48", + "name": "cx2-2x4" + }, + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "resource_type": "instance", + "startable": true, + "status": "running", + "status_reasons": [], + "tags": [], + "total_network_bandwidth": 3000, + "total_volume_bandwidth": 1000, + "vcpu": { + "architecture": "amd64", + "count": 2, + "manufacturer": "intel" + }, + "volume_attachments": [ + { + "device": { + "id": "id:34" + }, + "href": "href:32", + "id": "id:33", + "name": "craziness-boa-ionic-zestfully", + "volume": { + "crn": "crn:35", + "href": "href:36", + "id": "id:37", + "name": "recharger-refinery-trace-hatchery" + } + } + ], + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:22", + "name": "us-south-1" + } + }, + { + "availability_policy": { + "host_failure": "restart" + }, + "bandwidth": 4000, + "boot_volume_attachment": { + "device": { + "id": "id:54" + }, + "href": "href:52", + "id": "id:53", + "name": "isolating-detector-sycamore-subarctic", + "volume": { + "crn": "crn:55", + "href": "href:56", + "id": "id:57", + "name": "heave-dreary-secluded-delicacy" + } + }, + "created_at": "2023-03-13T11:51:04Z", + "crn": "crn:49", + "disks": [], + "href": "href:50", + "id": "id:51", + "image": { + "crn": "crn:38", + "href": "href:39", + "id": "id:40", + "name": "ibm-centos-7-9-minimal-amd64-8" + }, + "lifecycle_reasons": [], + "lifecycle_state": "stable", + "memory": 4, + "metadata_service": { + "enabled": false, + "protocol": "http", + "response_hop_limit": 1 + }, + "name": "vsi2-ky", + "network_interfaces": [ + { + "allow_ip_spoofing": false, + "created_at": "2023-03-13T11:51:04Z", + "floating_ips": [ + { + "address": "52.118.145.114", + "crn": "crn:15", + "href": "href:16", + "id": "id:17", + "name": "floating-ip-ky" + } + ], + "href": "href:18", + "id": "id:19", + "name": "yarn-canary-guileless-deftly", + "port_speed": 3000, + "primary_ip": { + "address": "10.240.20.4", + "href": "href:20", + "id": "id:21", + "name": "precision-grudge-daylight-married", + "resource_type": "subnet_reserved_ip" + }, + "resource_type": "network_interface", + "security_groups": [ + { + "crn": "crn:8", + "href": "href:9", + "id": "id:10", + "name": "sg1-ky" + } + ], + "status": "available", + "subnet": { + "crn": "crn:58", + "href": "href:59", + "id": "id:60", + "name": "subnet2-ky", + "resource_type": "subnet" + }, + "type": "primary" + } + ], + "numa_count": 1, + "primary_network_interface": { + "href": "href:18", + "id": "id:19", + "name": "yarn-canary-guileless-deftly", + "primary_ip": { + "address": "10.240.20.4", + "href": "href:20", + "id": "id:21", + "name": "precision-grudge-daylight-married", + "resource_type": "subnet_reserved_ip" + }, + "resource_type": "network_interface", + "subnet": { + "crn": "crn:58", + "href": "href:59", + "id": "id:60", + "name": "subnet2-ky", + "resource_type": "subnet" + } + }, + "profile": { + "href": "href:48", + "name": "cx2-2x4" + }, + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "resource_type": "instance", + "startable": true, + "status": "running", + "status_reasons": [], + "tags": [], + "total_network_bandwidth": 3000, + "total_volume_bandwidth": 1000, + "vcpu": { + "architecture": "amd64", + "count": 2, + "manufacturer": "intel" + }, + "volume_attachments": [ + { + "device": { + "id": "id:54" + }, + "href": "href:52", + "id": "id:53", + "name": "isolating-detector-sycamore-subarctic", + "volume": { + "crn": "crn:55", + "href": "href:56", + "id": "id:57", + "name": "heave-dreary-secluded-delicacy" + } + } + ], + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:22", + "name": "us-south-1" + } + }, + { + "availability_policy": { + "host_failure": "restart" + }, + "bandwidth": 4000, + "boot_volume_attachment": { + "device": { + "id": "id:66" + }, + "href": "href:64", + "id": "id:65", + "name": "dispersal-sister-antacid-icon", + "volume": { + "crn": "crn:67", + "href": "href:68", + "id": "id:69", + "name": "moonlight-pawing-video-shed" + } + }, + "created_at": "2023-03-13T11:50:50Z", + "crn": "crn:61", + "disks": [], + "href": "href:62", + "id": "id:63", + "image": { + "crn": "crn:38", + "href": "href:39", + "id": "id:40", + "name": "ibm-centos-7-9-minimal-amd64-8" + }, + "lifecycle_reasons": [], + "lifecycle_state": "stable", + "memory": 4, + "metadata_service": { + "enabled": false, + "protocol": "http", + "response_hop_limit": 1 + }, + "name": "vsi3a-ky", + "network_interfaces": [ + { + "allow_ip_spoofing": false, + "created_at": "2023-03-13T11:50:50Z", + "floating_ips": [], + "href": "href:70", + "id": "id:71", + "name": "data-washstand-blot-scrambler", + "port_speed": 3000, + "primary_ip": { + "address": "10.240.30.5", + "href": "href:72", + "id": "id:73", + "name": "ointment-fading-shabby-sectional", + "resource_type": "subnet_reserved_ip" + }, + "resource_type": "network_interface", + "security_groups": [ + { + "crn": "crn:8", + "href": "href:9", + "id": "id:10", + "name": "sg1-ky" + } + ], + "status": "available", + "subnet": { + "crn": "crn:74", + "href": "href:75", + "id": "id:76", + "name": "subnet3-ky", + "resource_type": "subnet" + }, + "type": "primary" + } + ], + "numa_count": 1, + "primary_network_interface": { + "href": "href:70", + "id": "id:71", + "name": "data-washstand-blot-scrambler", + "primary_ip": { + "address": "10.240.30.5", + "href": "href:72", + "id": "id:73", + "name": "ointment-fading-shabby-sectional", + "resource_type": "subnet_reserved_ip" + }, + "resource_type": "network_interface", + "subnet": { + "crn": "crn:74", + "href": "href:75", + "id": "id:76", + "name": "subnet3-ky", + "resource_type": "subnet" + } + }, + "profile": { + "href": "href:48", + "name": "cx2-2x4" + }, + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "resource_type": "instance", + "startable": true, + "status": "running", + "status_reasons": [], + "tags": [], + "total_network_bandwidth": 3000, + "total_volume_bandwidth": 1000, + "vcpu": { + "architecture": "amd64", + "count": 2, + "manufacturer": "intel" + }, + "volume_attachments": [ + { + "device": { + "id": "id:66" + }, + "href": "href:64", + "id": "id:65", + "name": "dispersal-sister-antacid-icon", + "volume": { + "crn": "crn:67", + "href": "href:68", + "id": "id:69", + "name": "moonlight-pawing-video-shed" + } + } + ], + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:22", + "name": "us-south-1" + } + }, + { + "availability_policy": { + "host_failure": "restart" + }, + "bandwidth": 4000, + "boot_volume_attachment": { + "device": { + "id": "id:82" + }, + "href": "href:80", + "id": "id:81", + "name": "fanning-conceded-reapprove-finishing", + "volume": { + "crn": "crn:83", + "href": "href:84", + "id": "id:85", + "name": "oblong-federal-reason-aide" + } + }, + "created_at": "2023-03-13T11:50:50Z", + "crn": "crn:77", + "disks": [], + "href": "href:78", + "id": "id:79", + "image": { + "crn": "crn:38", + "href": "href:39", + "id": "id:40", + "name": "ibm-centos-7-9-minimal-amd64-8" + }, + "lifecycle_reasons": [], + "lifecycle_state": "stable", + "memory": 4, + "metadata_service": { + "enabled": false, + "protocol": "http", + "response_hop_limit": 1 + }, + "name": "vsi3c-ky", + "network_interfaces": [ + { + "allow_ip_spoofing": false, + "created_at": "2023-03-13T11:50:50Z", + "floating_ips": [], + "href": "href:86", + "id": "id:87", + "name": "contest-dance-divided-brilliant", + "port_speed": 3000, + "primary_ip": { + "address": "10.240.30.4", + "href": "href:88", + "id": "id:89", + "name": "wobbling-pueblo-bulldozer-spring", + "resource_type": "subnet_reserved_ip" + }, + "resource_type": "network_interface", + "security_groups": [ + { + "crn": "crn:8", + "href": "href:9", + "id": "id:10", + "name": "sg1-ky" + } + ], + "status": "available", + "subnet": { + "crn": "crn:74", + "href": "href:75", + "id": "id:76", + "name": "subnet3-ky", + "resource_type": "subnet" + }, + "type": "primary" + } + ], + "numa_count": 1, + "primary_network_interface": { + "href": "href:86", + "id": "id:87", + "name": "contest-dance-divided-brilliant", + "primary_ip": { + "address": "10.240.30.4", + "href": "href:88", + "id": "id:89", + "name": "wobbling-pueblo-bulldozer-spring", + "resource_type": "subnet_reserved_ip" + }, + "resource_type": "network_interface", + "subnet": { + "crn": "crn:74", + "href": "href:75", + "id": "id:76", + "name": "subnet3-ky", + "resource_type": "subnet" + } + }, + "profile": { + "href": "href:48", + "name": "cx2-2x4" + }, + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "resource_type": "instance", + "startable": true, + "status": "running", + "status_reasons": [], + "tags": [], + "total_network_bandwidth": 3000, + "total_volume_bandwidth": 1000, + "vcpu": { + "architecture": "amd64", + "count": 2, + "manufacturer": "intel" + }, + "volume_attachments": [ + { + "device": { + "id": "id:82" + }, + "href": "href:80", + "id": "id:81", + "name": "fanning-conceded-reapprove-finishing", + "volume": { + "crn": "crn:83", + "href": "href:84", + "id": "id:85", + "name": "oblong-federal-reason-aide" + } + } + ], + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:22", + "name": "us-south-1" + } + }, + { + "availability_policy": { + "host_failure": "restart" + }, + "bandwidth": 4000, + "boot_volume_attachment": { + "device": { + "id": "id:95" + }, + "href": "href:93", + "id": "id:94", + "name": "people-emphasize-tracing-majorette", + "volume": { + "crn": "crn:96", + "href": "href:97", + "id": "id:98", + "name": "pogo-unripe-snowdrift-untwist" + } + }, + "created_at": "2023-03-13T11:50:50Z", + "crn": "crn:90", + "disks": [], + "href": "href:91", + "id": "id:92", + "image": { + "crn": "crn:38", + "href": "href:39", + "id": "id:40", + "name": "ibm-centos-7-9-minimal-amd64-8" + }, + "lifecycle_reasons": [], + "lifecycle_state": "stable", + "memory": 4, + "metadata_service": { + "enabled": false, + "protocol": "http", + "response_hop_limit": 1 + }, + "name": "vsi3b-ky", + "network_interfaces": [ + { + "allow_ip_spoofing": false, + "created_at": "2023-03-13T11:50:50Z", + "floating_ips": [], + "href": "href:99", + "id": "id:100", + "name": "filterable-steersman-collar-whoops", + "port_speed": 3000, + "primary_ip": { + "address": "10.240.30.6", + "href": "href:101", + "id": "id:102", + "name": "attach-portfolio-natural-lisp", + "resource_type": "subnet_reserved_ip" + }, + "resource_type": "network_interface", + "security_groups": [ + { + "crn": "crn:8", + "href": "href:9", + "id": "id:10", + "name": "sg1-ky" + } + ], + "status": "available", + "subnet": { + "crn": "crn:74", + "href": "href:75", + "id": "id:76", + "name": "subnet3-ky", + "resource_type": "subnet" + }, + "type": "primary" + } + ], + "numa_count": 1, + "primary_network_interface": { + "href": "href:99", + "id": "id:100", + "name": "filterable-steersman-collar-whoops", + "primary_ip": { + "address": "10.240.30.6", + "href": "href:101", + "id": "id:102", + "name": "attach-portfolio-natural-lisp", + "resource_type": "subnet_reserved_ip" + }, + "resource_type": "network_interface", + "subnet": { + "crn": "crn:74", + "href": "href:75", + "id": "id:76", + "name": "subnet3-ky", + "resource_type": "subnet" + } + }, + "profile": { + "href": "href:48", + "name": "cx2-2x4" + }, + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "resource_type": "instance", + "startable": true, + "status": "running", + "status_reasons": [], + "tags": [], + "total_network_bandwidth": 3000, + "total_volume_bandwidth": 1000, + "vcpu": { + "architecture": "amd64", + "count": 2, + "manufacturer": "intel" + }, + "volume_attachments": [ + { + "device": { + "id": "id:95" + }, + "href": "href:93", + "id": "id:94", + "name": "people-emphasize-tracing-majorette", + "volume": { + "crn": "crn:96", + "href": "href:97", + "id": "id:98", + "name": "pogo-unripe-snowdrift-untwist" + } + } + ], + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:22", + "name": "us-south-1" + } + } + ], + "network_acls": [ + { + "created_at": "2023-03-13T11:50:34Z", + "crn": "crn:103", + "href": "href:104", + "id": "id:105", + "name": "acl2-ky", + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "rules": [ + { + "action": "allow", + "before": { + "href": "href:108", + "id": "id:109", + "name": "acl2-out-2" + }, + "created_at": "2023-03-13T12:21:36Z", + "destination": "142.0.0.0/8", + "direction": "outbound", + "href": "href:106", + "id": "id:107", + "ip_version": "ipv4", + "name": "acl2-out-1", + "protocol": "icmp", + "source": "10.240.20.0/24" + }, + { + "action": "allow", + "before": { + "href": "href:110", + "id": "id:111", + "name": "acl2-out-3" + }, + "created_at": "2023-03-13T12:21:37Z", + "destination": "10.240.30.0/24", + "direction": "outbound", + "href": "href:108", + "id": "id:109", + "ip_version": "ipv4", + "name": "acl2-out-2", + "protocol": "icmp", + "source": "10.240.20.0/24" + }, + { + "action": "allow", + "before": { + "href": "href:112", + "id": "id:113", + "name": "acl2-in-1" + }, + "created_at": "2023-03-13T12:21:37Z", + "destination": "10.240.10.0/24", + "direction": "outbound", + "href": "href:110", + "id": "id:111", + "ip_version": "ipv4", + "name": "acl2-out-3", + "protocol": "all", + "source": "10.240.20.0/24" + }, + { + "action": "deny", + "before": { + "href": "href:114", + "id": "id:115", + "name": "acl2-in-2" + }, + "created_at": "2023-03-13T12:21:38Z", + "destination": "147.235.219.207/32", + "destination_port_max": 22, + "destination_port_min": 22, + "direction": "inbound", + "href": "href:112", + "id": "id:113", + "ip_version": "ipv4", + "name": "acl2-in-1", + "protocol": "tcp", + "source": "0.0.0.0/0", + "source_port_max": 65535, + "source_port_min": 1 + }, + { + "action": "allow", + "before": { + "href": "href:116", + "id": "id:117", + "name": "acl2-in-3" + }, + "created_at": "2023-03-13T12:21:38Z", + "destination": "147.235.219.206/31", + "destination_port_max": 22, + "destination_port_min": 22, + "direction": "inbound", + "href": "href:114", + "id": "id:115", + "ip_version": "ipv4", + "name": "acl2-in-2", + "protocol": "tcp", + "source": "0.0.0.0/0", + "source_port_max": 65535, + "source_port_min": 1 + }, + { + "action": "allow", + "before": { + "href": "href:118", + "id": "id:119", + "name": "acl2-in-4" + }, + "created_at": "2023-03-13T12:21:39Z", + "destination": "10.240.20.0/24", + "destination_port_max": 22, + "destination_port_min": 22, + "direction": "inbound", + "href": "href:116", + "id": "id:117", + "ip_version": "ipv4", + "name": "acl2-in-3", + "protocol": "tcp", + "source": "10.240.30.0/24", + "source_port_max": 65535, + "source_port_min": 1 + }, + { + "action": "allow", + "created_at": "2023-03-13T12:21:39Z", + "destination": "10.240.20.0/24", + "direction": "inbound", + "href": "href:118", + "id": "id:119", + "ip_version": "ipv4", + "name": "acl2-in-4", + "protocol": "all", + "source": "10.240.10.0/24" + } + ], + "subnets": [ + { + "crn": "crn:58", + "href": "href:59", + "id": "id:60", + "name": "subnet2-ky", + "resource_type": "subnet" + } + ], + "tags": [], + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_type": "vpc" + } + }, + { + "created_at": "2023-03-13T11:50:34Z", + "crn": "crn:120", + "href": "href:121", + "id": "id:122", + "name": "acl1-ky", + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "rules": [ + { + "action": "deny", + "before": { + "href": "href:125", + "id": "id:126", + "name": "acl1-out-2" + }, + "created_at": "2023-03-13T12:21:36Z", + "destination": "10.240.20.0/24", + "direction": "outbound", + "href": "href:123", + "id": "id:124", + "ip_version": "ipv4", + "name": "acl1-out-1", + "protocol": "icmp", + "source": "10.240.10.0/24" + }, + { + "action": "deny", + "before": { + "href": "href:127", + "id": "id:128", + "name": "acl1-out-3" + }, + "created_at": "2023-03-13T12:21:37Z", + "destination": "161.26.0.0/16", + "destination_port_max": 65535, + "destination_port_min": 1, + "direction": "outbound", + "href": "href:125", + "id": "id:126", + "ip_version": "ipv4", + "name": "acl1-out-2", + "protocol": "udp", + "source": "10.240.10.0/24", + "source_port_max": 65535, + "source_port_min": 1 + }, + { + "action": "deny", + "before": { + "href": "href:129", + "id": "id:130", + "name": "acl1-in-1" + }, + "created_at": "2023-03-13T12:21:37Z", + "destination": "10.240.20.0/24", + "direction": "outbound", + "href": "href:127", + "id": "id:128", + "ip_version": "ipv4", + "name": "acl1-out-3", + "protocol": "all", + "source": "10.240.10.0/24" + }, + { + "action": "allow", + "before": { + "href": "href:131", + "id": "id:132", + "name": "acl1-in-2" + }, + "created_at": "2023-03-13T12:21:38Z", + "destination": "0.0.0.0/0", + "direction": "inbound", + "href": "href:129", + "id": "id:130", + "ip_version": "ipv4", + "name": "acl1-in-1", + "protocol": "all", + "source": "10.240.30.0/24" + }, + { + "action": "allow", + "created_at": "2023-03-13T12:21:38Z", + "destination": "10.240.10.0/24", + "direction": "inbound", + "href": "href:131", + "id": "id:132", + "ip_version": "ipv4", + "name": "acl1-in-2", + "protocol": "all", + "source": "10.240.20.0/24" + } + ], + "subnets": [ + { + "crn": "crn:45", + "href": "href:46", + "id": "id:47", + "name": "subnet1-ky", + "resource_type": "subnet" + } + ], + "tags": [], + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_type": "vpc" + } + }, + { + "created_at": "2023-03-13T11:50:33Z", + "crn": "crn:133", + "href": "href:134", + "id": "id:135", + "name": "acl3-ky", + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "rules": [ + { + "action": "allow", + "before": { + "href": "href:138", + "id": "id:139", + "name": "acl3-out-2" + }, + "created_at": "2023-03-13T12:21:36Z", + "destination": "10.240.10.0/24", + "direction": "outbound", + "href": "href:136", + "id": "id:137", + "ip_version": "ipv4", + "name": "acl3-out-1", + "protocol": "all", + "source": "0.0.0.0/0" + }, + { + "action": "allow", + "before": { + "href": "href:140", + "id": "id:141", + "name": "acl3-in-1" + }, + "created_at": "2023-03-13T12:21:36Z", + "destination": "10.240.20.0/24", + "direction": "outbound", + "href": "href:138", + "id": "id:139", + "ip_version": "ipv4", + "name": "acl3-out-2", + "protocol": "all", + "source": "10.240.30.0/31" + }, + { + "action": "allow", + "before": { + "href": "href:142", + "id": "id:143", + "name": "acl3-in-2" + }, + "created_at": "2023-03-13T12:21:37Z", + "destination": "0.0.0.0/0", + "direction": "inbound", + "href": "href:140", + "id": "id:141", + "ip_version": "ipv4", + "name": "acl3-in-1", + "protocol": "all", + "source": "10.240.10.0/24" + }, + { + "action": "allow", + "created_at": "2023-03-13T12:21:37Z", + "destination": "10.240.30.0/31", + "direction": "inbound", + "href": "href:142", + "id": "id:143", + "ip_version": "ipv4", + "name": "acl3-in-2", + "protocol": "all", + "source": "10.240.20.0/24" + } + ], + "subnets": [ + { + "crn": "crn:74", + "href": "href:75", + "id": "id:76", + "name": "subnet3-ky", + "resource_type": "subnet" + } + ], + "tags": [], + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_type": "vpc" + } + }, + { + "created_at": "2023-03-13T11:50:18Z", + "crn": "crn:144", + "href": "href:145", + "id": "id:146", + "name": "demilune-humorless-captain-lurex", + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "rules": [ + { + "action": "allow", + "before": { + "href": "href:149", + "id": "id:150", + "name": "allow-outbound" + }, + "created_at": "2023-03-13T11:50:18Z", + "destination": "0.0.0.0/0", + "direction": "inbound", + "href": "href:147", + "id": "id:148", + "ip_version": "ipv4", + "name": "allow-inbound", + "protocol": "all", + "source": "0.0.0.0/0" + }, + { + "action": "allow", + "created_at": "2023-03-13T11:50:18Z", + "destination": "0.0.0.0/0", + "direction": "outbound", + "href": "href:149", + "id": "id:150", + "ip_version": "ipv4", + "name": "allow-outbound", + "protocol": "all", + "source": "0.0.0.0/0" + } + ], + "subnets": [], + "tags": [], + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_type": "vpc" + } + } + ], + "public_gateways": [ + { + "created_at": "2023-03-13T11:50:34Z", + "crn": "crn:26", + "floating_ip": { + "address": "52.116.129.150", + "crn": "crn:23", + "href": "href:24", + "id": "id:25", + "name": "public-gw-ky" + }, + "href": "href:27", + "id": "id:28", + "name": "public-gw-ky", + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "resource_type": "public_gateway", + "status": "available", + "tags": [], + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:22", + "name": "us-south-1" + } + } + ], + "security_groups": [ + { + "created_at": "2023-03-13T11:50:34Z", + "crn": "crn:8", + "href": "href:9", + "id": "id:10", + "name": "sg1-ky", + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "rules": [ + { + "direction": "outbound", + "href": "href:151", + "id": "id:152", + "ip_version": "ipv4", + "protocol": "all", + "remote": { + "cidr_block": "0.0.0.0/0" + } + }, + { + "direction": "inbound", + "href": "href:153", + "id": "id:154", + "ip_version": "ipv4", + "protocol": "all", + "remote": { + "cidr_block": "0.0.0.0/0" + } + } + ], + "tags": [], + "targets": [ + { + "href": "href:86", + "id": "id:87", + "name": "contest-dance-divided-brilliant", + "resource_type": "network_interface" + }, + { + "href": "href:70", + "id": "id:71", + "name": "data-washstand-blot-scrambler", + "resource_type": "network_interface" + }, + { + "href": "href:99", + "id": "id:100", + "name": "filterable-steersman-collar-whoops", + "resource_type": "network_interface" + }, + { + "href": "href:18", + "id": "id:19", + "name": "yarn-canary-guileless-deftly", + "resource_type": "network_interface" + }, + { + "href": "href:41", + "id": "id:42", + "name": "cycling-juvenile-traipse-paramount", + "resource_type": "network_interface" + }, + { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "db-endpoint-gateway-ky", + "resource_type": "endpoint_gateway" + } + ], + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_type": "vpc" + } + }, + { + "created_at": "2023-03-13T11:50:18Z", + "crn": "crn:155", + "href": "href:156", + "id": "id:157", + "name": "barbecue-frayed-varied-average", + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "rules": [ + { + "direction": "outbound", + "href": "href:158", + "id": "id:159", + "ip_version": "ipv4", + "protocol": "all", + "remote": { + "cidr_block": "0.0.0.0/0" + } + }, + { + "direction": "inbound", + "href": "href:160", + "id": "id:161", + "ip_version": "ipv4", + "protocol": "all", + "remote": { + "crn": "crn:155", + "href": "href:156", + "id": "id:157", + "name": "barbecue-frayed-varied-average" + } + } + ], + "tags": [], + "targets": [], + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_type": "vpc" + } + } + ], + "subnets": [ + { + "available_ipv4_address_count": 250, + "created_at": "2023-03-13T11:51:03Z", + "crn": "crn:45", + "href": "href:46", + "id": "id:47", + "ip_version": "ipv4", + "ipv4_cidr_block": "10.240.10.0/24", + "name": "subnet1-ky", + "network_acl": { + "crn": "crn:120", + "href": "href:121", + "id": "id:122", + "name": "acl1-ky" + }, + "public_gateway": { + "crn": "crn:26", + "href": "href:27", + "id": "id:28", + "name": "public-gw-ky", + "resource_type": "public_gateway" + }, + "reserved_ips": [ + { + "address": "10.240.10.0", + "auto_delete": false, + "created_at": "2023-03-13T11:51:03Z", + "href": "href:162", + "id": "id:163", + "lifecycle_state": "stable", + "name": "ibm-network-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.10.1", + "auto_delete": false, + "created_at": "2023-03-13T11:51:03Z", + "href": "href:164", + "id": "id:165", + "lifecycle_state": "stable", + "name": "ibm-default-gateway", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.10.2", + "auto_delete": false, + "created_at": "2023-03-13T11:51:03Z", + "href": "href:166", + "id": "id:167", + "lifecycle_state": "stable", + "name": "ibm-dns-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.10.3", + "auto_delete": false, + "created_at": "2023-03-13T11:51:03Z", + "href": "href:168", + "id": "id:169", + "lifecycle_state": "stable", + "name": "ibm-reserved-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.10.4", + "auto_delete": true, + "created_at": "2023-03-13T11:51:16Z", + "href": "href:43", + "id": "id:44", + "lifecycle_state": "stable", + "name": "swiftly-running-ounce-chrome", + "owner": "user", + "resource_type": "subnet_reserved_ip", + "target": { + "href": "href:41", + "id": "id:42", + "name": "cycling-juvenile-traipse-paramount", + "resource_type": "network_interface" + } + }, + { + "address": "10.240.10.255", + "auto_delete": false, + "created_at": "2023-03-13T11:51:03Z", + "href": "href:170", + "id": "id:171", + "lifecycle_state": "stable", + "name": "ibm-broadcast-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + } + ], + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "resource_type": "subnet", + "routing_table": { + "href": "href:172", + "id": "id:173", + "name": "catnap-music-yearbook-rotunda", + "resource_type": "routing_table" + }, + "status": "available", + "tags": [ + "public" + ], + "total_ipv4_address_count": 256, + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:22", + "name": "us-south-1" + } + }, + { + "available_ipv4_address_count": 250, + "created_at": "2023-03-13T11:50:50Z", + "crn": "crn:58", + "href": "href:59", + "id": "id:60", + "ip_version": "ipv4", + "ipv4_cidr_block": "10.240.20.0/24", + "name": "subnet2-ky", + "network_acl": { + "crn": "crn:103", + "href": "href:104", + "id": "id:105", + "name": "acl2-ky" + }, + "reserved_ips": [ + { + "address": "10.240.20.0", + "auto_delete": false, + "created_at": "2023-03-13T11:50:50Z", + "href": "href:174", + "id": "id:175", + "lifecycle_state": "stable", + "name": "ibm-network-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.20.1", + "auto_delete": false, + "created_at": "2023-03-13T11:50:50Z", + "href": "href:176", + "id": "id:177", + "lifecycle_state": "stable", + "name": "ibm-default-gateway", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.20.2", + "auto_delete": false, + "created_at": "2023-03-13T11:50:50Z", + "href": "href:178", + "id": "id:179", + "lifecycle_state": "stable", + "name": "ibm-dns-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.20.3", + "auto_delete": false, + "created_at": "2023-03-13T11:50:50Z", + "href": "href:180", + "id": "id:181", + "lifecycle_state": "stable", + "name": "ibm-reserved-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.20.4", + "auto_delete": true, + "created_at": "2023-03-13T11:51:04Z", + "href": "href:20", + "id": "id:21", + "lifecycle_state": "stable", + "name": "precision-grudge-daylight-married", + "owner": "user", + "resource_type": "subnet_reserved_ip", + "target": { + "href": "href:18", + "id": "id:19", + "name": "yarn-canary-guileless-deftly", + "resource_type": "network_interface" + } + }, + { + "address": "10.240.20.255", + "auto_delete": false, + "created_at": "2023-03-13T11:50:50Z", + "href": "href:182", + "id": "id:183", + "lifecycle_state": "stable", + "name": "ibm-broadcast-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + } + ], + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "resource_type": "subnet", + "routing_table": { + "href": "href:172", + "id": "id:173", + "name": "catnap-music-yearbook-rotunda", + "resource_type": "routing_table" + }, + "status": "available", + "tags": [ + "public" + ], + "total_ipv4_address_count": 256, + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:22", + "name": "us-south-1" + } + }, + { + "available_ipv4_address_count": 247, + "created_at": "2023-03-13T11:50:37Z", + "crn": "crn:74", + "href": "href:75", + "id": "id:76", + "ip_version": "ipv4", + "ipv4_cidr_block": "10.240.30.0/24", + "name": "subnet3-ky", + "network_acl": { + "crn": "crn:133", + "href": "href:134", + "id": "id:135", + "name": "acl3-ky" + }, + "reserved_ips": [ + { + "address": "10.240.30.0", + "auto_delete": false, + "created_at": "2023-03-13T11:50:37Z", + "href": "href:184", + "id": "id:185", + "lifecycle_state": "stable", + "name": "ibm-network-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.30.1", + "auto_delete": false, + "created_at": "2023-03-13T11:50:37Z", + "href": "href:186", + "id": "id:187", + "lifecycle_state": "stable", + "name": "ibm-default-gateway", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.30.2", + "auto_delete": false, + "created_at": "2023-03-13T11:50:37Z", + "href": "href:188", + "id": "id:189", + "lifecycle_state": "stable", + "name": "ibm-dns-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.30.3", + "auto_delete": false, + "created_at": "2023-03-13T11:50:37Z", + "href": "href:190", + "id": "id:191", + "lifecycle_state": "stable", + "name": "ibm-reserved-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + }, + { + "address": "10.240.30.4", + "auto_delete": true, + "created_at": "2023-03-13T11:50:51Z", + "href": "href:88", + "id": "id:89", + "lifecycle_state": "stable", + "name": "wobbling-pueblo-bulldozer-spring", + "owner": "user", + "resource_type": "subnet_reserved_ip", + "target": { + "href": "href:86", + "id": "id:87", + "name": "contest-dance-divided-brilliant", + "resource_type": "network_interface" + } + }, + { + "address": "10.240.30.5", + "auto_delete": true, + "created_at": "2023-03-13T11:50:51Z", + "href": "href:72", + "id": "id:73", + "lifecycle_state": "stable", + "name": "ointment-fading-shabby-sectional", + "owner": "user", + "resource_type": "subnet_reserved_ip", + "target": { + "href": "href:70", + "id": "id:71", + "name": "data-washstand-blot-scrambler", + "resource_type": "network_interface" + } + }, + { + "address": "10.240.30.6", + "auto_delete": true, + "created_at": "2023-03-13T11:50:51Z", + "href": "href:101", + "id": "id:102", + "lifecycle_state": "stable", + "name": "attach-portfolio-natural-lisp", + "owner": "user", + "resource_type": "subnet_reserved_ip", + "target": { + "href": "href:99", + "id": "id:100", + "name": "filterable-steersman-collar-whoops", + "resource_type": "network_interface" + } + }, + { + "address": "10.240.30.7", + "auto_delete": true, + "created_at": "2023-03-13T12:08:06Z", + "href": "href:4", + "id": "id:5", + "lifecycle_state": "stable", + "name": "vpe-for-etcd-db-ky", + "owner": "user", + "resource_type": "subnet_reserved_ip", + "target": { + "crn": "crn:1", + "href": "href:2", + "id": "id:3", + "name": "db-endpoint-gateway-ky", + "resource_type": "endpoint_gateway" + } + }, + { + "address": "10.240.30.255", + "auto_delete": false, + "created_at": "2023-03-13T11:50:37Z", + "href": "href:192", + "id": "id:193", + "lifecycle_state": "stable", + "name": "ibm-broadcast-address", + "owner": "provider", + "resource_type": "subnet_reserved_ip" + } + ], + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "resource_type": "subnet", + "routing_table": { + "href": "href:172", + "id": "id:173", + "name": "catnap-music-yearbook-rotunda", + "resource_type": "routing_table" + }, + "status": "available", + "tags": [ + "private" + ], + "total_ipv4_address_count": 256, + "vpc": { + "crn": "crn:12", + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_type": "vpc" + }, + "zone": { + "href": "href:22", + "name": "us-south-1" + } + } + ], + "vpcs": [ + { + "classic_access": false, + "created_at": "2023-03-13T11:50:18Z", + "crn": "crn:12", + "cse_source_ips": [ + { + "ip": { + "address": "10.12.127.77" + }, + "zone": { + "href": "href:22", + "name": "us-south-1" + } + }, + { + "ip": { + "address": "10.249.201.197" + }, + "zone": { + "href": "href:194", + "name": "us-south-2" + } + }, + { + "ip": { + "address": "10.12.165.70" + }, + "zone": { + "href": "href:195", + "name": "us-south-3" + } + } + ], + "default_network_acl": { + "crn": "crn:144", + "href": "href:145", + "id": "id:146", + "name": "demilune-humorless-captain-lurex" + }, + "default_routing_table": { + "href": "href:172", + "id": "id:173", + "name": "catnap-music-yearbook-rotunda", + "resource_type": "routing_table" + }, + "default_security_group": { + "crn": "crn:155", + "href": "href:156", + "id": "id:157", + "name": "barbecue-frayed-varied-average" + }, + "href": "href:13", + "id": "id:14", + "name": "test-vpc2-ky", + "resource_group": { + "href": "href:6", + "id": "id:7", + "name": "anonymous" + }, + "resource_type": "vpc", + "status": "available", + "tags": [] + } + ] +} diff --git a/pkg/vpcmodel/debugOutput.go b/pkg/vpcmodel/debugOutput.go index bccd04daf..2788f6b40 100644 --- a/pkg/vpcmodel/debugOutput.go +++ b/pkg/vpcmodel/debugOutput.go @@ -6,7 +6,7 @@ type DebugOutputFormatter struct { func (t *DebugOutputFormatter) WriteOutput(c1, c2 *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, - subnetsDiff *DiffBetweenSubnets, + subnetsDiff *diffBetweenCfgs, outFile string, grouping bool, uc OutputUseCase) (*VPCAnalysisOutput, error) { diff --git a/pkg/vpcmodel/diffSubnets_test.go b/pkg/vpcmodel/diffSubnets_test.go deleted file mode 100644 index 3b7abd7cd..000000000 --- a/pkg/vpcmodel/diffSubnets_test.go +++ /dev/null @@ -1,191 +0,0 @@ -package vpcmodel - -import ( - "fmt" - "strings" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/np-guard/vpc-network-config-analyzer/pkg/common" -) - -// simple diff: -// cfg1 has subnet0, subnet1, subnet2, subnet3, subnet4 -// subnet0 -> subnet1 -// subnet1 -> subnet2 -// subnet3 -> subnet1 -// subnet2 -> subnet3 -// subnet3 -> subnet2 -// subnet3 -> subnet4 not all connections -// cfg2 has subnet2, subnet3, subnet4 -// subnet3 -> subnet2 -// subnet3 -> subnet4 - -// expected diff cfg1 connMissingOrChanged cfg2: -// cfg1 connMissingOrChanged cfg2 -// subnet0 -> subnet1 missing src and dst -// subnet1 -> subnet2 missing src -// subnet3 -> subnet1 missing dst -// subnet2 -> subnet3 missing connection -// -// cfg2 connMissingOrChanged cfg1 -// subnet1 connMissingOrChanged subnet2: -// subnet3 -> subnet4 different connection - -func configSimpleSubnetSubtract() (subnetConfigConn1, subnetConfigConn2 *SubnetConfigConnectivity) { - cfg1 := &VPCConfig{Nodes: []Node{}, NodeSets: []NodeSet{}} - cfg1.Nodes = append(cfg1.Nodes, - &mockNetIntf{cidr: "10.0.20.5/32", name: "vsi1-1"}, - &mockNetIntf{cidr: "10.3.20.6/32", name: "vsi1-2"}, - &mockNetIntf{cidr: "10.7.20.7/32", name: "vsi1-3"}) - - cfg1.NodeSets = append(cfg1.NodeSets, &mockSubnet{"10.0.20.0/22", "subnet0", []Node{cfg1.Nodes[0]}}, - &mockSubnet{"10.1.20.0/22", "subnet1", []Node{cfg1.Nodes[0]}}, - &mockSubnet{"10.2.20.0/22", "subnet2", []Node{cfg1.Nodes[1]}}) - cfg1.NodeSets = append(cfg1.NodeSets, &mockSubnet{"10.3.20.0/22", "subnet3", []Node{cfg1.Nodes[2]}}, - &mockSubnet{"10.4.20.0/22", "subnet4", []Node{cfg1.Nodes[2]}}) - - cfg2 := &VPCConfig{Nodes: []Node{}, NodeSets: []NodeSet{}} - cfg2.Nodes = append(cfg2.Nodes, - &mockNetIntf{cidr: "10.3.20.5/32", name: "vsi2-1"}, - &mockNetIntf{cidr: "10.7.20.6/32", name: "vsi2-2"}, - &mockNetIntf{cidr: "10.9.20.7/32", name: "vsi2-3"}, - &mockNetIntf{cidr: "11.4.20.6/32", name: "vsi2-4"}) - cfg2.NodeSets = append(cfg2.NodeSets, &mockSubnet{"10.2.20.0/22", "subnet2", []Node{cfg2.Nodes[0]}}, - &mockSubnet{"10.3.20.0/22", "subnet3", []Node{cfg2.Nodes[1]}}, - &mockSubnet{"10.4.20.0/22", "subnet4", []Node{cfg2.Nodes[2]}}, - &mockSubnet{"11.4.20.0/22", "subnet5", []Node{cfg2.Nodes[3]}}) - - connectionTCP := common.NewConnectionSet(false) - connectionTCP.AddTCPorUDPConn(common.ProtocolTCP, 10, 100, 443, 443) - subnetConnMap1 := &VPCsubnetConnectivity{AllowedConnsCombined: NewSubnetConnectivityMap()} - subnetConnMap1.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg1.NodeSets[0], cfg1.NodeSets[1], common.NewConnectionSet(true)) - subnetConnMap1.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg1.NodeSets[1], cfg1.NodeSets[2], common.NewConnectionSet(true)) - subnetConnMap1.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg1.NodeSets[3], cfg1.NodeSets[1], common.NewConnectionSet(true)) - subnetConnMap1.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg1.NodeSets[2], cfg1.NodeSets[3], common.NewConnectionSet(true)) - subnetConnMap1.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg1.NodeSets[3], cfg1.NodeSets[2], common.NewConnectionSet(true)) - subnetConnMap1.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg1.NodeSets[3], cfg1.NodeSets[4], connectionTCP) - - subnetConnMap2 := &VPCsubnetConnectivity{AllowedConnsCombined: NewSubnetConnectivityMap()} - subnetConnMap2.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg2.NodeSets[1], cfg2.NodeSets[0], common.NewConnectionSet(true)) - subnetConnMap2.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg2.NodeSets[1], cfg2.NodeSets[2], common.NewConnectionSet(true)) - subnetConnMap2.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg2.NodeSets[2], cfg2.NodeSets[3], common.NewConnectionSet(true)) - - subnetConfigConn1 = &SubnetConfigConnectivity{cfg1, subnetConnMap1.AllowedConnsCombined} - subnetConfigConn2 = &SubnetConfigConnectivity{cfg2, subnetConnMap2.AllowedConnsCombined} - - return subnetConfigConn1, subnetConfigConn2 -} - -func TestSimpleSubnetSubtract(t *testing.T) { - subnetConfigConn1, subnetConfigConn2 := configSimpleSubnetSubtract() - subnet1Subtract2, err := subnetConfigConn1.connMissingOrChanged(subnetConfigConn2, true) - if err != nil { - fmt.Println("error:", err.Error()) - } - subnet1Subtract2Str := subnet1Subtract2.EnhancedString(true) - fmt.Printf("subnet1Subtract2:\n%v\n", subnet1Subtract2Str) - require.Equal(t, err, nil) - newLines := strings.Count(subnet1Subtract2Str, "\n") - require.Equal(t, 5, newLines) - require.Contains(t, subnet1Subtract2Str, "diff-type: removed, source: subnet0, destination: subnet1, "+ - "config1: All Connections, config2: No connection, subnets-diff-info: subnet0 and subnet1 removed") - require.Contains(t, subnet1Subtract2Str, "diff-type: removed, source: subnet1, destination: subnet2, "+ - "config1: All Connections, config2: No connection, subnets-diff-info: subnet1 removed") - require.Contains(t, subnet1Subtract2Str, "diff-type: removed, source: subnet2, destination: subnet3, "+ - "config1: All Connections, config2: No connection, subnets-diff-info:") - require.Contains(t, subnet1Subtract2Str, "diff-type: removed, source: subnet3, destination: subnet1, "+ - "config1: All Connections, config2: No connection, subnets-diff-info: subnet1 removed") - require.Contains(t, subnet1Subtract2Str, "diff-type: changed, source: subnet3, destination: subnet4, "+ - "config1: protocol: TCP src-ports: 10-100 dst-ports: 443, config2: All Connections, subnets-diff-info:") - - cfg2Subtract1, err := subnetConfigConn2.connMissingOrChanged(subnetConfigConn1, false) - if err != nil { - fmt.Println("error:", err.Error()) - } - require.Equal(t, err, nil) - subnet2Subtract1Str := cfg2Subtract1.EnhancedString(false) - fmt.Printf("cfg2Subtract1:\n%v", subnet2Subtract1Str) - require.Equal(t, subnet2Subtract1Str, "diff-type: added, source: subnet4, destination: subnet5, config1: "+ - "No connection, config2: All Connections, subnets-diff-info: subnet5 added\n") -} - -func configSimpleIPAndSubnetSubtract() (subnetConfigConn1, subnetConfigConn2 *SubnetConfigConnectivity) { - cfg1 := &VPCConfig{Nodes: []Node{}, NodeSets: []NodeSet{}} - cfg1.NodeSets = append(cfg1.NodeSets, &mockSubnet{"10.1.20.0/22", "subnet1", nil}, - &mockSubnet{"10.2.20.0/22", "subnet2", nil}) - cfg1.Nodes = append(cfg1.Nodes, - &mockNetIntf{cidr: "1.2.3.0/30", name: "public1-1", isPublic: true}, - &mockNetIntf{cidr: "250.2.4.0/24", name: "public1-2", isPublic: true}, - &mockNetIntf{cidr: "200.2.4.0/24", name: "public1-3", isPublic: true}) - - cfg2 := &VPCConfig{Nodes: []Node{}, NodeSets: []NodeSet{}} - cfg2.NodeSets = append(cfg2.NodeSets, &mockSubnet{"10.1.20.0/22", "subnet1", nil}, - &mockSubnet{"10.2.20.0/22", "subnet2", nil}) - cfg2.Nodes = append(cfg2.Nodes, - &mockNetIntf{cidr: "1.2.3.0/26", name: "public2-1", isPublic: true}, - &mockNetIntf{cidr: "250.2.4.0/30", name: "public2-2", isPublic: true}, - &mockNetIntf{cidr: "200.2.4.0/24", name: "public1-3", isPublic: true}) - - // cfg1 cfg2 - // and are comparable - // and are comparable - // and are comparable - // and are comparable - subnetConnMap1 := &VPCsubnetConnectivity{AllowedConnsCombined: NewSubnetConnectivityMap()} - subnetConnMap1.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg1.Nodes[0], cfg1.NodeSets[0], common.NewConnectionSet(true)) - subnetConnMap1.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg1.Nodes[0], cfg1.NodeSets[1], common.NewConnectionSet(true)) - subnetConnMap1.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg1.Nodes[1], cfg1.NodeSets[1], common.NewConnectionSet(true)) - subnetConnMap1.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg1.NodeSets[1], cfg1.Nodes[0], common.NewConnectionSet(true)) - subnetConnMap1.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg1.NodeSets[1], cfg1.Nodes[2], common.NewConnectionSet(true)) - - subnetConnMap2 := &VPCsubnetConnectivity{AllowedConnsCombined: NewSubnetConnectivityMap()} - subnetConnMap2.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg2.Nodes[0], cfg2.NodeSets[0], common.NewConnectionSet(true)) - subnetConnMap2.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg2.Nodes[0], cfg2.NodeSets[1], common.NewConnectionSet(true)) - subnetConnMap2.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg2.Nodes[1], cfg2.NodeSets[1], common.NewConnectionSet(true)) - subnetConnMap2.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg2.NodeSets[1], cfg2.Nodes[0], common.NewConnectionSet(true)) - connectionTCP := common.NewConnectionSet(false) - connectionTCP.AddTCPorUDPConn(common.ProtocolTCP, 0, 1000, 0, 443) - subnetConnMap2.AllowedConnsCombined.updateAllowedSubnetConnsMap(cfg2.NodeSets[1], cfg2.Nodes[2], connectionTCP) - - subnetConfigConn1 = &SubnetConfigConnectivity{cfg1, subnetConnMap1.AllowedConnsCombined} - subnetConfigConn2 = &SubnetConfigConnectivity{cfg2, subnetConnMap2.AllowedConnsCombined} - - return subnetConfigConn1, subnetConfigConn2 -} - -func TestSimpleIPAndSubnetSubtract(t *testing.T) { - cfgConn1, cfgConn2 := configSimpleIPAndSubnetSubtract() - alignedCfgConn1, alignedCfgConn2, err := cfgConn1.getConnectivesWithSameIPBlocks(cfgConn2) - if err != nil { - fmt.Printf("err: %v\n", err.Error()) - require.Equal(t, err, nil) - return - } - - // verified bit by bit :-) - cfg1SubCfg2, err := alignedCfgConn1.connMissingOrChanged(alignedCfgConn2, true) - if err != nil { - fmt.Println("error:", err.Error()) - } - require.Equal(t, err, nil) - cfg1SubtractCfg2Str := cfg1SubCfg2.EnhancedString(true) - fmt.Printf("cfg1SubCfg2:\n%v\n", cfg1SubtractCfg2Str) - newLines := strings.Count(cfg1SubtractCfg2Str, "\n") - require.Equal(t, 7, newLines) - require.Contains(t, cfg1SubtractCfg2Str, "diff-type: removed, source: Public Internet [250.2.4.128/25], destination: subnet2, "+ - "config1: All Connections, config2: No connection, subnets-diff-info:") - require.Contains(t, cfg1SubtractCfg2Str, "diff-type: removed, source: Public Internet [250.2.4.16/28], destination: subnet2, "+ - "config1: All Connections, config2: No connection, subnets-diff-info:") - require.Contains(t, cfg1SubtractCfg2Str, "diff-type: removed, source: Public Internet [250.2.4.32/27], destination: subnet2, "+ - "config1: All Connections, config2: No connection, subnets-diff-info:") - require.Contains(t, cfg1SubtractCfg2Str, "diff-type: removed, source: Public Internet [250.2.4.4/30], destination: subnet2, "+ - "config1: All Connections, config2: No connection, subnets-diff-info:") - require.Contains(t, cfg1SubtractCfg2Str, "diff-type: removed, source: Public Internet [250.2.4.64/26], destination: subnet2, "+ - "config1: All Connections, config2: No connection, subnets-diff-info:") - require.Contains(t, cfg1SubtractCfg2Str, "diff-type: removed, source: Public Internet [250.2.4.8/29], destination: subnet2, "+ - "config1: All Connections, config2: No connection, subnets-diff-info:") - require.Contains(t, cfg1SubtractCfg2Str, "diff-type: changed, source: subnet2, destination: Public Internet [200.2.4.0/24], "+ - "config1: All Connections, config2: protocol: TCP src-ports: 0-1000 dst-ports: 0-443, subnets-diff-info:") -} diff --git a/pkg/vpcmodel/drawioOutput.go b/pkg/vpcmodel/drawioOutput.go index 493088604..b4456b6dc 100644 --- a/pkg/vpcmodel/drawioOutput.go +++ b/pkg/vpcmodel/drawioOutput.go @@ -112,7 +112,7 @@ func (d *DrawioOutputFormatter) createEdges() { func (d *DrawioOutputFormatter) WriteOutput(c1, c2 *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, - subnetsDiff *DiffBetweenSubnets, + subnetsDiff *diffBetweenCfgs, outFile string, grouping bool, uc OutputUseCase) (*VPCAnalysisOutput, error) { @@ -140,7 +140,7 @@ type ArchDrawioOutputFormatter struct { func (d *ArchDrawioOutputFormatter) WriteOutput(c1, c2 *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, - subnetsDiff *DiffBetweenSubnets, + subnetsDiff *diffBetweenCfgs, outFile string, grouping bool, uc OutputUseCase) (*VPCAnalysisOutput, error) { diff --git a/pkg/vpcmodel/grouping_test.go b/pkg/vpcmodel/grouping_test.go index 77127efe2..8753902ea 100644 --- a/pkg/vpcmodel/grouping_test.go +++ b/pkg/vpcmodel/grouping_test.go @@ -382,13 +382,13 @@ func configSubnetSelfLoop() (*VPCConfig, *VPCsubnetConnectivity) { &mockSubnet{"10.3.20.0/22", "subnet2", []Node{res.Nodes[1]}}, &mockSubnet{"10.7.20.0/22", "subnet3", []Node{res.Nodes[2]}}) - res1 := &VPCsubnetConnectivity{AllowedConnsCombined: NewSubnetConnectivityMap()} - res1.AllowedConnsCombined.updateAllowedSubnetConnsMap(res.NodeSets[0], res.NodeSets[1], common.NewConnectionSet(true)) - res1.AllowedConnsCombined.updateAllowedSubnetConnsMap(res.NodeSets[0], res.NodeSets[2], common.NewConnectionSet(true)) - res1.AllowedConnsCombined.updateAllowedSubnetConnsMap(res.NodeSets[1], res.NodeSets[0], common.NewConnectionSet(true)) - res1.AllowedConnsCombined.updateAllowedSubnetConnsMap(res.NodeSets[1], res.NodeSets[2], common.NewConnectionSet(true)) - res1.AllowedConnsCombined.updateAllowedSubnetConnsMap(res.NodeSets[2], res.NodeSets[0], common.NewConnectionSet(true)) - res1.AllowedConnsCombined.updateAllowedSubnetConnsMap(res.NodeSets[2], res.NodeSets[1], common.NewConnectionSet(true)) + res1 := &VPCsubnetConnectivity{AllowedConnsCombined: GeneralConnectivityMap{}} + res1.AllowedConnsCombined.updateAllowedConnsMap(res.NodeSets[0], res.NodeSets[1], common.NewConnectionSet(true)) + res1.AllowedConnsCombined.updateAllowedConnsMap(res.NodeSets[0], res.NodeSets[2], common.NewConnectionSet(true)) + res1.AllowedConnsCombined.updateAllowedConnsMap(res.NodeSets[1], res.NodeSets[0], common.NewConnectionSet(true)) + res1.AllowedConnsCombined.updateAllowedConnsMap(res.NodeSets[1], res.NodeSets[2], common.NewConnectionSet(true)) + res1.AllowedConnsCombined.updateAllowedConnsMap(res.NodeSets[2], res.NodeSets[0], common.NewConnectionSet(true)) + res1.AllowedConnsCombined.updateAllowedConnsMap(res.NodeSets[2], res.NodeSets[1], common.NewConnectionSet(true)) return res, res1 } diff --git a/pkg/vpcmodel/jsonOutput.go b/pkg/vpcmodel/jsonOutput.go index 0c88219af..93b9dc5dc 100644 --- a/pkg/vpcmodel/jsonOutput.go +++ b/pkg/vpcmodel/jsonOutput.go @@ -14,7 +14,7 @@ type JSONoutputFormatter struct { func (j *JSONoutputFormatter) WriteOutput(c1, c2 *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, - subnetsDiff *DiffBetweenSubnets, + subnetsDiff *diffBetweenCfgs, outFile string, grouping bool, uc OutputUseCase) (*VPCAnalysisOutput, error) { diff --git a/pkg/vpcmodel/mdOutput.go b/pkg/vpcmodel/mdOutput.go index 82afca332..66fbda15e 100644 --- a/pkg/vpcmodel/mdOutput.go +++ b/pkg/vpcmodel/mdOutput.go @@ -18,7 +18,7 @@ const ( func (m *MDoutputFormatter) WriteOutput(c1, c2 *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, - subnetsDiff *DiffBetweenSubnets, + subnetsDiff *diffBetweenCfgs, outFile string, grouping bool, uc OutputUseCase) (*VPCAnalysisOutput, error) { diff --git a/pkg/vpcmodel/output.go b/pkg/vpcmodel/output.go index 12e5ca41f..a16d4bc3e 100644 --- a/pkg/vpcmodel/output.go +++ b/pkg/vpcmodel/output.go @@ -29,8 +29,9 @@ 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) + SubnetsDiff // diff between subnets connectivity of two cfgs (consider nacl + pgw) + EndpointsDiff // diff between vsis connectivity of two cfgs ) // OutputGenerator captures one vpc config1 with its connectivity analysis results, and implements @@ -42,7 +43,7 @@ type OutputGenerator struct { useCase OutputUseCase nodesConn *VPCConnectivity subnetsConn *VPCsubnetConnectivity - subnetsDiff *DiffBetweenSubnets + cfgsDiff *diffBetweenCfgs } func NewOutputGenerator(c1, c2 *VPCConfig, grouping bool, uc OutputUseCase, archOnly bool) (*OutputGenerator, error) { @@ -67,13 +68,21 @@ func NewOutputGenerator(c1, c2 *VPCConfig, grouping bool, uc OutputUseCase, arch } res.subnetsConn = subnetsConn } - if uc == AllSubnetsDiff { - configsForDiff := &ConfigsForDiff{c1, c2} - subnetsDiff, err := configsForDiff.GetSubnetsDiff(grouping) + if uc == SubnetsDiff { + configsForDiff := &configsForDiff{c1, c2, Subnets} + configsDiff, err := configsForDiff.GetDiff() if err != nil { return nil, err } - res.subnetsDiff = subnetsDiff + res.cfgsDiff = configsDiff + } + if uc == EndpointsDiff { + configsForDiff := &configsForDiff{c1, c2, Vsis} + configsDiff, err := configsForDiff.GetDiff() + if err != nil { + return nil, err + } + res.cfgsDiff = configsDiff } } return res, nil @@ -107,11 +116,11 @@ func (o *OutputGenerator) Generate(f OutFormat, outFile string) (*VPCAnalysisOut return nil, errors.New("unsupported output format") } - return formatter.WriteOutput(o.config1, o.config2, o.nodesConn, o.subnetsConn, o.subnetsDiff, outFile, o.outputGrouping, o.useCase) + return formatter.WriteOutput(o.config1, o.config2, o.nodesConn, o.subnetsConn, o.cfgsDiff, outFile, o.outputGrouping, o.useCase) } type OutputFormatter interface { - WriteOutput(c1, c2 *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, subnetsDiff *DiffBetweenSubnets, + WriteOutput(c1, c2 *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, subnetsDiff *diffBetweenCfgs, outFile string, grouping bool, uc OutputUseCase) (*VPCAnalysisOutput, error) } diff --git a/pkg/vpcmodel/semanticDiffSubnets.go b/pkg/vpcmodel/semanticDiff.go similarity index 67% rename from pkg/vpcmodel/semanticDiffSubnets.go rename to pkg/vpcmodel/semanticDiff.go index c45349a7c..e715c2b2c 100644 --- a/pkg/vpcmodel/semanticDiffSubnets.go +++ b/pkg/vpcmodel/semanticDiff.go @@ -19,6 +19,13 @@ const ( changedConnection ) +type diffAnalysisType = int + +const ( + Vsis diffAnalysisType = iota + Subnets +) + const ( castingNodeErr = "%s should be external node but casting to Node failed" ) @@ -29,64 +36,104 @@ type connectionDiff struct { diff DiffType } -type SubnetsDiff map[VPCResourceIntf]map[VPCResourceIntf]*connectionDiff +type connectivityDiff map[VPCResourceIntf]map[VPCResourceIntf]*connectionDiff -type ConfigsForDiff struct { - config1 *VPCConfig - config2 *VPCConfig +type configsForDiff struct { + config1 *VPCConfig + config2 *VPCConfig + diffAnalysis diffAnalysisType } -type SubnetConfigConnectivity struct { - config *VPCConfig - subnetConnectivity SubnetConnectivityMap +type configConnectivity struct { + config *VPCConfig + connectivity GeneralConnectivityMap } -type DiffBetweenSubnets struct { - subnet1Subtract2 SubnetsDiff - subnet2Subtract1 SubnetsDiff +type diffBetweenCfgs struct { + cfg1ConnRemovedFrom2 connectivityDiff + cfg2ConnRemovedFrom1 connectivityDiff + diffAnalysis diffAnalysisType } -func (configs ConfigsForDiff) GetSubnetsDiff(grouping bool) (*DiffBetweenSubnets, error) { - // 1. compute connectivity for each of the subnets - subnetsConn1, err := configs.config1.GetSubnetsConnectivity(true, grouping) +// GetDiff given 2 *VPCConfigs and an diff analysis - either subnets or endpoints - +// computes and returns the semantic diff of endpoints or subnets connectivity, as per the required analysis +func (configs configsForDiff) GetDiff() (*diffBetweenCfgs, error) { + // 1. compute connectivity for each of the configurations + generalConnectivityMap1, err := configs.config1.getAllowedConnectionsCombined(configs.diffAnalysis) if err != nil { return nil, err } - subnetsConn2, err := configs.config2.GetSubnetsConnectivity(true, grouping) + generalConnectivityMap2, err := configs.config2.getAllowedConnectionsCombined(configs.diffAnalysis) if err != nil { return nil, err } // 2. Computes delta in both directions - subnetConfigConn1 := &SubnetConfigConnectivity{configs.config1, - subnetsConn1.AllowedConnsCombined} - subnetConfigConn2 := &SubnetConfigConnectivity{configs.config2, - subnetsConn2.AllowedConnsCombined} + configConn1 := &configConnectivity{configs.config1, + generalConnectivityMap1} + configConn2 := &configConnectivity{configs.config2, + generalConnectivityMap2} alignedConfigConnectivity1, alignedConfigConnectivity2, err := - subnetConfigConn1.getConnectivesWithSameIPBlocks(subnetConfigConn2) + configConn1.getConnectivesWithSameIPBlocks(configConn2) if err != nil { return nil, err } - subnet1Subtract2, err1 := alignedConfigConnectivity1.connMissingOrChanged(alignedConfigConnectivity2, true) + cfg1ConnRemovedFrom2, err1 := alignedConfigConnectivity1.connMissingOrChanged(alignedConfigConnectivity2, configs.diffAnalysis, true) if err1 != nil { return nil, err1 } - subnet2Subtract1, err2 := alignedConfigConnectivity2.connMissingOrChanged(alignedConfigConnectivity1, false) + cfg2ConnRemovedFrom1, err2 := alignedConfigConnectivity2.connMissingOrChanged(alignedConfigConnectivity1, configs.diffAnalysis, false) if err2 != nil { return nil, err2 } // 3. ToDo: grouping, see comment at the end of this file - res := &DiffBetweenSubnets{ - subnet1Subtract2: subnet1Subtract2, - subnet2Subtract1: subnet2Subtract1} + res := &diffBetweenCfgs{ + cfg1ConnRemovedFrom2: cfg1ConnRemovedFrom2, + cfg2ConnRemovedFrom1: cfg2ConnRemovedFrom1, + diffAnalysis: configs.diffAnalysis} return res, nil } +func (c *VPCConfig) getAllowedConnectionsCombined( + diffAnalysis diffAnalysisType) (generalConnectivityMap GeneralConnectivityMap, err error) { + if diffAnalysis == Subnets { + subnetsConn, err := c.GetSubnetsConnectivity(true, false) + if err != nil { + return nil, err + } + return subnetsConn.AllowedConnsCombined, err + } else if diffAnalysis == Vsis { + connectivity1, err := c.GetVPCNetworkConnectivity(false) + if err != nil { + return nil, err + } + return connectivity1.AllowedConnsCombined.nodesConnectivityToGeneralConnectivity(), nil + } + return nil, fmt.Errorf("illegal diff analysis type") +} + +func (nodesConnMap NodesConnectionsMap) nodesConnectivityToGeneralConnectivity() (generalConnMap GeneralConnectivityMap) { + generalConnMap = GeneralConnectivityMap{} + for src, connsMap := range nodesConnMap { + for dst, conn := range connsMap { + if conn.IsEmpty() { + continue + } + if _, ok := generalConnMap[src]; !ok { + generalConnMap[src] = map[VPCResourceIntf]*common.ConnectionSet{} + } + generalConnMap[src][dst] = conn + } + } + return generalConnMap +} + // for a given VPCResourceIntf (representing a subnet or an external ip) in config return the VPCResourceIntf representing the // subnet/external address in otherConfig or nil if the subnet does not exist in the other config. -func (c *VPCConfig) getVPCResourceInfInOtherConfig(other *VPCConfig, ep VPCResourceIntf) (res VPCResourceIntf, err error) { +func (c *VPCConfig) getVPCResourceInfInOtherConfig(other *VPCConfig, ep VPCResourceIntf, + diffAnalysis diffAnalysisType) (res VPCResourceIntf, err error) { if ep.IsExternal() { var node Node var ok bool @@ -96,42 +143,56 @@ func (c *VPCConfig) getVPCResourceInfInOtherConfig(other *VPCConfig, ep VPCResou } return nil, fmt.Errorf(castingNodeErr, node.Name()) } - for _, nodeSet := range other.NodeSets { - if nodeSet.Name() == ep.Name() { - res = VPCResourceIntf(nodeSet) - return res, nil + // endpoint is a vsi or a subnet, depending on diffAnalysis value + if diffAnalysis == Vsis { + for _, node := range other.Nodes { + if !node.IsInternal() { + continue + } + if node.Name() == ep.Name() { + res = VPCResourceIntf(node) + return res, nil + } + } + } else if diffAnalysis == Subnets { + for _, nodeSet := range other.NodeSets { + if nodeSet.Name() == ep.Name() { + res = VPCResourceIntf(nodeSet) + return res, nil + } } } return nil, nil } -// connMissingOrChanged of subnetConfConnectivity w.r.t. the other: +// connMissingOrChanged of confConnectivity w.r.t. the other: // connections may be identical, non-existing in other or existing in other but changed; // the latter are included only if includeChanged, to avoid duplication in the final presentation // // assumption: any connection from connectivity and "other" have src (dst) which are either disjoint or equal -func (subnetConfConnectivity *SubnetConfigConnectivity) connMissingOrChanged(other *SubnetConfigConnectivity, includeChanged bool) ( - connectivitySubtract SubnetsDiff, err error) { - connectivitySubtract = map[VPCResourceIntf]map[VPCResourceIntf]*connectionDiff{} - for src, endpointConns := range subnetConfConnectivity.subnetConnectivity { +func (confConnectivity *configConnectivity) connMissingOrChanged(other *configConnectivity, + diffAnalysis diffAnalysisType, includeChanged bool) ( + connectivityMissingOrChanged connectivityDiff, err error) { + connectivityMissingOrChanged = map[VPCResourceIntf]map[VPCResourceIntf]*connectionDiff{} + for src, endpointConns := range confConnectivity.connectivity { for dst, conns := range endpointConns { if conns.IsEmpty() { continue } - if _, ok := connectivitySubtract[src]; !ok { - connectivitySubtract[src] = map[VPCResourceIntf]*connectionDiff{} + if _, ok := connectivityMissingOrChanged[src]; !ok { + connectivityMissingOrChanged[src] = map[VPCResourceIntf]*connectionDiff{} } - srcInOther, err1 := subnetConfConnectivity.config.getVPCResourceInfInOtherConfig(other.config, src) + srcInOther, err1 := confConnectivity.config.getVPCResourceInfInOtherConfig(other.config, src, diffAnalysis) if err1 != nil { return nil, err1 } - dstInOther, err2 := subnetConfConnectivity.config.getVPCResourceInfInOtherConfig(other.config, dst) + dstInOther, err2 := confConnectivity.config.getVPCResourceInfInOtherConfig(other.config, dst, diffAnalysis) if err2 != nil { return nil, err2 } connDiff := &connectionDiff{conns, nil, missingConnection} if srcInOther != nil && dstInOther != nil { - if otherSrc, ok := other.subnetConnectivity[srcInOther]; ok { + if otherSrc, ok := other.connectivity[srcInOther]; ok { if otherConn, ok := otherSrc[dstInOther]; ok { equalConnections := conns.Equal(otherConn) && // ToDo: https://github.com/np-guard/vpc-network-config-analyzer/issues/199 @@ -146,20 +207,20 @@ func (subnetConfConnectivity *SubnetConfigConnectivity) connMissingOrChanged(oth } else { // srcInOther == nil || dstInOther == nil connDiff.diff = getDiffType(src, srcInOther, dst, dstInOther) } - connectivitySubtract[src][dst] = connDiff + connectivityMissingOrChanged[src][dst] = connDiff } } - return connectivitySubtract, nil + return connectivityMissingOrChanged, nil } // lack of a subnet is marked as a missing endpoint // a lack of identical external endpoint is considered as a missing connection // and not as a missing endpoint func getDiffType(src, srcInOther, dst, dstInOther VPCResourceIntf) DiffType { - _, srcIsSubnet := src.(NodeSet) - _, dstIsSubnet := dst.(NodeSet) - missingSrc := srcInOther == nil && srcIsSubnet - missingDst := dstInOther == nil && dstIsSubnet + srcIsInternal := !src.IsExternal() + dstIsInternal := !dst.IsExternal() + missingSrc := srcInOther == nil && srcIsInternal + missingDst := dstInOther == nil && dstIsInternal switch { case missingSrc && missingDst: return missingSrcDstEP @@ -173,17 +234,17 @@ func getDiffType(src, srcInOther, dst, dstInOther VPCResourceIntf) DiffType { return noDiff } -// EnhancedString ToDo: likely the current printing functionality will no longer be needed once the grouping is added +// string 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) + - diff.subnet2Subtract1.EnhancedString(false) +func (diff *diffBetweenCfgs) String() string { + return diff.cfg1ConnRemovedFrom2.string(diff.diffAnalysis, true) + + diff.cfg2ConnRemovedFrom1.string(diff.diffAnalysis, false) } -func (subnetDiff *SubnetsDiff) EnhancedString(thisMinusOther bool) string { +func (connDiff *connectivityDiff) string(diffAnalysis diffAnalysisType, thisMinusOther bool) string { strList := []string{} - for src, endpointConnDiff := range *subnetDiff { + for src, endpointConnDiff := range *connDiff { for dst, connDiff := range endpointConnDiff { conn1Str, conn2Str := "", "" if thisMinusOther { @@ -194,8 +255,14 @@ func (subnetDiff *SubnetsDiff) EnhancedString(thisMinusOther bool) string { conn2Str = connStr(connDiff.conn1) } diffType, endpointsDiff := diffAndEndpointsDisc(connDiff.diff, src, dst, thisMinusOther) - printDiff := fmt.Sprintf("diff-type: %s, source: %s, destination: %s, config1: %s, config2: %s, subnets-diff-info: %s\n", - diffType, src.Name(), dst.Name(), conn1Str, conn2Str, endpointsDiff) + diffInfo := "" + if diffAnalysis == Subnets { + diffInfo = "subnets-diff-info:" + } else if diffAnalysis == Vsis { + diffInfo = "vsis-diff-info:" + } + printDiff := fmt.Sprintf("diff-type: %s, source: %s, destination: %s, config1: %s, config2: %s, %s %s\n", + diffType, src.Name(), dst.Name(), conn1Str, conn2Str, diffInfo, endpointsDiff) strList = append(strList, printDiff) } } @@ -204,7 +271,7 @@ func (subnetDiff *SubnetsDiff) EnhancedString(thisMinusOther bool) string { return res } -// prints connection for func (subnetDiff *SubnetsDiff) EnhancedString(..) where the connection could be empty +// prints connection for the above string(..) where the connection could be empty func connStr(conn *common.ConnectionSet) string { if conn == nil { return "No connection" @@ -238,53 +305,53 @@ func diffAndEndpointsDisc(diff DiffType, src, dst VPCResourceIntf, thisMinusOthe return "", "" } -// getConnectivesWithSameIPBlocks generates from subnet1Connectivity.AllowedConnsCombined and subnet2Connectivity.AllowedConnsCombined -// Two equivalent SubnetConnectivityMap objects s.t. any (src1, dst1) of subnet1Connectivity and -// (src2, dst2) of subnet2Connectivity s.t. if src1 and src2 (dst1 and dst2) are both external then +// getConnectivesWithSameIPBlocks generates from the given GeneralConnectivityMap +// Two equivalent GeneralConnectivityMap objects s.t. any (src1, dst1) of the first map and +// (src2, dst2) of the 2nd map s.t. if src1 and src2 (dst1 and dst2) are both external then // they are either equal or disjoint -func (subnetConfConnectivity *SubnetConfigConnectivity) getConnectivesWithSameIPBlocks(otherConfConnectivity *SubnetConfigConnectivity) ( - alignedConnectivityConfig, alignedOtherConnectivityConfig *SubnetConfigConnectivity, myErr error) { +func (confConnectivity *configConnectivity) getConnectivesWithSameIPBlocks(otherConfConnectivity *configConnectivity) ( + alignedConnectivityConfig, alignedOtherConnectivityConfig *configConnectivity, myErr error) { // 1. computes new set of external nodes (only type of nodes here) in cfg1 and cfg2 // does so by computing disjoint block between src+dst ipBlocks in cfg1 and in cfg2 // the new set of external nodes is determined based on them - connectivityIPBlist, err := subnetConfConnectivity.subnetConnectivity.getIPBlocksList() + connectivityIPBlist, err := confConnectivity.connectivity.getIPBlocksList() if err != nil { return nil, nil, err } - otherIPBlist, err := otherConfConnectivity.subnetConnectivity.getIPBlocksList() + otherIPBlist, err := otherConfConnectivity.connectivity.getIPBlocksList() if err != nil { return nil, nil, err } disjointIPblocks := common.DisjointIPBlocks(connectivityIPBlist, otherIPBlist) // 2. copy configs and generates Nodes[] as per disjointIPblocks - err = subnetConfConnectivity.config.refineConfigExternalNodes(disjointIPblocks) + err = confConnectivity.config.refineConfigExternalNodes(disjointIPblocks) if err != nil { return nil, nil, err } - alignedConfig := subnetConfConnectivity.config + alignedConfig := confConnectivity.config err = otherConfConnectivity.config.refineConfigExternalNodes(disjointIPblocks) if err != nil { return nil, nil, err } otherAlignedConfig := otherConfConnectivity.config // 3. resize connections as per the new Nodes[] - alignedConnectivity, err := subnetConfConnectivity.subnetConnectivity.alignConnectionsGivenIPBlists( + alignedConnectivity, err := confConnectivity.connectivity.alignConnectionsGivenIPBlists( alignedConfig, disjointIPblocks) if err != nil { return nil, nil, err } - alignedOtherConnectivity, err := otherConfConnectivity.subnetConnectivity.alignConnectionsGivenIPBlists( + alignedOtherConnectivity, err := otherConfConnectivity.connectivity.alignConnectionsGivenIPBlists( otherAlignedConfig, disjointIPblocks) if err != nil { return nil, nil, err } - return &SubnetConfigConnectivity{alignedConfig, alignedConnectivity}, - &SubnetConfigConnectivity{otherAlignedConfig, alignedOtherConnectivity}, nil + return &configConnectivity{alignedConfig, alignedConnectivity}, + &configConnectivity{otherAlignedConfig, alignedOtherConnectivity}, nil } -func (subnetConnectivity *SubnetConnectivityMap) alignConnectionsGivenIPBlists(config *VPCConfig, disjointIPblocks []*common.IPBlock) ( - alignedConnectivity SubnetConnectivityMap, err error) { - alignedConnectivitySrc, err := subnetConnectivity.actualAlignSrcOrDstGivenIPBlists(config, disjointIPblocks, true) +func (connectivityMap *GeneralConnectivityMap) alignConnectionsGivenIPBlists(config *VPCConfig, disjointIPblocks []*common.IPBlock) ( + alignedConnectivity GeneralConnectivityMap, err error) { + alignedConnectivitySrc, err := connectivityMap.actualAlignSrcOrDstGivenIPBlists(config, disjointIPblocks, true) if err != nil { return nil, err } @@ -333,15 +400,15 @@ func resizeNodes(oldNodes []Node, disjointIPblocks []*common.IPBlock) (newNodes return newNodes, nil } -func (subnetConnectivity *SubnetConnectivityMap) actualAlignSrcOrDstGivenIPBlists(config *VPCConfig, +func (connectivityMap *GeneralConnectivityMap) actualAlignSrcOrDstGivenIPBlists(config *VPCConfig, disjointIPblocks []*common.IPBlock, resizeSrc bool) ( - alignedConnectivity SubnetConnectivityMap, err error) { + alignedConnectivity GeneralConnectivityMap, err error) { // goes over all sources of connections in connectivity // if src is external then for each IPBlock in disjointIPblocks copies dsts and connection type // otherwise just copies as is err = nil alignedConnectivity = map[VPCResourceIntf]map[VPCResourceIntf]*common.ConnectionSet{} - for src, endpointConns := range *subnetConnectivity { + for src, endpointConns := range *connectivityMap { for dst, conns := range endpointConns { if conns.IsEmpty() { continue @@ -421,9 +488,9 @@ func findNodeWithCidr(configNodes []Node, cidr string) Node { } // get a list of IPBlocks of the src and dst of the connections -func (subnetConnectivity SubnetConnectivityMap) getIPBlocksList() (ipbList []*common.IPBlock, +func (connectivityMap GeneralConnectivityMap) getIPBlocksList() (ipbList []*common.IPBlock, myErr error) { - for src, endpointConns := range subnetConnectivity { + for src, endpointConns := range connectivityMap { for dst, conns := range endpointConns { if conns.IsEmpty() { continue @@ -471,10 +538,10 @@ func externalNodeToIPBlock(external Node) (ipBlock *common.IPBlock, err error) { // src EndpointElem // dst EndpointElem // } -// func (subnetConnectivity SubnetConnectivityMap) getIntersectingConnections(other SubnetConnectivityMap) (areIntersecting string, +// func (connectivity GeneralConnectivityMap) getIntersectingConnections(other GeneralConnectivityMap) (areIntersecting string, // err error) { // err = nil -// for src, endpointConns := range subnetConnectivity { +// for src, endpointConns := range connectivity { // for dst, conns := range endpointConns { // if (!src.IsExternal() && !dst.IsExternal()) || conns.IsEmpty() { // continue // nothing to do here @@ -562,20 +629,20 @@ 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 cfgsDiff 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 //// this will requires some rewriting in the existing grouping functionality and the way it provides //// service to subnetsConnectivity and nodesConnectivity // -// func (subnetConnectivity *SubnetConnectivityMap) PrintConnectivity() { -// for src, endpointConns := range *subnetConnectivity { +// func (connectivity *GeneralConnectivityMap) PrintConnectivity() { +// for src, endpointConns := range *connectivity { // for dst, conns := range endpointConns { // if conns.IsEmpty() { // continue // } -// fmt.Printf("\t%v => %v %v\n", src.Name(), dst.Name(), conns.EnhancedString()) +// fmt.Printf("\t%v => %v %v\n", src.Name(), dst.Name(), conns.string()) // } // } // } diff --git a/pkg/vpcmodel/semanticDiff_test.go b/pkg/vpcmodel/semanticDiff_test.go new file mode 100644 index 000000000..75dd428ce --- /dev/null +++ b/pkg/vpcmodel/semanticDiff_test.go @@ -0,0 +1,289 @@ +package vpcmodel + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/np-guard/vpc-network-config-analyzer/pkg/common" +) + +// simple diff: +// cfg1 has subnet0, subnet1, subnet2, subnet3, subnet4 +// subnet0 -> subnet1 +// subnet1 -> subnet2 +// subnet3 -> subnet1 +// subnet2 -> subnet3 +// subnet3 -> subnet2 +// subnet3 -> subnet4 not all connections +// cfg2 has subnet2, subnet3, subnet4 +// subnet3 -> subnet2 +// subnet3 -> subnet4 + +// expected diff cfg1 connMissingOrChanged cfg2: +// cfg1 connMissingOrChanged cfg2 +// subnet0 -> subnet1 missing src and dst +// subnet1 -> subnet2 missing src +// subnet3 -> subnet1 missing dst +// subnet2 -> subnet3 missing connection +// +// cfg2 connMissingOrChanged cfg1 +// subnet1 connMissingOrChanged subnet2: +// subnet3 -> subnet4 different connection + +func configSimpleSubnetDiff() (subnetConfigConn1, subnetConfigConn2 *configConnectivity) { + cfg1 := &VPCConfig{Nodes: []Node{}, NodeSets: []NodeSet{}} + cfg1.Nodes = append(cfg1.Nodes, + &mockNetIntf{cidr: "10.0.20.5/32", name: "vsi1-1"}, + &mockNetIntf{cidr: "10.3.20.6/32", name: "vsi1-2"}, + &mockNetIntf{cidr: "10.7.20.7/32", name: "vsi1-3"}) + + cfg1.NodeSets = append(cfg1.NodeSets, &mockSubnet{"10.0.20.0/22", "subnet0", []Node{cfg1.Nodes[0]}}, + &mockSubnet{"10.1.20.0/22", "subnet1", []Node{cfg1.Nodes[0]}}, + &mockSubnet{"10.2.20.0/22", "subnet2", []Node{cfg1.Nodes[1]}}) + cfg1.NodeSets = append(cfg1.NodeSets, &mockSubnet{"10.3.20.0/22", "subnet3", []Node{cfg1.Nodes[2]}}, + &mockSubnet{"10.4.20.0/22", "subnet4", []Node{cfg1.Nodes[2]}}) + + cfg2 := &VPCConfig{Nodes: []Node{}, NodeSets: []NodeSet{}} + cfg2.Nodes = append(cfg2.Nodes, + &mockNetIntf{cidr: "10.3.20.5/32", name: "vsi2-1"}, + &mockNetIntf{cidr: "10.7.20.6/32", name: "vsi2-2"}, + &mockNetIntf{cidr: "10.9.20.7/32", name: "vsi2-3"}, + &mockNetIntf{cidr: "11.4.20.6/32", name: "vsi2-4"}) + cfg2.NodeSets = append(cfg2.NodeSets, &mockSubnet{"10.2.20.0/22", "subnet2", []Node{cfg2.Nodes[0]}}, + &mockSubnet{"10.3.20.0/22", "subnet3", []Node{cfg2.Nodes[1]}}, + &mockSubnet{"10.4.20.0/22", "subnet4", []Node{cfg2.Nodes[2]}}, + &mockSubnet{"11.4.20.0/22", "subnet5", []Node{cfg2.Nodes[3]}}) + + connectionTCP := common.NewConnectionSet(false) + connectionTCP.AddTCPorUDPConn(common.ProtocolTCP, 10, 100, 443, 443) + subnetConnMap1 := &VPCsubnetConnectivity{AllowedConnsCombined: GeneralConnectivityMap{}} + subnetConnMap1.AllowedConnsCombined.updateAllowedConnsMap(cfg1.NodeSets[0], cfg1.NodeSets[1], common.NewConnectionSet(true)) + subnetConnMap1.AllowedConnsCombined.updateAllowedConnsMap(cfg1.NodeSets[1], cfg1.NodeSets[2], common.NewConnectionSet(true)) + subnetConnMap1.AllowedConnsCombined.updateAllowedConnsMap(cfg1.NodeSets[3], cfg1.NodeSets[1], common.NewConnectionSet(true)) + subnetConnMap1.AllowedConnsCombined.updateAllowedConnsMap(cfg1.NodeSets[2], cfg1.NodeSets[3], common.NewConnectionSet(true)) + subnetConnMap1.AllowedConnsCombined.updateAllowedConnsMap(cfg1.NodeSets[3], cfg1.NodeSets[2], common.NewConnectionSet(true)) + subnetConnMap1.AllowedConnsCombined.updateAllowedConnsMap(cfg1.NodeSets[3], cfg1.NodeSets[4], connectionTCP) + + subnetConnMap2 := &VPCsubnetConnectivity{AllowedConnsCombined: GeneralConnectivityMap{}} + subnetConnMap2.AllowedConnsCombined.updateAllowedConnsMap(cfg2.NodeSets[1], cfg2.NodeSets[0], common.NewConnectionSet(true)) + subnetConnMap2.AllowedConnsCombined.updateAllowedConnsMap(cfg2.NodeSets[1], cfg2.NodeSets[2], common.NewConnectionSet(true)) + subnetConnMap2.AllowedConnsCombined.updateAllowedConnsMap(cfg2.NodeSets[2], cfg2.NodeSets[3], common.NewConnectionSet(true)) + + subnetConfigConn1 = &configConnectivity{cfg1, subnetConnMap1.AllowedConnsCombined} + subnetConfigConn2 = &configConnectivity{cfg2, subnetConnMap2.AllowedConnsCombined} + + return subnetConfigConn1, subnetConfigConn2 +} + +func TestSimpleSubnetDiff(t *testing.T) { + subnetConfigConn1, subnetConfigConn2 := configSimpleSubnetDiff() + subnet1Subtract2, err := subnetConfigConn1.connMissingOrChanged(subnetConfigConn2, Subnets, true) + if err != nil { + fmt.Println("error:", err.Error()) + } + subnet1Subtract2Str := subnet1Subtract2.string(Subnets, true) + fmt.Printf("cfg1ConnRemovedFrom2:\n%v\n", subnet1Subtract2Str) + require.Equal(t, err, nil) + newLines := strings.Count(subnet1Subtract2Str, "\n") + require.Equal(t, 5, newLines) + require.Contains(t, subnet1Subtract2Str, "diff-type: removed, source: subnet0, destination: subnet1, "+ + "config1: All Connections, config2: No connection, subnets-diff-info: subnet0 and subnet1 removed") + require.Contains(t, subnet1Subtract2Str, "diff-type: removed, source: subnet1, destination: subnet2, "+ + "config1: All Connections, config2: No connection, subnets-diff-info: subnet1 removed") + require.Contains(t, subnet1Subtract2Str, "diff-type: removed, source: subnet2, destination: subnet3, "+ + "config1: All Connections, config2: No connection, subnets-diff-info:") + require.Contains(t, subnet1Subtract2Str, "diff-type: removed, source: subnet3, destination: subnet1, "+ + "config1: All Connections, config2: No connection, subnets-diff-info: subnet1 removed") + require.Contains(t, subnet1Subtract2Str, "diff-type: changed, source: subnet3, destination: subnet4, "+ + "config1: protocol: TCP src-ports: 10-100 dst-ports: 443, config2: All Connections, subnets-diff-info:") + + cfg2Subtract1, err := subnetConfigConn2.connMissingOrChanged(subnetConfigConn1, Subnets, false) + if err != nil { + fmt.Println("error:", err.Error()) + } + require.Equal(t, err, nil) + subnet2Subtract1Str := cfg2Subtract1.string(Subnets, false) + fmt.Printf("cfg2Subtract1:\n%v", subnet2Subtract1Str) + require.Equal(t, subnet2Subtract1Str, "diff-type: added, source: subnet4, destination: subnet5, config1: "+ + "No connection, config2: All Connections, subnets-diff-info: subnet5 added\n") +} + +func configSimpleIPAndSubnetDiff() (subnetConfigConn1, subnetConfigConn2 *configConnectivity) { + cfg1 := &VPCConfig{Nodes: []Node{}, NodeSets: []NodeSet{}} + cfg1.NodeSets = append(cfg1.NodeSets, &mockSubnet{"10.1.20.0/22", "subnet1", nil}, + &mockSubnet{"10.2.20.0/22", "subnet2", nil}) + cfg1.Nodes = append(cfg1.Nodes, + &mockNetIntf{cidr: "1.2.3.0/30", name: "public1-1", isPublic: true}, + &mockNetIntf{cidr: "250.2.4.0/24", name: "public1-2", isPublic: true}, + &mockNetIntf{cidr: "200.2.4.0/24", name: "public1-3", isPublic: true}) + + cfg2 := &VPCConfig{Nodes: []Node{}, NodeSets: []NodeSet{}} + cfg2.NodeSets = append(cfg2.NodeSets, &mockSubnet{"10.1.20.0/22", "subnet1", nil}, + &mockSubnet{"10.2.20.0/22", "subnet2", nil}) + cfg2.Nodes = append(cfg2.Nodes, + &mockNetIntf{cidr: "1.2.3.0/26", name: "public2-1", isPublic: true}, + &mockNetIntf{cidr: "250.2.4.0/30", name: "public2-2", isPublic: true}, + &mockNetIntf{cidr: "200.2.4.0/24", name: "public1-3", isPublic: true}) + + // cfg1 cfg2 + // and are comparable + // and are comparable + // and are comparable + // and are comparable + subnetConnMap1 := &VPCsubnetConnectivity{AllowedConnsCombined: GeneralConnectivityMap{}} + subnetConnMap1.AllowedConnsCombined.updateAllowedConnsMap(cfg1.Nodes[0], cfg1.NodeSets[0], common.NewConnectionSet(true)) + subnetConnMap1.AllowedConnsCombined.updateAllowedConnsMap(cfg1.Nodes[0], cfg1.NodeSets[1], common.NewConnectionSet(true)) + subnetConnMap1.AllowedConnsCombined.updateAllowedConnsMap(cfg1.Nodes[1], cfg1.NodeSets[1], common.NewConnectionSet(true)) + subnetConnMap1.AllowedConnsCombined.updateAllowedConnsMap(cfg1.NodeSets[1], cfg1.Nodes[0], common.NewConnectionSet(true)) + subnetConnMap1.AllowedConnsCombined.updateAllowedConnsMap(cfg1.NodeSets[1], cfg1.Nodes[2], common.NewConnectionSet(true)) + + subnetConnMap2 := &VPCsubnetConnectivity{AllowedConnsCombined: GeneralConnectivityMap{}} + subnetConnMap2.AllowedConnsCombined.updateAllowedConnsMap(cfg2.Nodes[0], cfg2.NodeSets[0], common.NewConnectionSet(true)) + subnetConnMap2.AllowedConnsCombined.updateAllowedConnsMap(cfg2.Nodes[0], cfg2.NodeSets[1], common.NewConnectionSet(true)) + subnetConnMap2.AllowedConnsCombined.updateAllowedConnsMap(cfg2.Nodes[1], cfg2.NodeSets[1], common.NewConnectionSet(true)) + subnetConnMap2.AllowedConnsCombined.updateAllowedConnsMap(cfg2.NodeSets[1], cfg2.Nodes[0], common.NewConnectionSet(true)) + connectionTCP := common.NewConnectionSet(false) + connectionTCP.AddTCPorUDPConn(common.ProtocolTCP, 0, 1000, 0, 443) + subnetConnMap2.AllowedConnsCombined.updateAllowedConnsMap(cfg2.NodeSets[1], cfg2.Nodes[2], connectionTCP) + + subnetConfigConn1 = &configConnectivity{cfg1, subnetConnMap1.AllowedConnsCombined} + subnetConfigConn2 = &configConnectivity{cfg2, subnetConnMap2.AllowedConnsCombined} + + return subnetConfigConn1, subnetConfigConn2 +} + +func TestSimpleIPAndSubnetDiff(t *testing.T) { + cfgConn1, cfgConn2 := configSimpleIPAndSubnetDiff() + alignedCfgConn1, alignedCfgConn2, err := cfgConn1.getConnectivesWithSameIPBlocks(cfgConn2) + if err != nil { + fmt.Printf("err: %v\n", err.Error()) + require.Equal(t, err, nil) + return + } + + // verified bit by bit :-) + cfg1SubCfg2, err := alignedCfgConn1.connMissingOrChanged(alignedCfgConn2, Subnets, true) + if err != nil { + fmt.Println("error:", err.Error()) + } + require.Equal(t, err, nil) + cfg1SubtractCfg2Str := cfg1SubCfg2.string(Subnets, true) + fmt.Printf("cfg1SubCfg2:\n%v\n", cfg1SubtractCfg2Str) + newLines := strings.Count(cfg1SubtractCfg2Str, "\n") + require.Equal(t, 7, newLines) + require.Contains(t, cfg1SubtractCfg2Str, "diff-type: removed, source: Public Internet [250.2.4.128/25], destination: subnet2, "+ + "config1: All Connections, config2: No connection, subnets-diff-info:") + require.Contains(t, cfg1SubtractCfg2Str, "diff-type: removed, source: Public Internet [250.2.4.16/28], destination: subnet2, "+ + "config1: All Connections, config2: No connection, subnets-diff-info:") + require.Contains(t, cfg1SubtractCfg2Str, "diff-type: removed, source: Public Internet [250.2.4.32/27], destination: subnet2, "+ + "config1: All Connections, config2: No connection, subnets-diff-info:") + require.Contains(t, cfg1SubtractCfg2Str, "diff-type: removed, source: Public Internet [250.2.4.4/30], destination: subnet2, "+ + "config1: All Connections, config2: No connection, subnets-diff-info:") + require.Contains(t, cfg1SubtractCfg2Str, "diff-type: removed, source: Public Internet [250.2.4.64/26], destination: subnet2, "+ + "config1: All Connections, config2: No connection, subnets-diff-info:") + require.Contains(t, cfg1SubtractCfg2Str, "diff-type: removed, source: Public Internet [250.2.4.8/29], destination: subnet2, "+ + "config1: All Connections, config2: No connection, subnets-diff-info:") + require.Contains(t, cfg1SubtractCfg2Str, "diff-type: changed, source: subnet2, destination: Public Internet [200.2.4.0/24], "+ + "config1: All Connections, config2: protocol: TCP src-ports: 0-1000 dst-ports: 0-443, subnets-diff-info:") +} + +func configSimpleVsisDiff() (configConn1, configConn2 *configConnectivity) { + cfg1 := &VPCConfig{Nodes: []Node{}, NodeSets: []NodeSet{}} + cfg1.Nodes = append(cfg1.Nodes, + &mockNetIntf{name: "vsi0", isPublic: false, cidr: ""}, + &mockNetIntf{name: "vsi1", isPublic: false, cidr: ""}, + &mockNetIntf{name: "vsi2", isPublic: false, cidr: ""}, + &mockNetIntf{name: "vsi3", isPublic: false, cidr: ""}, + &mockNetIntf{cidr: "1.2.3.0/30", name: "public1-1", isPublic: true}) + + cfg1.NodeSets = append(cfg1.NodeSets, &mockSubnet{"10.0.20.0/22", "subnet0", []Node{cfg1.Nodes[0], cfg1.Nodes[1], + cfg1.Nodes[2], cfg1.Nodes[3]}}) + + cfg2 := &VPCConfig{Nodes: []Node{}, NodeSets: []NodeSet{}} + cfg2.Nodes = append(cfg2.Nodes, + &mockNetIntf{name: "vsi1", isPublic: false, cidr: ""}, + &mockNetIntf{name: "vsi2", isPublic: false, cidr: ""}, + &mockNetIntf{name: "vsi3", isPublic: false, cidr: ""}, + &mockNetIntf{name: "vsi4", isPublic: false, cidr: ""}, + &mockNetIntf{cidr: "1.2.3.0/26", name: "public2-1", isPublic: true}) + + cfg2.NodeSets = append(cfg2.NodeSets, &mockSubnet{"10.0.20.0/22", "subnet0", []Node{cfg2.Nodes[0], cfg2.Nodes[1], + cfg2.Nodes[2], cfg2.Nodes[3]}}) + + connectionTCP := common.NewConnectionSet(false) + connectionTCP.AddTCPorUDPConn(common.ProtocolTCP, 10, 100, 443, 443) + cfg1Conn := &VPCConnectivity{AllowedConnsCombined: NewNodesConnectionsMap()} + cfg1Conn.AllowedConnsCombined.updateAllowedConnsMap(cfg1.Nodes[0], cfg1.Nodes[1], common.NewConnectionSet(true)) + cfg1Conn.AllowedConnsCombined.updateAllowedConnsMap(cfg1.Nodes[1], cfg1.Nodes[2], common.NewConnectionSet(true)) + cfg1Conn.AllowedConnsCombined.updateAllowedConnsMap(cfg1.Nodes[1], cfg1.Nodes[3], common.NewConnectionSet(true)) + cfg1Conn.AllowedConnsCombined.updateAllowedConnsMap(cfg1.Nodes[2], cfg1.Nodes[3], connectionTCP) + cfg1Conn.AllowedConnsCombined.updateAllowedConnsMap(cfg1.Nodes[2], cfg1.Nodes[4], connectionTCP) + + cfg2Conn := &VPCConnectivity{AllowedConnsCombined: NewNodesConnectionsMap()} + // 1st connections is identical to these in cfg1; the 2nd one differs in the conn type, the 3rd one has a dst that + // does not exist in cfg1 + cfg2Conn.AllowedConnsCombined.updateAllowedConnsMap(cfg2.Nodes[0], cfg2.Nodes[1], common.NewConnectionSet(true)) + cfg2Conn.AllowedConnsCombined.updateAllowedConnsMap(cfg2.Nodes[1], cfg2.Nodes[2], common.NewConnectionSet(true)) + cfg2Conn.AllowedConnsCombined.updateAllowedConnsMap(cfg2.Nodes[2], cfg2.Nodes[3], common.NewConnectionSet(true)) + cfg2Conn.AllowedConnsCombined.updateAllowedConnsMap(cfg2.Nodes[1], cfg2.Nodes[4], common.NewConnectionSet(true)) + + cfg1ConnGeneral := cfg1Conn.AllowedConnsCombined.nodesConnectivityToGeneralConnectivity() + cfg2ConnGeneral := cfg2Conn.AllowedConnsCombined.nodesConnectivityToGeneralConnectivity() + + configConn1 = &configConnectivity{cfg1, cfg1ConnGeneral} + configConn2 = &configConnectivity{cfg2, cfg2ConnGeneral} + + fmt.Printf("cfg1:\n%v\n", cfg1Conn.AllowedConnsCombined.getCombinedConnsStr()) + fmt.Printf("cfg2:\n%v\n", cfg2Conn.AllowedConnsCombined.getCombinedConnsStr()) + + return configConn1, configConn2 +} + +func TestSimpleVsisDiff(t *testing.T) { + cfgConn1, cfgConn2 := configSimpleVsisDiff() + alignedCfgConn1, alignedCfgConn2, err := cfgConn1.getConnectivesWithSameIPBlocks(cfgConn2) + if err != nil { + fmt.Printf("err: %v\n", err.Error()) + require.Equal(t, err, nil) + return + } + + cfg1SubCfg2, err := alignedCfgConn1.connMissingOrChanged(alignedCfgConn2, Vsis, true) + if err != nil { + fmt.Println("error:", err.Error()) + } + require.Equal(t, err, nil) + cfg1SubCfg2Str := cfg1SubCfg2.string(Vsis, true) + fmt.Printf("cfg1SubCfg2Str:\n%v\n", cfg1SubCfg2Str) + newLines := strings.Count(cfg1SubCfg2Str, "\n") + require.Equal(t, 4, newLines) + require.Contains(t, cfg1SubCfg2Str, "diff-type: changed, source: vsi2, destination: vsi3, config1: "+ + "protocol: TCP src-ports: 10-100 dst-ports: 443, config2: All Connections, vsis-diff-info:") + require.Contains(t, cfg1SubCfg2Str, "diff-type: removed, source: vsi0, destination: vsi1, config1: "+ + "All Connections, config2: No connection, vsis-diff-info: vsi0 removed") + require.Contains(t, cfg1SubCfg2Str, "diff-type: removed, source: vsi1, destination: vsi3, config1: "+ + "All Connections, config2: No connection, vsis-diff-info:") + + cfg2SubCfg1, err := alignedCfgConn2.connMissingOrChanged(alignedCfgConn1, Vsis, false) + if err != nil { + fmt.Println("error:", err.Error()) + } + require.Equal(t, err, nil) + cfg2SubCfg1Str := cfg2SubCfg1.string(Vsis, true) + fmt.Printf("cfg2SubCfg1Str:\n%v\n", cfg2SubCfg1Str) + newLines = strings.Count(cfg2SubCfg1Str, "\n") + require.Equal(t, 5, newLines) + require.Contains(t, cfg2SubCfg1Str, "diff-type: removed, source: vsi2, "+ + "destination: Public Internet [1.2.3.16/28], config1: All Connections, config2: No connection, vsis-diff-info: \n") + require.Contains(t, cfg2SubCfg1Str, "diff-type: removed, source: vsi2, "+ + "destination: Public Internet [1.2.3.32/27], config1: All Connections, config2: No connection, vsis-diff-info: \n") + require.Contains(t, cfg2SubCfg1Str, "diff-type: removed, source: vsi2, destination: Public Internet [1.2.3.4/30], "+ + "config1: All Connections, config2: No connection, vsis-diff-info: \n") + require.Contains(t, cfg2SubCfg1Str, "diff-type: removed, source: vsi2, "+ + "destination: Public Internet [1.2.3.8/29], config1: All Connections, config2: No connection, vsis-diff-info: \n") + require.Contains(t, cfg2SubCfg1Str, "diff-type: removed, source: vsi3, destination: vsi4, config1: "+ + "All Connections, config2: No connection, vsis-diff-info: vsi4 removed\n") +} diff --git a/pkg/vpcmodel/subnetsConnectivity.go b/pkg/vpcmodel/subnetsConnectivity.go index 1862cdeb8..34490a238 100644 --- a/pkg/vpcmodel/subnetsConnectivity.go +++ b/pkg/vpcmodel/subnetsConnectivity.go @@ -7,14 +7,12 @@ import ( "fmt" ) -type SubnetConnectivityMap map[VPCResourceIntf]map[VPCResourceIntf]*common.ConnectionSet - // VPCsubnetConnectivity captures allowed connectivity for subnets, considering nacl and pgw resources type VPCsubnetConnectivity struct { // computed for each node (subnet), by iterating its ConnectivityResult for all relevant VPC resources that capture it AllowedConns map[VPCResourceIntf]*ConfigBasedConnectivityResults // combined connectivity - considering both ingress and egress per connection - AllowedConnsCombined SubnetConnectivityMap + AllowedConnsCombined GeneralConnectivityMap VPCConfig *VPCConfig // grouped connectivity result GroupedConnectivity *GroupConnLines @@ -26,10 +24,6 @@ const ( errUnexpectedTypePeerNode = "unexpected type for peerNode in computeAllowedConnsCombined" ) -func NewSubnetConnectivityMap() SubnetConnectivityMap { - return SubnetConnectivityMap{} -} - func subnetConnLine(subnet string, conn *common.ConnectionSet) string { return fmt.Sprintf("%s : %s\n", subnet, conn.String()) } @@ -315,9 +309,9 @@ func (c *VPCConfig) GetConnectivityOutputPerEachSubnetSeparately() string { return "" } -func (subnetConnectivity SubnetConnectivityMap) updateAllowedSubnetConnsMap(src, dst VPCResourceIntf, conn *common.ConnectionSet) { - if _, ok := subnetConnectivity[src]; !ok { - subnetConnectivity[src] = map[VPCResourceIntf]*common.ConnectionSet{} +func (connectivityMap GeneralConnectivityMap) updateAllowedConnsMap(src, dst VPCResourceIntf, conn *common.ConnectionSet) { + if _, ok := connectivityMap[src]; !ok { + connectivityMap[src] = map[VPCResourceIntf]*common.ConnectionSet{} } - subnetConnectivity[src][dst] = conn + connectivityMap[src][dst] = conn } diff --git a/pkg/vpcmodel/textOutput.go b/pkg/vpcmodel/textOutput.go index 75527e083..d640d0686 100644 --- a/pkg/vpcmodel/textOutput.go +++ b/pkg/vpcmodel/textOutput.go @@ -18,7 +18,7 @@ func headerOfAnalyzedVPC(vpcName, vpc2Name string) string { func (t *TextOutputFormatter) WriteOutput(c1, c2 *VPCConfig, conn *VPCConnectivity, subnetsConn *VPCsubnetConnectivity, - subnetsDiff *DiffBetweenSubnets, + cfgsDiff *diffBetweenCfgs, outFile string, grouping bool, uc OutputUseCase) (*VPCAnalysisOutput, error) { @@ -36,8 +36,8 @@ func (t *TextOutputFormatter) WriteOutput(c1, c2 *VPCConfig, out += subnetsConn.String() case SingleSubnet: out += c1.GetConnectivityOutputPerEachSubnetSeparately() - case AllSubnetsDiff: - out += subnetsDiff.String() + case SubnetsDiff, EndpointsDiff: + out += cfgsDiff.String() } // write output to file and return the output string _, err := WriteToFile(out, outFile) diff --git a/pkg/vpcmodel/vpcConnectivity.go b/pkg/vpcmodel/vpcConnectivity.go index a2f908569..cbddc9d2c 100644 --- a/pkg/vpcmodel/vpcConnectivity.go +++ b/pkg/vpcmodel/vpcConnectivity.go @@ -4,6 +4,8 @@ import ( "github.com/np-guard/vpc-network-config-analyzer/pkg/common" ) +type GeneralConnectivityMap map[VPCResourceIntf]map[VPCResourceIntf]*common.ConnectionSet + // 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)