diff --git a/pkg/drawio/iconTreeNode.go b/pkg/drawio/iconTreeNode.go index f85d75e19..d573dc986 100644 --- a/pkg/drawio/iconTreeNode.go +++ b/pkg/drawio/iconTreeNode.go @@ -76,11 +76,11 @@ func NewNITreeNode(parent SquareTreeNodeInterface, sg *SGTreeNode, name string) ni := NITreeNode{abstractIconTreeNode: newAbstractIconTreeNode(parent, name)} parent.addIconTreeNode(&ni) if sg != nil { - ni.setSG(sg) + ni.SetSG(sg) } return &ni } -func (tn *NITreeNode) setSG(sg *SGTreeNode) { +func (tn *NITreeNode) SetSG(sg *SGTreeNode) { sg.addIconTreeNode(tn) tn.sg = sg } diff --git a/pkg/ibmvpc/ibmDrawioGenerator.go b/pkg/ibmvpc/ibmDrawioGenerator.go new file mode 100644 index 000000000..4eac2e664 --- /dev/null +++ b/pkg/ibmvpc/ibmDrawioGenerator.go @@ -0,0 +1,81 @@ +package ibmvpc + +import ( + "github.com/np-guard/vpc-network-config-analyzer/pkg/drawio" + "github.com/np-guard/vpc-network-config-analyzer/pkg/vpcmodel" +) + +// implementations of the GenerateDrawioTreeNode() for resource defined in ibmvpc: +func (v *VPC) GenerateDrawioTreeNode(gen *vpcmodel.DrawioGenerator) drawio.TreeNodeInterface { + return drawio.NewVpcTreeNode(gen.Cloud(), v.Name()) +} + +func (z *Zone) GenerateDrawioTreeNode(gen *vpcmodel.DrawioGenerator) drawio.TreeNodeInterface { + return drawio.NewZoneTreeNode(gen.TreeNode(z.VPC()).(*drawio.VpcTreeNode), z.name) +} + +func (s *Subnet) GenerateDrawioTreeNode(gen *vpcmodel.DrawioGenerator) drawio.TreeNodeInterface { + // todo - how to handle this error: + zone, _ := s.Zone() + zoneTn := gen.TreeNode(zone).(*drawio.ZoneTreeNode) + return drawio.NewSubnetTreeNode(zoneTn, s.Name(), s.cidr, "") +} + +func (sgl *SecurityGroupLayer) GenerateDrawioTreeNode(gen *vpcmodel.DrawioGenerator) drawio.TreeNodeInterface { + tn := drawio.NewSGTreeNode(gen.TreeNode(sgl.VPC()).(*drawio.VpcTreeNode), sgl.Name()) + for _, sg := range sgl.sgList { + for _, ni := range sg.members { + gen.TreeNode(ni).(*drawio.NITreeNode).SetSG(tn) + } + } + return tn +} + +func (nl *NaclLayer) GenerateDrawioTreeNode(gen *vpcmodel.DrawioGenerator) drawio.TreeNodeInterface { + for _, acl := range nl.naclList { + for _, sn := range acl.subnets { + gen.TreeNode(sn).(*drawio.SubnetTreeNode).SetACL(acl.Name()) + } + } + return nil +} + +func (ni *NetworkInterface) GenerateDrawioTreeNode(gen *vpcmodel.DrawioGenerator) drawio.TreeNodeInterface { + return drawio.NewNITreeNode( + gen.TreeNode(ni.subnet).(drawio.SquareTreeNodeInterface), + nil, ni.Name()) +} + +func (n *IKSNode) GenerateDrawioTreeNode(gen *vpcmodel.DrawioGenerator) drawio.TreeNodeInterface { + // todo - what is this? should we handle it? can we ignore it? + return nil +} + +func (v *Vsi) GenerateDrawioTreeNode(gen *vpcmodel.DrawioGenerator) drawio.TreeNodeInterface { + if len(v.Nodes()) == 0 { + return nil + } + vsiNIs := []drawio.TreeNodeInterface{} + for _, ni := range v.Nodes() { + vsiNIs = append(vsiNIs, gen.TreeNode(ni)) + } + // todo - how to handle this error: + zone, _ := v.Zone() + zoneTn := gen.TreeNode(zone).(*drawio.ZoneTreeNode) + drawio.GroupNIsWithVSI(zoneTn, v.Name(), vsiNIs) + return nil +} + +func (pgw *PublicGateway) GenerateDrawioTreeNode(gen *vpcmodel.DrawioGenerator) drawio.TreeNodeInterface { + // todo - how to handle this error: + zone, _ := pgw.Zone() + zoneTn := gen.TreeNode(zone).(*drawio.ZoneTreeNode) + return drawio.NewGatewayTreeNode(zoneTn, pgw.Name()) +} + +func (fip *FloatingIP) GenerateDrawioTreeNode(gen *vpcmodel.DrawioGenerator) drawio.TreeNodeInterface { + // todo - what if r.Src() is not at size of one? + nitn := gen.TreeNode(fip.Src()[0]).(*drawio.NITreeNode) + nitn.SetFIP(fip.Name()) + return nitn +} diff --git a/pkg/ibmvpc/parser.go b/pkg/ibmvpc/parser.go index 10b34e413..a8a33d886 100644 --- a/pkg/ibmvpc/parser.go +++ b/pkg/ibmvpc/parser.go @@ -661,6 +661,7 @@ func NewCloudConfig(rc *ResourcesContainer) (*vpcmodel.CloudConfig, error) { FilterResources: []vpcmodel.FilterTrafficResource{}, RoutingResources: []vpcmodel.RoutingResource{}, NameToResource: map[string]vpcmodel.VPCResourceIntf{}, + CloudName: "IBM Cloud", } var err error diff --git a/pkg/vpcmodel/abstractVPC.go b/pkg/vpcmodel/abstractVPC.go index 8395cec3e..0be6d0bd1 100644 --- a/pkg/vpcmodel/abstractVPC.go +++ b/pkg/vpcmodel/abstractVPC.go @@ -14,6 +14,7 @@ type VPCResourceIntf interface { // TODO: remove Details and DetailsMap from this interface Details() []string DetailsMap() []map[string]string + DrawioResourceIntf } // VPCResource implements VPCResourceIntf diff --git a/pkg/vpcmodel/cloudConfig.go b/pkg/vpcmodel/cloudConfig.go index c72bd8409..451e77750 100644 --- a/pkg/vpcmodel/cloudConfig.go +++ b/pkg/vpcmodel/cloudConfig.go @@ -10,6 +10,7 @@ type CloudConfig struct { FilterResources []FilterTrafficResource RoutingResources []RoutingResource NameToResource map[string]VPCResourceIntf + CloudName string } // TODO: consider add this mapping to CloudConfig diff --git a/pkg/vpcmodel/drawioGenerator.go b/pkg/vpcmodel/drawioGenerator.go new file mode 100644 index 000000000..6e3a53f9f --- /dev/null +++ b/pkg/vpcmodel/drawioGenerator.go @@ -0,0 +1,56 @@ +package vpcmodel + +import ( + "github.com/np-guard/vpc-network-config-analyzer/pkg/drawio" +) + +// DrawioResourceIntf is the interface of all the resources that are converted to a drawio treeNodes +type DrawioResourceIntf interface { + GenerateDrawioTreeNode(gen *DrawioGenerator) drawio.TreeNodeInterface +} + +// DrawioGenerator is the struct that generate the drawio tree. +// its main interface is: +// 1. TreeNode() - generate and returns the drawio tree node of a resource +// 2. the constructor - generate the treeNodes that does not represent a specific resource +// (the constructor creates the publicNetwork tree node, and the Cloud TreeNode) +// the rest of the interface i getters: +// Network(), PublicNetwork(), Cloud() +// returns the tree nodes which are created at the constructor +// please notice: +// creating the cloud treeNode is vendor specific (IBM, aws...). +// currently, the input that distinguish between the vendors is the cloudName, which is provided to NewDrawioGenerator() as parameter. +// we might later give as parameters more information to create the cloud, or create the cloud at the specific pkg. +type DrawioGenerator struct { + network *drawio.NetworkTreeNode + publicNetwork *drawio.PublicNetworkTreeNode + cloud *drawio.CloudTreeNode + treeNodes map[DrawioResourceIntf]drawio.TreeNodeInterface +} + +func NewDrawioGenerator(cloudName string) *DrawioGenerator { + // creates the top of the tree node - treeNodes that does not represent a specific resource. + gen := &DrawioGenerator{} + gen.network = drawio.NewNetworkTreeNode() + gen.publicNetwork = drawio.NewPublicNetworkTreeNode(gen.network) + gen.cloud = drawio.NewCloudTreeNode(gen.network, cloudName) + gen.treeNodes = map[DrawioResourceIntf]drawio.TreeNodeInterface{} + return gen +} +func (gen *DrawioGenerator) Network() *drawio.NetworkTreeNode { return gen.network } +func (gen *DrawioGenerator) PublicNetwork() *drawio.PublicNetworkTreeNode { return gen.publicNetwork } +func (gen *DrawioGenerator) Cloud() *drawio.CloudTreeNode { return gen.cloud } + +func (gen *DrawioGenerator) TreeNode(res DrawioResourceIntf) drawio.TreeNodeInterface { + if gen.treeNodes[res] == nil { + gen.treeNodes[res] = res.GenerateDrawioTreeNode(gen) + } + return gen.treeNodes[res] +} + +// //////////////////////////////////////////////////////////////////////////////////////////////////////////// +// implementations of the GenerateDrawioTreeNode() for resource defined in vpcmodel: +// (currently only ExternalNetwork, will add the grouping resource later) +func (exn *ExternalNetwork) GenerateDrawioTreeNode(gen *DrawioGenerator) drawio.TreeNodeInterface { + return drawio.NewInternetTreeNode(gen.PublicNetwork(), exn.CidrStr) +} diff --git a/pkg/vpcmodel/drawioOutput.go b/pkg/vpcmodel/drawioOutput.go index 8d9d1d96f..195a36b32 100644 --- a/pkg/vpcmodel/drawioOutput.go +++ b/pkg/vpcmodel/drawioOutput.go @@ -2,18 +2,10 @@ package vpcmodel import ( "errors" - "strings" "github.com/np-guard/vpc-network-config-analyzer/pkg/drawio" ) -const ( - commaSeparator = "," - cidrAttr = "cidr" - nameAttr = "name" - nwInterface = "NetworkInterface" -) - type Edge struct { src Node dst Node @@ -24,42 +16,24 @@ type Edge struct { // It build the drawio tree out of the CloudConfig and VPCConnectivity, and output it to a drawio file // the steps of creating the drawio tree: // 1. collect all the connectivity edges to a map of (src,dst,label) -> isDirected. also mark the nodes that has connections -// 2. create the treeNodes of the squares out of the cConfig.NodeSets - network, VPCs zones and subnets -// (also zones are created on demand from the zone names in the nodeSet details) -// 3. from the cConfig.filters, create treeNodes of SGs, and give ACL to subnets -// 4. create the icons Tree nodes out of the cConfig.Nodes -// 5. create the VSIs tree nodes from cConfig.NodeSets -// 6. create the routers from cConfig.routers -// 7. create the edges from the map we created in stage (1). also also set the routers to the edges +// 2. create the treeNodes of the NodeSets, filters. routers and nodes +// 3. create the edges from the map we created in stage (1). also also set the routers to the edges type DrawioOutputFormatter struct { - cConfig *CloudConfig - conn *VPCConnectivity - network *drawio.NetworkTreeNode - publicNetwork *drawio.PublicNetworkTreeNode - vpc *drawio.VpcTreeNode - zoneNameToZonesTreeNodes map[string]*drawio.ZoneTreeNode - uidToSubnetsTreeNodes map[string]*drawio.SubnetTreeNode - cidrToSubnetsTreeNodes map[string]*drawio.SubnetTreeNode - allIconsTreeNodes map[VPCResourceIntf]drawio.IconTreeNodeInterface - isExternalIcon map[drawio.IconTreeNodeInterface]bool - connectedNodes map[VPCResourceIntf]bool - routers map[drawio.IconTreeNodeInterface]drawio.IconTreeNodeInterface - sgMembers map[string]*drawio.SGTreeNode - isEdgeDirected map[Edge]bool + cConfig *CloudConfig + conn *VPCConnectivity + gen *DrawioGenerator + connectedNodes map[VPCResourceIntf]bool + routers map[drawio.TreeNodeInterface]drawio.IconTreeNodeInterface + isEdgeDirected map[Edge]bool } func (d *DrawioOutputFormatter) init(cConfig *CloudConfig, conn *VPCConnectivity) { d.cConfig = cConfig d.conn = conn - d.zoneNameToZonesTreeNodes = map[string]*drawio.ZoneTreeNode{} - d.uidToSubnetsTreeNodes = map[string]*drawio.SubnetTreeNode{} - d.cidrToSubnetsTreeNodes = map[string]*drawio.SubnetTreeNode{} - d.allIconsTreeNodes = map[VPCResourceIntf]drawio.IconTreeNodeInterface{} - d.isExternalIcon = map[drawio.IconTreeNodeInterface]bool{} + d.gen = NewDrawioGenerator(cConfig.CloudName) d.connectedNodes = map[VPCResourceIntf]bool{} - d.routers = map[drawio.IconTreeNodeInterface]drawio.IconTreeNodeInterface{} - d.sgMembers = map[string]*drawio.SGTreeNode{} + d.routers = map[drawio.TreeNodeInterface]drawio.IconTreeNodeInterface{} d.isEdgeDirected = map[Edge]bool{} } @@ -67,28 +41,26 @@ func (d *DrawioOutputFormatter) WriteOutputAllEndpoints(cConfig *CloudConfig, co string, error) { d.init(cConfig, conn) d.createDrawioTree() - err := drawio.CreateDrawioConnectivityMapFile(d.network, outFile) + err := drawio.CreateDrawioConnectivityMapFile(d.gen.Network(), outFile) return "", err } +// will be rewrite when implementing grouping +func (d *DrawioOutputFormatter) isExternal(i VPCResourceIntf) bool { + return i.Kind() == externalNetworkNodeKind +} + func (d *DrawioOutputFormatter) createDrawioTree() { if d.conn != nil { d.createEdgesMap() } + d.createNodeSets() - d.createFilters() d.createNodes() - d.createVSIs() + d.createFilters() d.createRouters() - d.createEdges() -} -func (d *DrawioOutputFormatter) getZoneTreeNode(resource VPCResourceIntf) *drawio.ZoneTreeNode { - zoneName := resource.ZoneName() - if _, ok := d.zoneNameToZonesTreeNodes[zoneName]; !ok { - d.zoneNameToZonesTreeNodes[zoneName] = drawio.NewZoneTreeNode(d.vpc, zoneName) - } - return d.zoneNameToZonesTreeNodes[zoneName] + d.createEdges() } func (d *DrawioOutputFormatter) createEdgesMap() { @@ -116,108 +88,44 @@ func (d *DrawioOutputFormatter) createEdgesMap() { } func (d *DrawioOutputFormatter) createNodeSets() { - d.network = drawio.NewNetworkTreeNode() - ibmCloud := drawio.NewCloudTreeNode(d.network, "IBM Cloud") - d.publicNetwork = drawio.NewPublicNetworkTreeNode(d.network) - // todo: support multi vnc - for _, ns := range d.cConfig.NodeSets { - details := ns.DetailsMap()[0] - if details[DetailsAttributeKind] == "VPC" { - d.vpc = drawio.NewVpcTreeNode(ibmCloud, details[DetailsAttributeName]) - } - } for _, ns := range d.cConfig.NodeSets { - details := ns.DetailsMap()[0] - if details[DetailsAttributeKind] == subnetKind { - subnet := drawio.NewSubnetTreeNode(d.getZoneTreeNode(ns), details[DetailsAttributeName], details[cidrAttr], "") - d.uidToSubnetsTreeNodes[details["uid"]] = subnet - d.cidrToSubnetsTreeNodes[details[cidrAttr]] = subnet - } - } -} - -func (d *DrawioOutputFormatter) createFilters() { - for _, fl := range d.cConfig.FilterResources { - for _, details := range fl.DetailsMap() { - if details[DetailsAttributeKind] == "SG" { - sgTn := drawio.NewSGTreeNode(d.vpc, details[nameAttr]) - for _, member := range strings.Split(details["members"], commaSeparator) { - d.sgMembers[member] = sgTn - } - } else if details[DetailsAttributeKind] == "NACL" { - for _, subnetCidr := range strings.Split(details["subnets"], commaSeparator) { - if subnetCidr != "" { - d.cidrToSubnetsTreeNodes[subnetCidr].SetACL(details[nameAttr]) - } - } - } - } + d.gen.TreeNode(ns) } } func (d *DrawioOutputFormatter) createNodes() { for _, n := range d.cConfig.Nodes { - details := n.DetailsMap()[0] - if details[DetailsAttributeKind] == nwInterface { - // todo: what is the name of NI - d.allIconsTreeNodes[n] = drawio.NewNITreeNode( - d.uidToSubnetsTreeNodes[details["subnetUID"]], - d.sgMembers[details["address"]], details[nameAttr]) - } else if details[DetailsAttributeKind] == externalNetworkNodeKind { - if d.connectedNodes[n] { - // todo - should we show it anyway? - d.allIconsTreeNodes[n] = drawio.NewInternetTreeNode(d.publicNetwork, details[DetailsAttributeCIDR]) - d.isExternalIcon[d.allIconsTreeNodes[n]] = true - } + if d.connectedNodes[n] || !d.isExternal(n) { + d.gen.TreeNode(n) } } } -func (d *DrawioOutputFormatter) createVSIs() { - for _, ns := range d.cConfig.NodeSets { - details := ns.DetailsMap()[0] - if details[DetailsAttributeKind] == "VSI" { - if len(ns.Nodes()) == 0 { - continue - } else { - vsiNIs := []drawio.TreeNodeInterface{} - for _, ni := range ns.Nodes() { - vsiNIs = append(vsiNIs, d.allIconsTreeNodes[ni]) - } - drawio.GroupNIsWithVSI(d.getZoneTreeNode(ns), ns.Name(), vsiNIs) - } - } +func (d *DrawioOutputFormatter) createFilters() { + for _, fl := range d.cConfig.FilterResources { + d.gen.TreeNode(fl) } } func (d *DrawioOutputFormatter) createRouters() { for _, r := range d.cConfig.RoutingResources { - dm := r.DetailsMap()[0] - if dm[DetailsAttributeKind] == "PublicGateway" { - pgwTn := drawio.NewGatewayTreeNode(d.getZoneTreeNode(r), dm[DetailsAttributeName]) - d.allIconsTreeNodes[r] = pgwTn - for _, ni := range r.Src() { - d.routers[d.allIconsTreeNodes[ni]] = pgwTn - } - } - if dm[DetailsAttributeKind] == "FloatingIP" { - // todo - what if r.Src() is not at size of one? - nitn := d.allIconsTreeNodes[r.Src()[0]].(*drawio.NITreeNode) - nitn.SetFIP(r.Name()) - d.routers[nitn] = nitn + rTn := d.gen.TreeNode(r) + + for _, ni := range r.Src() { + d.routers[d.gen.TreeNode(ni)] = rTn.(drawio.IconTreeNodeInterface) } } } func (d *DrawioOutputFormatter) createEdges() { for edge, directed := range d.isEdgeDirected { - srcTn := d.allIconsTreeNodes[edge.src] - dstTn := d.allIconsTreeNodes[edge.dst] - cn := drawio.NewConnectivityLineTreeNode(d.network, srcTn, dstTn, directed, edge.label) - if d.routers[srcTn] != nil && d.isExternalIcon[dstTn] { + srcTn := d.gen.TreeNode(edge.src).(drawio.IconTreeNodeInterface) + dstTn := d.gen.TreeNode(edge.dst).(drawio.IconTreeNodeInterface) + cn := drawio.NewConnectivityLineTreeNode(d.gen.Network(), srcTn, dstTn, directed, edge.label) + if d.routers[srcTn] != nil && d.isExternal(edge.dst) { cn.SetRouter(d.routers[srcTn], false) } - if d.routers[dstTn] != nil && d.isExternalIcon[srcTn] { + if d.routers[dstTn] != nil && d.isExternal(edge.src) { cn.SetRouter(d.routers[dstTn], true) } } diff --git a/pkg/vpcmodel/grouping.go b/pkg/vpcmodel/grouping.go index b9f7f93a2..cb397e989 100644 --- a/pkg/vpcmodel/grouping.go +++ b/pkg/vpcmodel/grouping.go @@ -7,6 +7,8 @@ import ( "github.com/np-guard/vpc-network-config-analyzer/pkg/common" ) +const commaSeparator = "," + // for each line here can group list of external nodes to cidrs list as of one element type groupingConnections map[EndpointElem]map[string][]Node @@ -332,7 +334,7 @@ func listEndpointElemStr(eps []EndpointElem, fn func(ep EndpointElem) string) st endpointsStrings[i] = fn(ep) } sort.Strings(endpointsStrings) - return strings.Join(endpointsStrings, ",") + return strings.Join(endpointsStrings, commaSeparator) } func (g *groupedExternalNodes) String() string { diff --git a/pkg/vpcmodel/grouping_test.go b/pkg/vpcmodel/grouping_test.go index 36c689b25..e934d2f53 100644 --- a/pkg/vpcmodel/grouping_test.go +++ b/pkg/vpcmodel/grouping_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/np-guard/vpc-network-config-analyzer/pkg/common" + "github.com/np-guard/vpc-network-config-analyzer/pkg/drawio" ) type mockNetIntf struct { @@ -31,7 +32,7 @@ func (m *mockNetIntf) DetailsMap() []map[string]string { return nil } func (m *mockNetIntf) Kind() string { - return nwInterface + return "NetworkInterface" } func (m *mockNetIntf) UID() string { return "" @@ -42,6 +43,9 @@ func (m *mockNetIntf) Name() string { func (m *mockNetIntf) ZoneName() string { return "" } +func (m *mockNetIntf) GenerateDrawioTreeNode(gen *DrawioGenerator) drawio.TreeNodeInterface { + return nil +} type mockSubnet struct { cidr string @@ -73,6 +77,9 @@ func (m *mockSubnet) Kind() string { func (m *mockSubnet) ZoneName() string { return "" } +func (m *mockSubnet) GenerateDrawioTreeNode(gen *DrawioGenerator) drawio.TreeNodeInterface { + return nil +} func newCloudConfigTest1() (*CloudConfig, *VPCConnectivity) { res := &CloudConfig{Nodes: []Node{}}