diff --git a/.changes/v2.15.0/452-features.md b/.changes/v2.15.0/452-features.md new file mode 100644 index 000000000..0e86d9749 --- /dev/null +++ b/.changes/v2.15.0/452-features.md @@ -0,0 +1,5 @@ +* Add support for for Network Context Profile lookup using `GetAllNetworkContextProfiles` and + `GetNetworkContextProfilesByNameScopeAndContext` functions [GH-452] +* Add support for NSX-T Distributed Firewall rule management using type `DistributedFirewall` and +`VdcGroup.GetDistributedFirewall`, `VdcGroup.UpdateDistributedFirewall`, +`VdcGroup.DeleteAllDistributedFirewallRules`, `DistributedFirewall.DeleteAllRules` [GH-452] diff --git a/govcd/nsxt_distributed_firewall.go b/govcd/nsxt_distributed_firewall.go new file mode 100644 index 000000000..731b1bed0 --- /dev/null +++ b/govcd/nsxt_distributed_firewall.go @@ -0,0 +1,101 @@ +package govcd + +import ( + "errors" + "fmt" + + "github.com/vmware/go-vcloud-director/v2/types/v56" +) + +// DistributedFirewall contains a types.DistributedFirewallRules which handles Distributed Firewall +// rules in a VDC Group +type DistributedFirewall struct { + DistributedFirewallRuleContainer *types.DistributedFirewallRules + client *Client + VdcGroup *VdcGroup +} + +// GetDistributedFirewall retrieves Distributed Firewall in a VDC Group which contains all rules +// +// Note. This function works only with `default` policy as this was the only supported when this +// functions was created +func (vdcGroup *VdcGroup) GetDistributedFirewall() (*DistributedFirewall, error) { + client := vdcGroup.client + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcGroupsDfwRules + apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint) + if err != nil { + return nil, err + } + + // "default" policy is hardcoded because there is no other policy supported + urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, vdcGroup.VdcGroup.Id, types.DistributedFirewallPolicyDefault)) + if err != nil { + return nil, err + } + + returnObject := &DistributedFirewall{ + DistributedFirewallRuleContainer: &types.DistributedFirewallRules{}, + client: client, + VdcGroup: vdcGroup, + } + + err = client.OpenApiGetItem(apiVersion, urlRef, nil, returnObject.DistributedFirewallRuleContainer, nil) + if err != nil { + return nil, fmt.Errorf("error retrieving Distributed Firewall rules: %s", err) + } + + return returnObject, nil +} + +// UpdateDistributedFirewall updates Distributed Firewall in a VDC Group +// +// Note. This function works only with `default` policy as this was the only supported when this +// functions was created +func (vdcGroup *VdcGroup) UpdateDistributedFirewall(dfwRules *types.DistributedFirewallRules) (*DistributedFirewall, error) { + client := vdcGroup.client + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcGroupsDfwRules + apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint) + if err != nil { + return nil, err + } + + // "default" policy is hardcoded because there is no other policy supported + urlRef, err := client.OpenApiBuildEndpoint(fmt.Sprintf(endpoint, vdcGroup.VdcGroup.Id, types.DistributedFirewallPolicyDefault)) + if err != nil { + return nil, err + } + + returnObject := &DistributedFirewall{ + DistributedFirewallRuleContainer: &types.DistributedFirewallRules{}, + client: client, + VdcGroup: vdcGroup, + } + + err = client.OpenApiPutItem(apiVersion, urlRef, nil, dfwRules, returnObject.DistributedFirewallRuleContainer, nil) + if err != nil { + return nil, fmt.Errorf("error updating Distributed Firewall rules: %s", err) + } + + return returnObject, nil +} + +// DeleteAllDistributedFirewallRules removes all Distributed Firewall rules +// +// Note. This function works only with `default` policy as this was the only supported when this +// functions was created +func (vdcGroup *VdcGroup) DeleteAllDistributedFirewallRules() error { + _, err := vdcGroup.UpdateDistributedFirewall(&types.DistributedFirewallRules{}) + return err +} + +// DeleteAllRules removes all Distributed Firewall rules +// +// Note. This function works only with `default` policy as this was the only supported when this +// functions was created +func (firewall *DistributedFirewall) DeleteAllRules() error { + if firewall.VdcGroup != nil && firewall.VdcGroup.VdcGroup != nil && firewall.VdcGroup.VdcGroup.Id == "" { + return errors.New("empty VDC Group ID for parent VDC Group") + } + + return firewall.VdcGroup.DeleteAllDistributedFirewallRules() +} diff --git a/govcd/nsxt_distributed_firewall_test.go b/govcd/nsxt_distributed_firewall_test.go new file mode 100644 index 000000000..465fa785e --- /dev/null +++ b/govcd/nsxt_distributed_firewall_test.go @@ -0,0 +1,292 @@ +//go:build network || nsxt || functional || openapi || ALL +// +build network nsxt functional openapi ALL + +package govcd + +import ( + "fmt" + "os" + "strconv" + "strings" + "text/tabwriter" + + "github.com/vmware/go-vcloud-director/v2/types/v56" + . "gopkg.in/check.v1" +) + +// Test_NsxtDistributedFirewall creates a list of distributed firewall rules with randomized +// parameters in two modes: +// * System user +// * Org Admin user +func (vcd *TestVCD) Test_NsxtDistributedFirewallRules(check *C) { + skipNoNsxtConfiguration(vcd, check) + skipOpenApiEndpointTest(vcd, check, types.OpenApiPathVersion1_0_0+types.OpenApiEndpointEdgeGateways) + + adminOrg, err := vcd.client.GetAdminOrgByName(vcd.config.VCD.Org) + check.Assert(adminOrg, NotNil) + check.Assert(err, IsNil) + + nsxtExternalNetwork, err := GetExternalNetworkV2ByName(vcd.client, vcd.config.VCD.Nsxt.ExternalNetwork) + check.Assert(nsxtExternalNetwork, NotNil) + check.Assert(err, IsNil) + + vdc, vdcGroup := test_CreateVdcGroup(check, adminOrg, vcd) + check.Assert(vdc, NotNil) + check.Assert(vdcGroup, NotNil) + + // Run firewall tests as System user + fmt.Println("# Running Distributed Firewall tests as 'System' user") + test_NsxtDistributedFirewallRules(vcd, check, vdcGroup.VdcGroup.Id, vcd.client, vdc) + + // Prep Org admin user and run firewall tests + userName := strings.ToLower(check.TestName()) + fmt.Printf("# Running Distributed Firewall tests as Org Admin user '%s'\n", userName) + orgUserVcdClient, err := newOrgUserConnection(adminOrg, userName, "CHANGE-ME", vcd.config.Provider.Url, true) + check.Assert(err, IsNil) + orgUserOrgAdmin, err := orgUserVcdClient.GetAdminOrgById(adminOrg.AdminOrg.ID) + check.Assert(err, IsNil) + orgUserVdc, err := orgUserOrgAdmin.GetVDCById(vdc.Vdc.ID, false) + check.Assert(err, IsNil) + test_NsxtDistributedFirewallRules(vcd, check, vdcGroup.VdcGroup.Id, orgUserVcdClient, orgUserVdc) + + // Cleanup + err = vdcGroup.Delete() + check.Assert(err, IsNil) + err = vdc.DeleteWait(true, true) + check.Assert(err, IsNil) +} + +func test_NsxtDistributedFirewallRules(vcd *TestVCD, check *C, vdcGroupId string, vcdClient *VCDClient, vdc *Vdc) { + adminOrg, err := vcdClient.GetAdminOrgByName(vcd.config.VCD.Org) + check.Assert(adminOrg, NotNil) + check.Assert(err, IsNil) + + vdcGroup, err := adminOrg.GetVdcGroupById(vdcGroupId) + check.Assert(err, IsNil) + + _, err = vdcGroup.ActivateDfw() + check.Assert(err, IsNil) + + // Get existing firewall rule configuration + fwRules, err := vdcGroup.GetDistributedFirewall() + check.Assert(err, IsNil) + check.Assert(fwRules.DistributedFirewallRuleContainer.Values, NotNil) + + // Create some prerequisites and generate firewall rule configurations to feed them into config + randomizedFwRuleDefs, ipSet, secGroup := createDistributedFirewallDefinitions(check, vcd, vdcGroup.VdcGroup.Id, vcdClient, vdc) + + fwRules.DistributedFirewallRuleContainer.Values = randomizedFwRuleDefs + + if testVerbose { + dumpDistributedFirewallRulesToScreen(randomizedFwRuleDefs) + } + + fwUpdated, err := vdcGroup.UpdateDistributedFirewall(fwRules.DistributedFirewallRuleContainer) + check.Assert(err, IsNil) + check.Assert(fwUpdated, Not(IsNil)) + + check.Assert(len(fwUpdated.DistributedFirewallRuleContainer.Values), Equals, len(randomizedFwRuleDefs)) + + // Check that all created rules have the same attributes and order + for index := range fwUpdated.DistributedFirewallRuleContainer.Values { + check.Assert(fwUpdated.DistributedFirewallRuleContainer.Values[index].Name, Equals, randomizedFwRuleDefs[index].Name) + check.Assert(fwUpdated.DistributedFirewallRuleContainer.Values[index].Direction, Equals, randomizedFwRuleDefs[index].Direction) + check.Assert(fwUpdated.DistributedFirewallRuleContainer.Values[index].IpProtocol, Equals, randomizedFwRuleDefs[index].IpProtocol) + check.Assert(fwUpdated.DistributedFirewallRuleContainer.Values[index].Enabled, Equals, randomizedFwRuleDefs[index].Enabled) + check.Assert(fwUpdated.DistributedFirewallRuleContainer.Values[index].Logging, Equals, randomizedFwRuleDefs[index].Logging) + check.Assert(fwUpdated.DistributedFirewallRuleContainer.Values[index].Comments, Equals, randomizedFwRuleDefs[index].Comments) + + // API V 35.2 uses ActionValue field instead of deprecated `Action` + if vcd.client.Client.APIVCDMaxVersionIs(">= 35.2") { + check.Assert(fwUpdated.DistributedFirewallRuleContainer.Values[index].ActionValue, Equals, randomizedFwRuleDefs[index].ActionValue) + } else { + check.Assert(fwUpdated.DistributedFirewallRuleContainer.Values[index].Action, Equals, randomizedFwRuleDefs[index].Action) + } + + for fwGroupIndex := range fwUpdated.DistributedFirewallRuleContainer.Values[index].SourceFirewallGroups { + check.Assert(fwUpdated.DistributedFirewallRuleContainer.Values[index].SourceFirewallGroups[fwGroupIndex].ID, Equals, randomizedFwRuleDefs[index].SourceFirewallGroups[fwGroupIndex].ID) + } + + for fwGroupIndex := range fwUpdated.DistributedFirewallRuleContainer.Values[index].DestinationFirewallGroups { + check.Assert(fwUpdated.DistributedFirewallRuleContainer.Values[index].DestinationFirewallGroups[fwGroupIndex].ID, Equals, randomizedFwRuleDefs[index].DestinationFirewallGroups[fwGroupIndex].ID) + } + + // Ensure the same amount of Application Port Profiles are assigned and created + check.Assert(len(fwUpdated.DistributedFirewallRuleContainer.Values), Equals, len(randomizedFwRuleDefs)) + definedAppPortProfileIds := extractIdsFromOpenApiReferences(randomizedFwRuleDefs[index].ApplicationPortProfiles) + for _, appPortProfile := range fwUpdated.DistributedFirewallRuleContainer.Values[index].ApplicationPortProfiles { + check.Assert(contains(appPortProfile.ID, definedAppPortProfileIds), Equals, true) + } + + // Ensure the same amount of Network Context Profiles are assigned and created + definedNetContextProfileIds := extractIdsFromOpenApiReferences(randomizedFwRuleDefs[index].NetworkContextProfiles) + for _, networkContextProfile := range fwUpdated.DistributedFirewallRuleContainer.Values[index].NetworkContextProfiles { + check.Assert(contains(networkContextProfile.ID, definedNetContextProfileIds), Equals, true) + } + } + + // Cleanup + err = fwRules.DeleteAllRules() + check.Assert(err, IsNil) + // Check that rules were removed + newRules, err := vdcGroup.GetDistributedFirewall() + check.Assert(err, IsNil) + check.Assert(len(newRules.DistributedFirewallRuleContainer.Values) == 0, Equals, true) + + // Cleanup remaining setup + _, err = vdcGroup.DisableDefaultPolicy() + check.Assert(err, IsNil) + _, err = vdcGroup.DeactivateDfw() + check.Assert(err, IsNil) + err = ipSet.Delete() + check.Assert(err, IsNil) + err = secGroup.Delete() + check.Assert(err, IsNil) +} + +// createDistributedFirewallDefinitions creates some randomized firewall rule configurations to match possible configurations +func createDistributedFirewallDefinitions(check *C, vcd *TestVCD, vdcGroupId string, vcdClient *VCDClient, vdc *Vdc) ([]*types.DistributedFirewallRule, *NsxtFirewallGroup, *NsxtFirewallGroup) { + // This number does not impact performance because all rules are created at once in the API + numberOfRules := 40 + + // Pre-Create Firewall Groups (IP Set and Security Group to randomly configure them) + ipSet := preCreateVdcGroupIpSet(check, vcd, vdcGroupId, vdc) + secGroup := preCreateVdcGroupSecurityGroup(check, vcd, vdcGroupId, vdc) + fwGroupIds := []string{ipSet.NsxtFirewallGroup.ID, secGroup.NsxtFirewallGroup.ID} + fwGroupRefs := convertSliceOfStringsToOpenApiReferenceIds(fwGroupIds) + appPortProfileReferences := getRandomListOfAppPortProfiles(check, vcd) + networkContextProfiles := getRandomListOfNetworkContextProfiles(check, vcd, vcdClient) + + firewallRules := make([]*types.DistributedFirewallRule, numberOfRules) + for a := 0; a < numberOfRules; a++ { + + // Feed in empty value for source and destination or a firewall group + src := pickRandomOpenApiRefOrEmpty(fwGroupRefs) + var srcValue []types.OpenApiReference + dst := pickRandomOpenApiRefOrEmpty(fwGroupRefs) + var dstValue []types.OpenApiReference + if src != (types.OpenApiReference{}) { + srcValue = []types.OpenApiReference{src} + } + if dst != (types.OpenApiReference{}) { + dstValue = []types.OpenApiReference{dst} + } + + firewallRules[a] = &types.DistributedFirewallRule{ + Name: check.TestName() + strconv.Itoa(a), + Action: pickRandomString([]string{"ALLOW", "DROP"}), + Enabled: a%2 == 0, + SourceFirewallGroups: srcValue, + DestinationFirewallGroups: dstValue, + ApplicationPortProfiles: appPortProfileReferences[0:a], + IpProtocol: pickRandomString([]string{"IPV6", "IPV4", "IPV4_IPV6"}), + Logging: a%2 == 1, + Direction: pickRandomString([]string{"IN", "OUT", "IN_OUT"}), + } + + // Network Context Profile can usually work with up to one Application Profile therefore this + // needs to be explicitly preset + if a%5 == 1 { // Every fifth rule + netCtxProfile := networkContextProfiles[0:a] + networkContextProfile := make([]types.OpenApiReference, 0) + for _, netCtxProf := range netCtxProfile { + if netCtxProf.ID != "" { + networkContextProfile = append(networkContextProfile, types.OpenApiReference{ID: netCtxProf.ID}) + } + } + + firewallRules[a].NetworkContextProfiles = networkContextProfile + // firewallRules[a].ApplicationPortProfiles = appPortProfileReferences[0:1] + firewallRules[a].ApplicationPortProfiles = nil + + } + + // API V35.2 introduced new field ActionValue instead of deprecated Action + if vcd.client.Client.APIVCDMaxVersionIs(">= 35.2") { + firewallRules[a].Action = "" + firewallRules[a].ActionValue = pickRandomString([]string{"ALLOW", "DROP", "REJECT"}) + } + + // API V36.2 introduced new field Comment which is shown in UI + if vcd.client.Client.APIVCDMaxVersionIs(">= 36.2") { + firewallRules[a].Comments = "Comment Rule" + } + + } + + return firewallRules, ipSet, secGroup +} + +func preCreateVdcGroupIpSet(check *C, vcd *TestVCD, ownerId string, nsxtVdc *Vdc) *NsxtFirewallGroup { + ipSetDefinition := &types.NsxtFirewallGroup{ + Name: check.TestName() + "ipset", + Description: check.TestName() + "-Description", + Type: types.FirewallGroupTypeIpSet, + OwnerRef: &types.OpenApiReference{ID: ownerId}, + + IpAddresses: []string{ + "12.12.12.1", + "10.10.10.0/24", + "11.11.11.1-11.11.11.2", + // represents the block of IPv6 addresses from 2001:db8:0:0:0:0:0:0 to 2001:db8:0:ffff:ffff:ffff:ffff:ffff + "2001:db8::/48", + "2001:db6:0:0:0:0:0:0-2001:db6:0:ffff:ffff:ffff:ffff:ffff", + }, + } + + // Create IP Set and add to cleanup if it was created + createdIpSet, err := nsxtVdc.CreateNsxtFirewallGroup(ipSetDefinition) + check.Assert(err, IsNil) + openApiEndpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointFirewallGroups + createdIpSet.NsxtFirewallGroup.ID + PrependToCleanupListOpenApi(createdIpSet.NsxtFirewallGroup.Name, check.TestName(), openApiEndpoint) + + return createdIpSet +} + +func preCreateVdcGroupSecurityGroup(check *C, vcd *TestVCD, ownerId string, nsxtVdc *Vdc) *NsxtFirewallGroup { + fwGroupDefinition := &types.NsxtFirewallGroup{ + Name: check.TestName() + "security-group", + Description: check.TestName() + "-Description", + Type: types.FirewallGroupTypeSecurityGroup, + OwnerRef: &types.OpenApiReference{ID: ownerId}, + } + + // Create firewall group and add to cleanup if it was created + createdSecGroup, err := nsxtVdc.CreateNsxtFirewallGroup(fwGroupDefinition) + check.Assert(err, IsNil) + openApiEndpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointFirewallGroups + createdSecGroup.NsxtFirewallGroup.ID + PrependToCleanupListOpenApi(createdSecGroup.NsxtFirewallGroup.Name, check.TestName(), openApiEndpoint) + + return createdSecGroup +} + +func getRandomListOfNetworkContextProfiles(check *C, vcd *TestVCD, vdcClient *VCDClient) []types.OpenApiReference { + networkContextProfiles, err := GetAllNetworkContextProfiles(&vcd.client.Client, nil) + check.Assert(err, IsNil) + openApiRefs := make([]types.OpenApiReference, 1) + for _, networkContextProfile := range networkContextProfiles { + if strings.Contains(networkContextProfile.Description, "ALG") { + continue + } + openApiRef := types.OpenApiReference{ + ID: networkContextProfile.ID, + Name: networkContextProfile.Name, + } + + openApiRefs = append(openApiRefs, openApiRef) + } + + return openApiRefs +} + +func dumpDistributedFirewallRulesToScreen(rules []*types.DistributedFirewallRule) { + fmt.Println("# The following firewall rules will be created") + w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) + fmt.Fprintln(w, "Name\tDirection\tIP Protocol\tEnabled\tAction\tLogging\tSrc Count\tDst Count\tAppPortProfile Count\tNet Context Profile Count") + + for _, rule := range rules { + fmt.Fprintf(w, "%s\t%s\t%s\t%t\t%s\t%t\t%d\t%d\t%d\t%d\n", rule.Name, rule.Direction, rule.IpProtocol, + rule.Enabled, rule.Action, rule.Logging, len(rule.SourceFirewallGroups), len(rule.DestinationFirewallGroups), len(rule.ApplicationPortProfiles), len(rule.NetworkContextProfiles)) + } + w.Flush() +} diff --git a/govcd/nsxt_nat_rule_test.go b/govcd/nsxt_nat_rule_test.go index 168846365..a6691c36a 100644 --- a/govcd/nsxt_nat_rule_test.go +++ b/govcd/nsxt_nat_rule_test.go @@ -50,58 +50,10 @@ func (vcd *TestVCD) Test_NsxtNatDnat(check *C) { nsxtNatRuleChecks(natRuleDefinition, edge, check, vcd) } -func (vcd *TestVCD) Test_NsxtNatDnatInternalPort(check *C) { - skipNoNsxtConfiguration(vcd, check) - skipOpenApiEndpointTest(vcd, check, types.OpenApiPathVersion1_0_0+types.OpenApiEndpointFirewallGroups) - - if vcd.client.Client.APIVCDMaxVersionIs(">= 35.2") { - check.Skip("InternalPort field is only used in older API versions (< 35.2) and is replaced by 'DnatExternalPort' field") - } - - org, err := vcd.client.GetOrgByName(vcd.config.VCD.Org) - check.Assert(err, IsNil) - - nsxtVdc, err := org.GetVDCByName(vcd.config.VCD.Nsxt.Vdc, false) - check.Assert(err, IsNil) - - edge, err := nsxtVdc.GetNsxtEdgeGatewayByName(vcd.config.VCD.Nsxt.EdgeGateway) - check.Assert(err, IsNil) - - appPortProfiles, err := org.GetAllNsxtAppPortProfiles(nil, types.ApplicationPortProfileScopeSystem) - check.Assert(err, IsNil) - - edgeGatewayPrimaryIp := "" - if edge.EdgeGateway != nil && len(edge.EdgeGateway.EdgeGatewayUplinks) > 0 && len(edge.EdgeGateway.EdgeGatewayUplinks[0].Subnets.Values) > 0 { - edgeGatewayPrimaryIp = edge.EdgeGateway.EdgeGatewayUplinks[0].Subnets.Values[0].PrimaryIP - } - check.Assert(edgeGatewayPrimaryIp, Not(Equals), "") - - natRuleDefinition := &types.NsxtNatRule{ - Name: check.TestName() + "dnat", - Description: "description", - Enabled: true, - RuleType: types.NsxtNatRuleTypeDnat, - ExternalAddresses: edgeGatewayPrimaryIp, - InternalAddresses: "11.11.11.2", - ApplicationPortProfile: &types.OpenApiReference{ - ID: appPortProfiles[0].NsxtAppPortProfile.ID, - Name: appPortProfiles[0].NsxtAppPortProfile.Name}, - SnatDestinationAddresses: "", - Logging: true, - InternalPort: "9898", - } - - nsxtNatRuleChecks(natRuleDefinition, edge, check, vcd) -} - func (vcd *TestVCD) Test_NsxtNatDnatExternalPortPort(check *C) { skipNoNsxtConfiguration(vcd, check) skipOpenApiEndpointTest(vcd, check, types.OpenApiPathVersion1_0_0+types.OpenApiEndpointFirewallGroups) - if vcd.client.Client.APIVCDMaxVersionIs("< 35.2") { - check.Skip("DnatExternalPort field is only used in API V35.2 (previously 'InternalPort' field)") - } - org, err := vcd.client.GetOrgByName(vcd.config.VCD.Org) check.Assert(err, IsNil) diff --git a/govcd/nsxt_network_context_profile.go b/govcd/nsxt_network_context_profile.go new file mode 100644 index 000000000..cf9b84927 --- /dev/null +++ b/govcd/nsxt_network_context_profile.go @@ -0,0 +1,77 @@ +package govcd + +import ( + "fmt" + "net/url" + + "github.com/vmware/go-vcloud-director/v2/types/v56" +) + +// GetAllNetworkContextProfiles retrieves a slice of types.NsxtNetworkContextProfile +// This function requires at least a filter value for 'context_id' which can be one of: +// * Org VDC ID - to get Network Context Profiles scoped for VDC +// * Network provider ID - to get Network Context Profiles scoped for attached NSX-T environment +// * VDC Group ID - to get Network Context Profiles scoped for attached NSX-T environment +func GetAllNetworkContextProfiles(client *Client, queryParameters url.Values) ([]*types.NsxtNetworkContextProfile, error) { + endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkContextProfiles + apiVersion, err := client.getOpenApiHighestElevatedVersion(endpoint) + if err != nil { + return nil, err + } + + urlRef, err := client.OpenApiBuildEndpoint(endpoint) + if err != nil { + return nil, err + } + + typeResponses := []*types.NsxtNetworkContextProfile{} + err = client.OpenApiGetAllItems(apiVersion, urlRef, queryParameters, &typeResponses, nil) + if err != nil { + return nil, err + } + + return typeResponses, nil +} + +// GetNetworkContextProfilesByScopeAndName retrieves a single NSX-T Network Context Profile by name +// and context ID. All fields - name, scope and contextId are mandatory +// +// contextId is mandatory and can be one off: +// * Org VDC ID - to get Network Context Profiles scoped for VDC +// * Network provider ID - to get Network Context Profiles scoped for attached NSX-T environment +// * VDC Group ID - to get Network Context Profiles scoped for attached NSX-T environment +// +// scope can be one off: +// * SYSTEM +// * PROVIDER +// * TENANT +func GetNetworkContextProfilesByNameScopeAndContext(client *Client, name, scope, contextId string) (*types.NsxtNetworkContextProfile, error) { + if name == "" || contextId == "" || scope == "" { + return nil, fmt.Errorf("error - 'name', 'scope' and 'contextId' must be specified") + } + + queryParams := copyOrNewUrlValues(nil) + queryParams.Add("filter", fmt.Sprintf("name==%s", name)) + queryParams = queryParameterFilterAnd(fmt.Sprintf("_context==%s", contextId), queryParams) + queryParams = queryParameterFilterAnd(fmt.Sprintf("scope==%s", scope), queryParams) + + allProfiles, err := GetAllNetworkContextProfiles(client, queryParams) + if err != nil { + return nil, fmt.Errorf("error retrieving Network Context Profiles by name '%s', scope '%s' and context ID '%s': %s ", + name, scope, contextId, err) + } + + return returnSingleNetworkContextProfile(allProfiles) +} + +func returnSingleNetworkContextProfile(allProfiles []*types.NsxtNetworkContextProfile) (*types.NsxtNetworkContextProfile, error) { + if len(allProfiles) > 1 { + return nil, fmt.Errorf("got more than 1 NSX-T Network Context Profile %d", len(allProfiles)) + } + + if len(allProfiles) < 1 { + return nil, fmt.Errorf("%s: got 0 NSX-T Network Context Profiles", ErrorEntityNotFound) + } + + return allProfiles[0], nil +} diff --git a/govcd/nsxt_network_context_profile_test.go b/govcd/nsxt_network_context_profile_test.go new file mode 100644 index 000000000..a1883065f --- /dev/null +++ b/govcd/nsxt_network_context_profile_test.go @@ -0,0 +1,71 @@ +//go:build network || nsxt || functional || openapi || ALL +// +build network nsxt functional openapi ALL + +package govcd + +import ( + "net/url" + + "github.com/vmware/go-vcloud-director/v2/types/v56" + . "gopkg.in/check.v1" +) + +func (vcd *TestVCD) Test_GetAllNetworkContextProfiles(check *C) { + skipNoNsxtConfiguration(vcd, check) + skipOpenApiEndpointTest(vcd, check, types.OpenApiPathVersion1_0_0+types.OpenApiEndpointNetworkContextProfiles) + + filteredTestGetAllNetworkContextProfiles(nil, &vcd.client.Client, check) + + // Test with SYSTEM scope + queryParams := copyOrNewUrlValues(nil) + queryParams.Add("filter", "scope==SYSTEM") + filteredTestGetAllNetworkContextProfiles(queryParams, &vcd.client.Client, check) + + // Test with PROVIDER scope + queryParams = copyOrNewUrlValues(nil) + queryParams.Add("filter", "scope==PROVIDER") + filteredTestGetAllNetworkContextProfiles(queryParams, &vcd.client.Client, check) + + // Test with TENANT scope + queryParams = copyOrNewUrlValues(nil) + queryParams.Add("filter", "scope==TENANT") + filteredTestGetAllNetworkContextProfiles(queryParams, &vcd.client.Client, check) +} + +func (vcd *TestVCD) Test_GetNetworkContextProfilesByNameScopeAndContext(check *C) { + skipNoNsxtConfiguration(vcd, check) + skipOpenApiEndpointTest(vcd, check, types.OpenApiPathVersion1_0_0+types.OpenApiEndpointNetworkContextProfiles) + + // Expect error when fields are empty + profiles, err := GetNetworkContextProfilesByNameScopeAndContext(&vcd.client.Client, "", "", "") + check.Assert(err, NotNil) + check.Assert(profiles, IsNil) + + nsxtManagers, err := vcd.client.QueryNsxtManagerByName(vcd.config.VCD.Nsxt.Manager) + check.Assert(err, IsNil) + check.Assert(len(nsxtManagers), Equals, 1) + uuid, err := GetUuidFromHref(nsxtManagers[0].HREF, true) + check.Assert(err, IsNil) + nsxtManagerUrn, err := BuildUrnWithUuid("urn:vcloud:nsxtmanager:", uuid) + check.Assert(err, IsNil) + + profiles, err = GetNetworkContextProfilesByNameScopeAndContext(&vcd.client.Client, "AMQP", "SYSTEM", nsxtManagerUrn) + check.Assert(err, IsNil) + check.Assert(profiles, NotNil) + + // VCD does not have PROVIDER Network Context Profiles by default + profiles, err = GetNetworkContextProfilesByNameScopeAndContext(&vcd.client.Client, "AMQP", "PROVIDER", nsxtManagerUrn) + check.Assert(err, NotNil) + check.Assert(profiles, IsNil) + + // VCD does not have TENANT Network Context Profiles by default + profiles, err = GetNetworkContextProfilesByNameScopeAndContext(&vcd.client.Client, "AMQP", "TENANT", nsxtManagerUrn) + check.Assert(err, NotNil) + check.Assert(profiles, IsNil) +} + +func filteredTestGetAllNetworkContextProfiles(queryParams url.Values, client *Client, check *C) { + profiles, err := GetAllNetworkContextProfiles(client, queryParams) + check.Assert(err, IsNil) + check.Assert(profiles, NotNil) +} diff --git a/govcd/openapi_endpoints.go b/govcd/openapi_endpoints.go index 1f61ab4a2..a9c5a38da 100644 --- a/govcd/openapi_endpoints.go +++ b/govcd/openapi_endpoints.go @@ -63,6 +63,8 @@ var endpointMinApiVersions = map[string]string{ types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointAlbVirtualServiceSummaries: "35.0", // VCD 10.2+ types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointSSLCertificateLibrary: "35.0", // VCD 10.2+ types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointSSLCertificateLibraryOld: "35.0", // VCD 10.2+ and deprecated from 10.3 + types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcGroupsDfwRules: "35.0", // VCD 10.2+ + types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNetworkContextProfiles: "35.0", // VCD 10.2+ } // elevateNsxtNatRuleApiVersion helps to elevate API version to consume newer NSX-T NAT Rule features @@ -81,6 +83,11 @@ var endpointElevatedApiVersions = map[string][]string{ "35.0", // Deprecates field BackingType in favor of BackingTypeValue "36.0", // Adds support new type of BackingTypeValue - IMPORTED_T_LOGICAL_SWITCH (backed by NSX-T segment) }, + types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointVdcGroupsDfwRules: { + //"35.0", // Basic minimum required version + "35.2", // Deprecates Action field in favor of ActionValue + "36.2", // Adds 3 new fields - Comments, SourceGroupsExcluded, and DestinationGroupsExcluded + }, } // checkOpenApiEndpointCompatibility checks if VCD version (to which the client is connected) is sufficient to work with diff --git a/types/v56/constants.go b/types/v56/constants.go index e8f7c710c..3c9c87410 100644 --- a/types/v56/constants.go +++ b/types/v56/constants.go @@ -370,6 +370,8 @@ const ( OpenApiEndpointVdcGroupsCandidateVdcs = "vdcGroups/networkingCandidateVdcs" OpenApiEndpointVdcGroupsDfwPolicies = "vdcGroups/%s/dfwPolicies" OpenApiEndpointVdcGroupsDfwDefaultPolicies = "vdcGroups/%s/dfwPolicies/default" + OpenApiEndpointVdcGroupsDfwRules = "vdcGroups/%s/dfwPolicies/%s/rules" + OpenApiEndpointNetworkContextProfiles = "networkContextProfiles" // NSX-T ALB related endpoints @@ -495,3 +497,8 @@ const ( MetadataDateTimeValue string = "MetadataDateTimeValue" MetadataBooleanValue string = "MetadataBooleanValue" ) + +const ( + // DistributedFirewallPolicyDefault is a constant for "default" Distributed Firewall Policy + DistributedFirewallPolicyDefault = "default" +) diff --git a/types/v56/nsxt_types.go b/types/v56/nsxt_types.go index 8b705fa93..405c0ecb0 100644 --- a/types/v56/nsxt_types.go +++ b/types/v56/nsxt_types.go @@ -1106,3 +1106,101 @@ type NsxtAlbVirtualServiceApplicationProfile struct { // * L4 TLS (certificate reference is mandatory) Type string `json:"type"` } + +// DistributedFirewallRule represents a single Distributed Firewall rule +type DistributedFirewallRule struct { + ID string `json:"id,omitempty"` + Name string `json:"name"` + + // Action field. Deprecated in favor of ActionValue in VCD 10.2.2+ (API V35.2) + Action string `json:"action,omitempty"` + + // Description field is not shown in UI. 'Comments' field was introduced in 10.3.2 and is shown + // in UI. + Description string `json:"description,omitempty"` + + // ApplicationPortProfiles contains a list of references to Application Port Profiles. Empty + // list means 'Any' + ApplicationPortProfiles []OpenApiReference `json:"applicationPortProfiles,omitempty"` + + // SourceFirewallGroups contains a list of references to Firewall Groups. Empty list means 'Any' + SourceFirewallGroups []OpenApiReference `json:"sourceFirewallGroups,omitempty"` + // DestinationFirewallGroups contains a list of references to Firewall Groups. Empty list means + // 'Any' + DestinationFirewallGroups []OpenApiReference `json:"destinationFirewallGroups,omitempty"` + + // Direction 'IN_OUT', 'OUT', 'IN' + Direction string `json:"direction"` + Enabled bool `json:"enabled"` + + // IpProtocol 'IPV4', 'IPV6', 'IPV4_IPV6' + IpProtocol string `json:"ipProtocol"` + + Logging bool `json:"logging"` + + // NetworkContextProfiles sets list of layer 7 network context profiles where this firewall + // rule is applicable. Null value or an empty list will be treated as 'ANY' which means rule + // applies to all applications and domains. + NetworkContextProfiles []OpenApiReference `json:"networkContextProfiles,omitempty"` + + // Version describes the current version of the entity. To prevent clients from overwriting each + // other's changes, update operations must include the version which can be obtained by issuing + // a GET operation. If the version number on an update call is missing, the operation will be + // rejected. This is only needed on update calls. + Version *DistributedFirewallRuleVersion `json:"version,omitempty"` + + // New fields starting with 35.2 + + // ActionValue replaces deprecated field Action and defines action to be applied to all the + // traffic that meets the firewall rule criteria. It determines if the rule permits or blocks + // traffic. Property is required if action is not set. Below are valid values: + // * ALLOW permits traffic to go through the firewall. + // * DROP blocks the traffic at the firewall. No response is sent back to the source. + // * REJECT blocks the traffic at the firewall. A response is sent back to the source. + ActionValue string `json:"actionValue,omitempty"` + + // New fields starting with 36.2 + + // Comments permits setting text for user entered comments on the firewall rule. Length cannot + // exceed 2048 characters. Comments are shown in UI for 10.3.2+. + Comments string `json:"comments,omitempty"` + + // SourceGroupsExcluded reverses the list specified in SourceFirewallGroups and the rule gets + // applied on all the groups that are NOT part of the SourceFirewallGroups. If false, the rule + // applies to the all the groups including the source groups. + SourceGroupsExcluded *bool `json:"sourceGroupsExcluded,omitempty"` + + // DestinationGroupsExcluded reverses the list specified in DestinationFirewallGroups and the + // rule gets applied on all the groups that are NOT part of the DestinationFirewallGroups. If + // false, the rule applies to the all the groups in DestinationFirewallGroups. + DestinationGroupsExcluded *bool `json:"destinationGroupsExcluded,omitempty"` +} + +type DistributedFirewallRules struct { + Values []*DistributedFirewallRule `json:"values"` +} + +type DistributedFirewallRuleVersion struct { + Version int `json:"version"` +} + +type NsxtNetworkContextProfile struct { + OrgRef *OpenApiReference `json:"orgRef"` + ContextEntityID interface{} `json:"contextEntityId"` + NetworkProviderScope interface{} `json:"networkProviderScope"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + + // Scope of NSX-T Network Context Profile + // SYSTEM profiles are available to all tenants. They are default profiles from the backing networking provider. + // PROVIDER profiles are available to all tenants. They are defined by the provider at a system level. + // TENANT profiles are available only to the specific tenant organization. They are defined by the tenant or by a provider on behalf of a tenant. + Scope string `json:"scope"` + Attributes []NsxtNetworkContextProfileAttributes `json:"attributes"` +} +type NsxtNetworkContextProfileAttributes struct { + Type string `json:"type"` + Values []string `json:"values"` + SubAttributes interface{} `json:"subAttributes"` +}