diff --git a/cmd/collect/subcmds.go b/cmd/collect/subcmds.go index 925ecf4..251bf4c 100644 --- a/cmd/collect/subcmds.go +++ b/cmd/collect/subcmds.go @@ -26,6 +26,8 @@ var ( regions []string resourceGroupID string outputFile string + + fabricatesOpts common.FabricateOptions ) func newRootCommand() *cobra.Command { @@ -39,8 +41,11 @@ func newRootCommand() *cobra.Command { rootCmd.PersistentFlags().VarP(&provider, providerFlag, "p", "collect resources from an account in this cloud provider") _ = rootCmd.MarkPersistentFlagRequired(providerFlag) + rootCmd.PersistentFlags().StringVar(&outputFile, "out", "", "file path to store results") + rootCmd.AddCommand(newCollectCommand()) rootCmd.AddCommand(newGetRegionsCommand()) + rootCmd.AddCommand(newFabricateCommand()) rootCmd.SetHelpCommand(&cobra.Command{Hidden: true}) // disable help command. should use --help flag instead @@ -58,7 +63,6 @@ func newCollectCommand() *cobra.Command { collectCmd.Flags().StringArrayVarP(®ions, "region", "r", nil, "cloud region from which to collect resources") collectCmd.Flags().StringVar(&resourceGroupID, "resource-group", "", "resource group id or name from which to collect resources") - collectCmd.Flags().StringVar(&outputFile, "out", "", "file path to store results") return collectCmd } @@ -80,3 +84,22 @@ func newGetRegionsCommand() *cobra.Command { }, } } + +func newFabricateCommand() *cobra.Command { + fabricateCmd := &cobra.Command{ + Use: "fabricate", + Short: "Fabricate synthetic data", + Long: `Generates synthetic data with a given number of VPCs, Subnets, VSIs, ...`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + resources := factory.GetResourceContainer(provider, regions, "") + resources.Fabricate(&fabricatesOpts) + OutputResources(resources, outputFile) + return nil + }, + } + fabricateCmd.Flags().IntVar(&fabricatesOpts.NumVPCs, "num-vpcs", 1, "Number of VPCs to generate") + fabricateCmd.Flags().IntVar(&fabricatesOpts.SubnetsPerVPC, "subnets-per-vpc", 1, "Number of subnets to generate in each VPC") + + return fabricateCmd +} diff --git a/go.mod b/go.mod index 302ced4..6453552 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/IBM/vpc-go-sdk v0.54.0 github.com/aws/aws-sdk-go-v2/config v1.27.21 github.com/aws/aws-sdk-go-v2/service/ec2 v1.163.0 + github.com/np-guard/models v0.3.4 github.com/spf13/cobra v1.8.1 ) diff --git a/go.sum b/go.sum index 3663d4b..c755dd5 100644 --- a/go.sum +++ b/go.sum @@ -164,6 +164,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/np-guard/models v0.3.4 h1:HOhVi6wyGvo+KmYBnQ5Km5HYCF+/PQlDs1v7mL1v05g= +github.com/np-guard/models v0.3.4/go.mod h1:mqE2Irf8r+7HWh8fII0fWbWyQRMHGEo2SgSLN/6VKs8= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= diff --git a/pkg/aws/resources_container.go b/pkg/aws/resources_container.go index 1ae4d65..4e3decf 100644 --- a/pkg/aws/resources_container.go +++ b/pkg/aws/resources_container.go @@ -68,6 +68,9 @@ func (resources *ResourcesContainer) GetResources() common.ResourcesModel { return resources } +func (resources *ResourcesContainer) Fabricate(opts *common.FabricateOptions) { // TODO: implement +} + // CollectResourcesFromAPI uses AWS APIs to collect resource configuration information func (resources *ResourcesContainer) CollectResourcesFromAPI() error { //nolint:gocyclo // due to many API calls // Load the Shared AWS Configuration (~/.aws/config) diff --git a/pkg/common/resources_container_inf.go b/pkg/common/resources_container_inf.go index b2dbf42..2bd26e8 100644 --- a/pkg/common/resources_container_inf.go +++ b/pkg/common/resources_container_inf.go @@ -13,6 +13,7 @@ type ResourcesContainerInf interface { ToJSONString() (string, error) AllRegions() []string GetResources() ResourcesModel + Fabricate(opts *FabricateOptions) } type ResourcesModel interface { @@ -22,3 +23,8 @@ type ResourceModelMetadata struct { Version string `json:"collector_version"` Provider string `json:"provider"` } + +type FabricateOptions struct { + NumVPCs int + SubnetsPerVPC int +} diff --git a/pkg/ibm/fabricate.go b/pkg/ibm/fabricate.go new file mode 100644 index 0000000..2f3745a --- /dev/null +++ b/pkg/ibm/fabricate.go @@ -0,0 +1,158 @@ +/* +Copyright 2023- IBM Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package ibm + +import ( + "fmt" + "math/rand" + + "github.com/IBM/vpc-go-sdk/vpcv1" + + "github.com/np-guard/cloud-resource-collector/pkg/common" + "github.com/np-guard/cloud-resource-collector/pkg/ibm/datamodel" + "github.com/np-guard/models/pkg/ipblock" +) + +const ( + ipElementSize = 256 + defaultCidrPrefix = 24 + maxNumACLsInVPC = 10 + maxNumRulesInNACL = 10 +) + +var ( + regionsAndZones = map[string][]string{ + "us-south": {"us-south1", "us-south2", "us-south3"}, + "us-east": {"us-east1", "us-east2", "us-east3"}, + } + uid = map[string]int{} + availableIPs, _ = ipblock.FromCidrList([]string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"}) + nACLsOfVPC = map[string][]*datamodel.NetworkACL{} + + vpcType = vpcv1.VPCReferenceResourceTypeVPCConst + ipv4 = vpcv1.NetworkACLRuleItemNetworkACLRuleProtocolAllIPVersionIpv4Const + allProtocols = vpcv1.NetworkACLRuleItemNetworkACLRuleProtocolAllProtocolAllConst +) + +func getUID(resource string) *string { + res := fmt.Sprintf("%s-%d", resource, uid[resource]) + uid[resource]++ + return &res +} + +func getVPCRef(vpcID *string) *vpcv1.VPCReference { + return &vpcv1.VPCReference{CRN: vpcID, ID: vpcID, Name: vpcID, ResourceType: &vpcType} +} + +func chooseRandElem[T any](pool []T) *T { + return &pool[rand.Intn(len(pool))] //nolint:gosec // weak random is ok here +} + +func getRandomRegion() string { + regionNum := rand.Intn(len(regionsAndZones)) //nolint:gosec // weak random is ok here + i := 0 + for k := range regionsAndZones { + if i == regionNum { + return k + } + i++ + } + return "" +} + +func getAvailableInternalCidrBlock() *string { + prefix := defaultCidrPrefix - rand.Intn(2) //nolint:gosec // weak random is ok here + baseIP := availableIPs.FirstIPAddress() + cidr := fmt.Sprintf("%s/%d", baseIP, prefix) + cidrIPBlock, _ := ipblock.FromCidr(cidr) + availableIPs = availableIPs.Subtract(cidrIPBlock) + return &cidr +} + +func getRandomCidr() *string { + var ipElem [4]int + for i := 0; i < len(ipElem); i++ { + ipElem[i] = rand.Intn(ipElementSize) //nolint:gosec // weak random is ok here + } + prefix := rand.Intn(2) //nolint:gosec // weak random is ok here + cidr := fmt.Sprintf("%d.%d.%d.%d/%d", ipElem[0], ipElem[1], ipElem[2], ipElem[3], prefix) + return &cidr +} + +func makeNACLRules() []vpcv1.NetworkACLRuleItemIntf { + res := []vpcv1.NetworkACLRuleItemIntf{} + + numRules := rand.Intn(maxNumRulesInNACL) //nolint:gosec // weak random is ok here + for i := 0; i < numRules; i++ { + ruleID := getUID("aclRule") + rule := vpcv1.NetworkACLRuleItemNetworkACLRuleProtocolAll{ + ID: ruleID, + Name: ruleID, + Action: chooseRandElem([]string{ + vpcv1.NetworkACLRuleItemNetworkACLRuleProtocolAllActionAllowConst, + vpcv1.NetworkACLRuleItemNetworkACLRuleProtocolAllActionDenyConst}), + Direction: chooseRandElem([]string{ + vpcv1.NetworkACLRuleItemNetworkACLRuleProtocolAllDirectionInboundConst, + vpcv1.NetworkACLRuleItemNetworkACLRuleProtocolAllDirectionOutboundConst, + }), + Source: getRandomCidr(), + Destination: getRandomCidr(), + Protocol: &allProtocols, + IPVersion: &ipv4, + } + res = append(res, &rule) + } + + return res +} + +func makeNACLs(vpcID string) []*datamodel.NetworkACL { + numNacls := rand.Intn(maxNumACLsInVPC) + 1 //nolint:gosec // weak random is ok here + res := []*datamodel.NetworkACL{} + for i := 0; i < numNacls; i++ { + naclID := getUID("nacl") + sdkNACL := vpcv1.NetworkACL{ID: naclID, CRN: naclID, Name: naclID, VPC: getVPCRef(&vpcID)} + sdkNACL.Rules = makeNACLRules() + modelNacl := datamodel.NewNetworkACL(&sdkNACL) + res = append(res, modelNacl) + nACLsOfVPC[vpcID] = append(nACLsOfVPC[vpcID], modelNacl) + } + + return res +} + +func getNACLRef(nacl *datamodel.NetworkACL) *vpcv1.NetworkACLReference { + return &vpcv1.NetworkACLReference{CRN: nacl.CRN, ID: nacl.ID, Name: nacl.Name} +} + +func getSubnetRef(subnet *datamodel.Subnet) vpcv1.SubnetReference { + return vpcv1.SubnetReference{CRN: subnet.CRN, ID: subnet.ID, Name: subnet.Name} +} + +func (resources *ResourcesContainer) Fabricate(opts *common.FabricateOptions) { + for i := 0; i < opts.NumVPCs; i++ { + vpcID := getUID("vpc") + vpcRegion := getRandomRegion() + sdkVPC := vpcv1.VPC{ID: vpcID, Name: vpcID, CRN: vpcID} + vpc := datamodel.NewVPC(&sdkVPC, vpcRegion, nil) + resources.VpcList = append(resources.VpcList, vpc) + + resources.NetworkACLList = append(resources.NetworkACLList, makeNACLs(*vpcID)...) + + zone := vpcv1.ZoneReference{Name: chooseRandElem(regionsAndZones[vpcRegion])} + for s := 0; s < opts.SubnetsPerVPC; s++ { + subnetID := getUID("subnet") + sdkSubnet := vpcv1.Subnet{ID: subnetID, Name: subnetID, CRN: subnetID, VPC: getVPCRef(vpcID), Zone: &zone} + sdkSubnet.Ipv4CIDRBlock = getAvailableInternalCidrBlock() + subnetNACL := *chooseRandElem(nACLsOfVPC[*vpcID]) + sdkSubnet.NetworkACL = getNACLRef(subnetNACL) + subnet := datamodel.NewSubnet(&sdkSubnet, nil) + subnetNACL.Subnets = append(subnetNACL.Subnets, getSubnetRef(subnet)) + resources.SubnetList = append(resources.SubnetList, subnet) + } + } +}