From e2e16e387e87a21d848aa03913606b70f287d6f7 Mon Sep 17 00:00:00 2001 From: Anna Khmelnitsky Date: Mon, 13 Jan 2025 18:45:36 -0800 Subject: [PATCH] Support service entries in policy rules This follows API change on NSX that allows to specify explicit service entries on rule, as an alternative to service path. Signed-off-by: Anna Khmelnitsky --- nsxt/policy_common.go | 35 ++ ...esource_nsxt_policy_gateway_policy_test.go | 90 ++-- ...source_nsxt_policy_security_policy_rule.go | 18 +- ...e_nsxt_policy_security_policy_rule_test.go | 21 + ...source_nsxt_policy_security_policy_test.go | 67 +-- nsxt/resource_nsxt_policy_service.go | 461 ++++++++++-------- nsxt/resource_nsxt_policy_service_test.go | 2 +- .../r/policy_gateway_policy.html.markdown | 24 + .../r/policy_security_policy.html.markdown | 31 +- .../policy_security_policy_rule.html.markdown | 24 + 10 files changed, 492 insertions(+), 281 deletions(-) diff --git a/nsxt/policy_common.go b/nsxt/policy_common.go index 490def339..930d2da65 100644 --- a/nsxt/policy_common.go +++ b/nsxt/policy_common.go @@ -271,6 +271,15 @@ func getSecurityPolicyAndGatewayRuleSchema(scopeRequired bool, isIds bool, nsxID }, Optional: true, }, + "service_entries": { + Type: schema.TypeList, + Description: "List of services to match", + Elem: &schema.Resource{ + Schema: getPolicyServiceEntrySchema(), + }, + Optional: true, + MaxItems: 1, + }, "source_groups": { Type: schema.TypeSet, Description: "List of source groups", @@ -330,6 +339,17 @@ func getPolicyGatewayPolicySchema(isVPC bool) map[string]*schema.Schema { return secPolicy } +func getPolicyServiceEntrySchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "icmp_entry": getIcmpEntrySchema(), + "l4_port_set_entry": getL4PortSetEntrySchema(), + "igmp_entry": getIgmpEntrySchema(), + "ether_type_entry": getEtherEntrySchema(false), + "ip_protocol_entry": getIPProtocolEntrySchema(), + "algorithm_entry": getAlgorithmEntrySchema(), + } +} + func getPolicySecurityPolicySchema(isIds, withContext, withRule, isVPC bool) map[string]*schema.Schema { result := map[string]*schema.Schema{ "nsx_id": getNsxIDSchema(), @@ -485,6 +505,14 @@ func setPolicyRulesInSchema(d *schema.ResourceData, rules []model.Rule) error { } elem["tag"] = tagList + if len(rule.ServiceEntries) > 0 { + var entryList []map[string]interface{} + entry := make(map[string]interface{}) + setServiceEntriesInSchema(entry, rule.ServiceEntries, false) + entryList = append(entryList, entry) + elem["service_entries"] = entryList + } + rulesList = append(rulesList, elem) } @@ -579,6 +607,13 @@ func getPolicyRulesFromSchema(d *schema.ResourceData) []model.Rule { SequenceNumber: &sequenceNumber, } + schemaServiceEntries := data["service_entries"].([]interface{}) + if len(schemaServiceEntries) > 0 { + schemaServiceEntry := schemaServiceEntries[0].(map[string]interface{}) + serviceEntries, _ := getServiceEntriesFromSchema(schemaServiceEntry) + elem.ServiceEntries = serviceEntries + } + ruleList = append(ruleList, elem) } diff --git a/nsxt/resource_nsxt_policy_gateway_policy_test.go b/nsxt/resource_nsxt_policy_gateway_policy_test.go index 9d43c7288..a99092479 100644 --- a/nsxt/resource_nsxt_policy_gateway_policy_test.go +++ b/nsxt/resource_nsxt_policy_gateway_policy_test.go @@ -501,6 +501,7 @@ func TestAccResourceNsxtPolicyGatewayPolicy_withIPCidrRange(t *testing.T) { resource.TestCheckResourceAttr(testResourceName, "rule.5.source_groups.#", "1"), resource.TestCheckResourceAttr(testResourceName, "rule.5.source_groups.0", policyRange), resource.TestCheckResourceAttr(testResourceName, "rule.5.destination_groups.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "rule.5.service_entries.#", "1"), ), }, { @@ -560,6 +561,7 @@ func TestAccResourceNsxtPolicyGatewayPolicy_withIPCidrRange(t *testing.T) { resource.TestCheckResourceAttr(testResourceName, "rule.5.source_groups.#", "1"), resource.TestCheckResourceAttr(testResourceName, "rule.5.source_groups.0", updatedPolicyRange), resource.TestCheckResourceAttr(testResourceName, "rule.5.destination_groups.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "rule.5.service_entries.#", "1"), ), }, }, @@ -952,48 +954,58 @@ func testAccNsxtPolicyGatewayPolicyWithIPCidrRange(name string, destIP string, d } rule { - display_name = "rule2" - source_groups = [nsxt_policy_group.group1.path] - destination_groups = ["%s"] - services = [nsxt_policy_service.icmp.path] - scope = [nsxt_policy_tier1_gateway.gwt1test.path] - action = "ALLOW" - } + display_name = "rule2" + source_groups = [nsxt_policy_group.group1.path] + destination_groups = ["%s"] + services = [nsxt_policy_service.icmp.path] + scope = [nsxt_policy_tier1_gateway.gwt1test.path] + action = "ALLOW" + } - rule { - display_name = "rule3" - source_groups = [nsxt_policy_group.group1.path] - destination_groups = ["%s"] - services = [nsxt_policy_service.icmp.path] - scope = [nsxt_policy_tier1_gateway.gwt1test.path] - action = "ALLOW" - } + rule { + display_name = "rule3" + source_groups = [nsxt_policy_group.group1.path] + destination_groups = ["%s"] + services = [nsxt_policy_service.icmp.path] + scope = [nsxt_policy_tier1_gateway.gwt1test.path] + action = "ALLOW" + } - rule { - display_name = "rule4" - source_groups = ["%s"] - destination_groups = [nsxt_policy_group.group2.path] - services = [nsxt_policy_service.icmp.path] - scope = [nsxt_policy_tier1_gateway.gwt1test.path] - action = "ALLOW" - } + rule { + display_name = "rule4" + source_groups = ["%s"] + destination_groups = [nsxt_policy_group.group2.path] + services = [nsxt_policy_service.icmp.path] + scope = [nsxt_policy_tier1_gateway.gwt1test.path] + action = "ALLOW" + } - rule { - display_name = "rule5" - source_groups = ["%s"] - destination_groups = [nsxt_policy_group.group2.path] - services = [nsxt_policy_service.icmp.path] - scope = [nsxt_policy_tier1_gateway.gwt1test.path] - action = "ALLOW" - } + rule { + display_name = "rule5" + source_groups = ["%s"] + destination_groups = [nsxt_policy_group.group2.path] + services = [nsxt_policy_service.icmp.path] + scope = [nsxt_policy_tier1_gateway.gwt1test.path] + action = "ALLOW" + } - rule { - display_name = "rule6" - source_groups = ["%s"] - destination_groups = [nsxt_policy_group.group2.path] - services = [nsxt_policy_service.icmp.path] - scope = [nsxt_policy_tier1_gateway.gwt1test.path] - action = "ALLOW" - } + rule { + display_name = "rule6" + source_groups = ["%s"] + destination_groups = [nsxt_policy_group.group2.path] + services = [nsxt_policy_service.icmp.path] + scope = [nsxt_policy_tier1_gateway.gwt1test.path] + action = "ALLOW" + service_entries { + algorithm_entry { + algorithm = "TFTP" + destination_port = "9000" + } + + ether_type_entry { + ether_type = "1536" + } + } + } }`, name, destIP, destCidr, destIPRange, sourceIP, sourceCidr, sourceIPRange) } diff --git a/nsxt/resource_nsxt_policy_security_policy_rule.go b/nsxt/resource_nsxt_policy_security_policy_rule.go index 083bf55b7..3751c5407 100644 --- a/nsxt/resource_nsxt_policy_security_policy_rule.go +++ b/nsxt/resource_nsxt_policy_security_policy_rule.go @@ -100,7 +100,7 @@ func securityPolicyRuleSchemaToModel(d *schema.ResourceData, id string) model.Ru tagStructs := getPolicyTagsFromSet(d.Get("tag").(*schema.Set)) resourceType := "Rule" - return model.Rule{ + rule := model.Rule{ ResourceType: &resourceType, Id: &id, DisplayName: &displayName, @@ -122,6 +122,14 @@ func securityPolicyRuleSchemaToModel(d *schema.ResourceData, id string) model.Ru Profiles: getPathListFromSchema(d, "profiles"), SequenceNumber: &sequenceNumber, } + + schemaServiceEntries := d.Get("service_entries").([]interface{}) + if len(schemaServiceEntries) > 0 { + schemaServiceEntry := schemaServiceEntries[0].(map[string]interface{}) + serviceEntries, _ := getServiceEntriesFromSchema(schemaServiceEntry) + rule.ServiceEntries = serviceEntries + } + return rule } func resourceNsxtPolicySecurityPolicyRuleExistsPartial(d *schema.ResourceData, m interface{}, policyPath string) func(sessionContext utl.SessionContext, id string, connector client.Connector) (bool, error) { @@ -210,6 +218,14 @@ func securityPolicyRuleModelToSchema(d *schema.ResourceData, rule model.Rule) { d.Set("nsx_id", rule.Id) d.Set("rule_id", rule.RuleId) + var entryList []map[string]interface{} + if len(rule.ServiceEntries) > 0 { + entry := make(map[string]interface{}) + setServiceEntriesInSchema(entry, rule.ServiceEntries, false) + entryList = append(entryList, entry) + } + d.Set("service_entries", entryList) + setPolicyTagsInSchema(d, rule.Tags) } diff --git a/nsxt/resource_nsxt_policy_security_policy_rule_test.go b/nsxt/resource_nsxt_policy_security_policy_rule_test.go index 77f4b5d4a..4899b5f47 100644 --- a/nsxt/resource_nsxt_policy_security_policy_rule_test.go +++ b/nsxt/resource_nsxt_policy_security_policy_rule_test.go @@ -70,6 +70,9 @@ func testAccResourceNsxtPolicySecurityPolicyRuleBasic(t *testing.T, withContext resource.TestCheckResourceAttr(ruleResourceName, "direction", direction), resource.TestCheckResourceAttr(ruleResourceName, "ip_version", proto), resource.TestCheckResourceAttr(ruleResourceName, "sequence_number", seqNum), + resource.TestCheckResourceAttr(ruleResourceName, "service_entries.#", "1"), + resource.TestCheckResourceAttr(ruleResourceName, "service_entries.0.igmp_entry.#", "1"), + resource.TestCheckResourceAttr(ruleResourceName, "service_entries.0.l4_port_set_entry.#", "2"), ), }, { @@ -282,6 +285,24 @@ resource "nsxt_policy_security_policy_rule" "%s" { ip_version = "%s" sequence_number = %s + service_entries { + igmp_entry { + display_name = "test" + } + + l4_port_set_entry { + display_name = "entry-2" + protocol = "TCP" + destination_ports = [ "443" ] + } + + l4_port_set_entry { + display_name = "entry-3" + protocol = "TCP" + destination_ports = [ "80" ] + } + } + tag { scope = "color" tag = "orange" diff --git a/nsxt/resource_nsxt_policy_security_policy_test.go b/nsxt/resource_nsxt_policy_security_policy_test.go index 821a20774..eda796609 100644 --- a/nsxt/resource_nsxt_policy_security_policy_test.go +++ b/nsxt/resource_nsxt_policy_security_policy_test.go @@ -359,6 +359,7 @@ func TestAccResourceNsxtPolicySecurityPolicy_withIPCidrRange(t *testing.T) { resource.TestCheckResourceAttr(testResourceName, "rule.5.source_groups.#", "1"), resource.TestCheckResourceAttr(testResourceName, "rule.5.source_groups.0", policyRange), resource.TestCheckResourceAttr(testResourceName, "rule.5.destination_groups.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "rule.5.service_entries.#", "1"), ), }, { @@ -418,6 +419,7 @@ func TestAccResourceNsxtPolicySecurityPolicy_withIPCidrRange(t *testing.T) { resource.TestCheckResourceAttr(testResourceName, "rule.5.source_groups.#", "1"), resource.TestCheckResourceAttr(testResourceName, "rule.5.source_groups.0", updatedPolicyRange), resource.TestCheckResourceAttr(testResourceName, "rule.5.destination_groups.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "rule.5.service_entries.#", "1"), ), }, }, @@ -870,7 +872,6 @@ resource "nsxt_policy_service" "tcp778" { }` } -// TODO: add profiles when available func testAccNsxtPolicySecurityPolicyWithDepsCreate(name string) string { return testAccNsxtPolicySecurityPolicyDeps() + fmt.Sprintf(` resource "nsxt_policy_security_policy" "test" { @@ -963,44 +964,54 @@ func testAccNsxtPolicySecurityPolicyWithIPCidrRange(name string, destIP string, } rule { - display_name = "rule2" - source_groups = [nsxt_policy_group.group1.path] - destination_groups = ["%s"] - services = [nsxt_policy_service.icmp.path] - action = "ALLOW" + display_name = "rule2" + source_groups = [nsxt_policy_group.group1.path] + destination_groups = ["%s"] + services = [nsxt_policy_service.icmp.path] + action = "ALLOW" } rule { - display_name = "rule3" - source_groups = [nsxt_policy_group.group1.path] - destination_groups = ["%s"] - services = [nsxt_policy_service.icmp.path] - action = "ALLOW" - sequence_number = 50 + display_name = "rule3" + source_groups = [nsxt_policy_group.group1.path] + destination_groups = ["%s"] + services = [nsxt_policy_service.icmp.path] + action = "ALLOW" + sequence_number = 50 } rule { - display_name = "rule4" - source_groups = ["%s"] - destination_groups = [nsxt_policy_group.group2.path] - services = [nsxt_policy_service.icmp.path] - action = "ALLOW" + display_name = "rule4" + source_groups = ["%s"] + destination_groups = [nsxt_policy_group.group2.path] + services = [nsxt_policy_service.icmp.path] + action = "ALLOW" } rule { - display_name = "rule5" - source_groups = ["%s"] - destination_groups = [nsxt_policy_group.group2.path] - services = [nsxt_policy_service.icmp.path] - action = "ALLOW" - sequence_number = 105 + display_name = "rule5" + source_groups = ["%s"] + destination_groups = [nsxt_policy_group.group2.path] + services = [nsxt_policy_service.icmp.path] + action = "ALLOW" + sequence_number = 105 } rule { - display_name = "rule6" - source_groups = ["%s"] - destination_groups = [nsxt_policy_group.group2.path] - services = [nsxt_policy_service.icmp.path] - action = "ALLOW" + display_name = "rule6" + source_groups = ["%s"] + destination_groups = [nsxt_policy_group.group2.path] + service_entries { + icmp_entry { + display_name = "test" + icmp_type = "3" + protocol = "ICMPv4" + } + l4_port_set_entry { + protocol = "TCP" + destination_ports = ["8000-8080"] + } + } + action = "ALLOW" } }`, name, destIP, destCidr, destIPRange, sourceIP, sourceCidr, sourceIPRange) } diff --git a/nsxt/resource_nsxt_policy_service.go b/nsxt/resource_nsxt_policy_service.go index 3cfc4add1..7fdbcd4f8 100644 --- a/nsxt/resource_nsxt_policy_service.go +++ b/nsxt/resource_nsxt_policy_service.go @@ -19,6 +19,185 @@ import ( utl "github.com/vmware/terraform-provider-nsxt/api/utl" ) +func getIcmpEntrySchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Description: "ICMP type service entry", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "display_name": getOptionalDisplayNameSchema(false), + "description": getDescriptionSchema(), + "protocol": { + Type: schema.TypeString, + Description: "Version of ICMP protocol (ICMPv4/ICMPv6)", + Required: true, + ValidateFunc: validation.StringInSlice(icmpProtocolValues, false), + }, + "icmp_type": { + // NOTE: icmp_type is required if icmp_code is set + Type: schema.TypeString, + Description: "ICMP message type", + Optional: true, + ValidateFunc: validateStringIntBetween(0, 255), + }, + "icmp_code": { + Type: schema.TypeString, + Description: "ICMP message code", + Optional: true, + ValidateFunc: validateStringIntBetween(0, 255), + }, + }, + }, + } +} + +func getL4PortSetEntrySchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Description: "L4 port set type service entry", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "display_name": getOptionalDisplayNameSchema(false), + "description": getDescriptionSchema(), + "destination_ports": { + Type: schema.TypeSet, + Description: "Set of destination ports", + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePortRange(), + }, + Optional: true, + }, + "source_ports": { + Type: schema.TypeSet, + Description: "Set of source ports", + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePortRange(), + }, + Optional: true, + }, + "protocol": { + Type: schema.TypeString, + Description: "L4 Protocol", + Required: true, + ValidateFunc: validation.StringInSlice(protocolValues, false), + }, + }, + }, + } +} + +func getIgmpEntrySchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Description: "IGMP type service entry", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "display_name": getOptionalDisplayNameSchema(false), + "description": getDescriptionSchema(), + }, + }, + } +} + +func getEtherEntrySchema(addConflictDef bool) *schema.Schema { + s := &schema.Schema{ + Type: schema.TypeSet, + Description: "Ether type service entry", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "display_name": getOptionalDisplayNameSchema(false), + "description": getDescriptionSchema(), + "ether_type": { + Type: schema.TypeInt, + Description: "Type of the encapsulated protocol", + Required: true, + }, + }, + }, + } + if addConflictDef { + s.ConflictsWith = []string{"algorithm_entry", "igmp_entry", "icmp_entry", "l4_port_set_entry", "ip_protocol_entry"} + } + + return s +} + +func getIPProtocolEntrySchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Description: "IP Protocol type service entry", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "display_name": getOptionalDisplayNameSchema(false), + "description": getDescriptionSchema(), + "protocol": { + Type: schema.TypeInt, + Description: "IP protocol number", + Required: true, + ValidateFunc: validation.IntBetween(0, 255), + }, + }, + }, + } +} + +func getAlgorithmEntrySchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Description: "Algorithm type service entry", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "display_name": getOptionalDisplayNameSchema(false), + "description": getDescriptionSchema(), + "destination_port": { + Type: schema.TypeString, + Description: "A single destination port", + Required: true, + ValidateFunc: validateSinglePort(), + }, + "source_ports": { + Type: schema.TypeSet, + Description: "Set of source ports or ranges", + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePortRange(), + }, + Optional: true, + }, + "algorithm": { + Type: schema.TypeString, + Description: "Algorithm", + Required: true, + ValidateFunc: validation.StringInSlice(algTypeValues, false), + }, + }, + }, + } +} + +func getNestedServiceEntrySchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Description: "Nested service service entry", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "display_name": getOptionalDisplayNameSchema(false), + "description": getDescriptionSchema(), + "nested_service_path": getPolicyPathSchema(true, false, "Nested Service Path"), + }, + }, + } +} + func resourceNsxtPolicyService() *schema.Resource { return &schema.Resource{ Create: resourceNsxtPolicyServiceCreate, @@ -38,176 +217,50 @@ func resourceNsxtPolicyService() *schema.Resource { "tag": getTagsSchema(), "context": getContextSchema(false, false, false), - "icmp_entry": { - Type: schema.TypeSet, - Description: "ICMP type service entry", - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "display_name": getOptionalDisplayNameSchema(false), - "description": getDescriptionSchema(), - "protocol": { - Type: schema.TypeString, - Description: "Version of ICMP protocol (ICMPv4/ICMPv6)", - Required: true, - ValidateFunc: validation.StringInSlice(icmpProtocolValues, false), - }, - "icmp_type": { - // NOTE: icmp_type is required if icmp_code is set - Type: schema.TypeString, - Description: "ICMP message type", - Optional: true, - ValidateFunc: validateStringIntBetween(0, 255), - }, - "icmp_code": { - Type: schema.TypeString, - Description: "ICMP message code", - Optional: true, - ValidateFunc: validateStringIntBetween(0, 255), - }, - }, - }, - }, - - "l4_port_set_entry": { - Type: schema.TypeSet, - Description: "L4 port set type service entry", - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "display_name": getOptionalDisplayNameSchema(false), - "description": getDescriptionSchema(), - "destination_ports": { - Type: schema.TypeSet, - Description: "Set of destination ports", - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validatePortRange(), - }, - Optional: true, - }, - "source_ports": { - Type: schema.TypeSet, - Description: "Set of source ports", - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validatePortRange(), - }, - Optional: true, - }, - "protocol": { - Type: schema.TypeString, - Description: "L4 Protocol", - Required: true, - ValidateFunc: validation.StringInSlice(protocolValues, false), - }, - }, - }, - }, - - "igmp_entry": { - Type: schema.TypeSet, - Description: "IGMP type service entry", - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "display_name": getOptionalDisplayNameSchema(false), - "description": getDescriptionSchema(), - }, - }, - }, + "icmp_entry": getIcmpEntrySchema(), + "l4_port_set_entry": getL4PortSetEntrySchema(), + "igmp_entry": getIgmpEntrySchema(), + "ether_type_entry": getEtherEntrySchema(true), + "ip_protocol_entry": getIPProtocolEntrySchema(), + "algorithm_entry": getAlgorithmEntrySchema(), + "nested_service_entry": getNestedServiceEntrySchema(), + }, + } +} - "ether_type_entry": { - Type: schema.TypeSet, - Description: "Ether type service entry", - Optional: true, - ConflictsWith: []string{"algorithm_entry", "igmp_entry", "icmp_entry", "l4_port_set_entry", "ip_protocol_entry"}, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "display_name": getOptionalDisplayNameSchema(false), - "description": getDescriptionSchema(), - "ether_type": { - Type: schema.TypeInt, - Description: "Type of the encapsulated protocol", - Required: true, - }, - }, - }, - }, +func getServiceEntryListFromSchemaOrMap(d interface{}, attrName string) []interface{} { + // non-nested attribute + if resourceData, ok := d.(*schema.ResourceData); ok { + return resourceData.Get(attrName).(*schema.Set).List() + } - "ip_protocol_entry": { - Type: schema.TypeSet, - Description: "IP Protocol type service entry", - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "display_name": getOptionalDisplayNameSchema(false), - "description": getDescriptionSchema(), - "protocol": { - Type: schema.TypeInt, - Description: "IP protocol number", - Required: true, - ValidateFunc: validation.IntBetween(0, 255), - }, - }, - }, - }, + // nested attribute + nestedMap := d.(map[string]interface{}) + if v, ok := nestedMap[attrName]; ok { + return v.(*schema.Set).List() + } - "algorithm_entry": { - Type: schema.TypeSet, - Description: "Algorithm type service entry", - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "display_name": getOptionalDisplayNameSchema(false), - "description": getDescriptionSchema(), - "destination_port": { - Type: schema.TypeString, - Description: "A single destination port", - Required: true, - ValidateFunc: validateSinglePort(), - }, - "source_ports": { - Type: schema.TypeSet, - Description: "Set of source ports or ranges", - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validatePortRange(), - }, - Optional: true, - }, - "algorithm": { - Type: schema.TypeString, - Description: "Algorithm", - Required: true, - ValidateFunc: validation.StringInSlice(algTypeValues, false), - }, - }, - }, - }, + return nil +} - "nested_service_entry": { - Type: schema.TypeSet, - Description: "Nested service service entry", - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "display_name": getOptionalDisplayNameSchema(false), - "description": getDescriptionSchema(), - "nested_service_path": getPolicyPathSchema(true, false, "Nested Service Path"), - }, - }, - }, - }, +func setServiceEntryListInSchemaOrMap(d interface{}, attrName string, entries []map[string]interface{}) { + // non-nested attribute + if resourceData, ok := d.(*schema.ResourceData); ok { + resourceData.Set(attrName, entries) + return } + + // nested attribute + nestedMap := d.(map[string]interface{}) + nestedMap[attrName] = entries } -func resourceNsxtPolicyServiceGetEntriesFromSchema(d *schema.ResourceData) ([]*data.StructValue, error) { +func getServiceEntriesFromSchema(d interface{}) ([]*data.StructValue, error) { converter := bindings.NewTypeConverter() serviceEntries := []*data.StructValue{} // ICMP Type service entries - icmpEntries := d.Get("icmp_entry").(*schema.Set).List() + icmpEntries := getServiceEntryListFromSchemaOrMap(d, "icmp_entry") for _, icmpEntry := range icmpEntries { entryData := icmpEntry.(map[string]interface{}) // Type and code can be unset @@ -254,7 +307,7 @@ func resourceNsxtPolicyServiceGetEntriesFromSchema(d *schema.ResourceData) ([]*d } // L4 port set Type service entries - l4Entries := d.Get("l4_port_set_entry").(*schema.Set).List() + l4Entries := getServiceEntryListFromSchemaOrMap(d, "l4_port_set_entry") for _, l4Entry := range l4Entries { entryData := l4Entry.(map[string]interface{}) l4Protocol := entryData["protocol"].(string) @@ -284,7 +337,7 @@ func resourceNsxtPolicyServiceGetEntriesFromSchema(d *schema.ResourceData) ([]*d } // IGMP Type service entries - igmpEntries := d.Get("igmp_entry").(*schema.Set).List() + igmpEntries := getServiceEntryListFromSchemaOrMap(d, "igmp_entry") for _, igmpEntry := range igmpEntries { entryData := igmpEntry.(map[string]interface{}) displayName := entryData["display_name"].(string) @@ -308,7 +361,7 @@ func resourceNsxtPolicyServiceGetEntriesFromSchema(d *schema.ResourceData) ([]*d } // Ether Type service entries - etherEntries := d.Get("ether_type_entry").(*schema.Set).List() + etherEntries := getServiceEntryListFromSchemaOrMap(d, "ether_type_entry") for _, etherEntry := range etherEntries { entryData := etherEntry.(map[string]interface{}) displayName := entryData["display_name"].(string) @@ -334,7 +387,7 @@ func resourceNsxtPolicyServiceGetEntriesFromSchema(d *schema.ResourceData) ([]*d } // IP Protocol Type service entries - ipProtEntries := d.Get("ip_protocol_entry").(*schema.Set).List() + ipProtEntries := getServiceEntryListFromSchemaOrMap(d, "ip_protocol_entry") for _, ipProtEntry := range ipProtEntries { entryData := ipProtEntry.(map[string]interface{}) displayName := entryData["display_name"].(string) @@ -360,7 +413,7 @@ func resourceNsxtPolicyServiceGetEntriesFromSchema(d *schema.ResourceData) ([]*d } // Algorithm Type service entries - algEntries := d.Get("algorithm_entry").(*schema.Set).List() + algEntries := getServiceEntryListFromSchemaOrMap(d, "algorithm_entry") for _, algEntry := range algEntries { entryData := algEntry.(map[string]interface{}) displayName := entryData["display_name"].(string) @@ -391,7 +444,7 @@ func resourceNsxtPolicyServiceGetEntriesFromSchema(d *schema.ResourceData) ([]*d } // Nested Service service entries - nestedEntries := d.Get("nested_service_entry").(*schema.Set).List() + nestedEntries := getServiceEntryListFromSchemaOrMap(d, "nested_service_entry") for _, nestedEntry := range nestedEntries { entryData := nestedEntry.(map[string]interface{}) displayName := entryData["display_name"].(string) @@ -439,11 +492,14 @@ func resourceNsxtPolicyServiceExists(sessionContext utl.SessionContext, id strin return false, logAPIError("Error retrieving service", err) } -func filterServiceEntryDisplayName(entryDisplayName string, entryID string) string { - if entryDisplayName == entryID { +func filterServiceEntryDisplayName(entryDisplayName *string, entryID *string) string { + if entryDisplayName == nil { + return "" + } + if entryID != nil && *entryDisplayName == *entryID { return "" } - return entryDisplayName + return *entryDisplayName } func resourceNsxtPolicyServiceCreate(d *schema.ResourceData, m interface{}) error { @@ -458,7 +514,7 @@ func resourceNsxtPolicyServiceCreate(d *schema.ResourceData, m interface{}) erro displayName := d.Get("display_name").(string) description := d.Get("description").(string) tags := getPolicyTagsFromSchema(d) - serviceEntries, errc := resourceNsxtPolicyServiceGetEntriesFromSchema(d) + serviceEntries, errc := getServiceEntriesFromSchema(d) if errc != nil { return fmt.Errorf("Error during Service entries conversion: %v", errc) } @@ -510,7 +566,15 @@ func resourceNsxtPolicyServiceRead(d *schema.ResourceData, m interface{}) error d.Set("nsx_id", id) d.Set("path", obj.Path) d.Set("revision", obj.Revision) + err = setServiceEntriesInSchema(d, obj.ServiceEntries, true) + if err != nil { + return handleReadError(d, "Service", id, err) + } + return nil +} + +func setServiceEntriesInSchema(d interface{}, serviceEntries []*data.StructValue, nestedSupported bool) error { // Translate the returned service entries converter := bindings.NewTypeConverter() var icmpEntriesList []map[string]interface{} @@ -521,7 +585,7 @@ func resourceNsxtPolicyServiceRead(d *schema.ResourceData, m interface{}) error var algEntriesList []map[string]interface{} var nestedServiceEntriesList []map[string]interface{} - for _, entry := range obj.ServiceEntries { + for _, entry := range serviceEntries { elem := make(map[string]interface{}) base, errs := converter.ConvertToGolang(entry, model.ServiceEntryBindingType()) resourceType := base.(model.ServiceEntry).ResourceType @@ -536,7 +600,7 @@ func resourceNsxtPolicyServiceRead(d *schema.ResourceData, m interface{}) error } serviceEntry := icmpEntry.(model.ICMPTypeServiceEntry) - elem["display_name"] = filterServiceEntryDisplayName(*serviceEntry.DisplayName, *serviceEntry.Id) + elem["display_name"] = filterServiceEntryDisplayName(serviceEntry.DisplayName, serviceEntry.Id) elem["description"] = serviceEntry.Description if serviceEntry.IcmpType != nil { elem["icmp_type"] = strconv.Itoa(int(*serviceEntry.IcmpType)) @@ -557,7 +621,7 @@ func resourceNsxtPolicyServiceRead(d *schema.ResourceData, m interface{}) error } serviceEntry := l4Entry.(model.L4PortSetServiceEntry) - elem["display_name"] = filterServiceEntryDisplayName(*serviceEntry.DisplayName, *serviceEntry.Id) + elem["display_name"] = filterServiceEntryDisplayName(serviceEntry.DisplayName, serviceEntry.Id) elem["description"] = serviceEntry.Description elem["destination_ports"] = serviceEntry.DestinationPorts elem["source_ports"] = serviceEntry.SourcePorts @@ -570,7 +634,7 @@ func resourceNsxtPolicyServiceRead(d *schema.ResourceData, m interface{}) error } serviceEntry := etherEntry.(model.EtherTypeServiceEntry) - elem["display_name"] = filterServiceEntryDisplayName(*serviceEntry.DisplayName, *serviceEntry.Id) + elem["display_name"] = filterServiceEntryDisplayName(serviceEntry.DisplayName, serviceEntry.Id) elem["description"] = serviceEntry.Description elem["ether_type"] = serviceEntry.EtherType etherEntriesList = append(etherEntriesList, elem) @@ -581,7 +645,7 @@ func resourceNsxtPolicyServiceRead(d *schema.ResourceData, m interface{}) error } serviceEntry := ipProtEntry.(model.IPProtocolServiceEntry) - elem["display_name"] = filterServiceEntryDisplayName(*serviceEntry.DisplayName, *serviceEntry.Id) + elem["display_name"] = filterServiceEntryDisplayName(serviceEntry.DisplayName, serviceEntry.Id) elem["description"] = serviceEntry.Description elem["protocol"] = serviceEntry.ProtocolNumber ipProtEntriesList = append(ipProtEntriesList, elem) @@ -592,7 +656,7 @@ func resourceNsxtPolicyServiceRead(d *schema.ResourceData, m interface{}) error } serviceEntry := algEntry.(model.ALGTypeServiceEntry) - elem["display_name"] = filterServiceEntryDisplayName(*serviceEntry.DisplayName, *serviceEntry.Id) + elem["display_name"] = filterServiceEntryDisplayName(serviceEntry.DisplayName, serviceEntry.Id) elem["description"] = serviceEntry.Description elem["algorithm"] = serviceEntry.Alg elem["destination_port"] = serviceEntry.DestinationPorts[0] @@ -605,7 +669,7 @@ func resourceNsxtPolicyServiceRead(d *schema.ResourceData, m interface{}) error } serviceEntry := igmpEntry.(model.IGMPTypeServiceEntry) - elem["display_name"] = filterServiceEntryDisplayName(*serviceEntry.DisplayName, *serviceEntry.Id) + elem["display_name"] = filterServiceEntryDisplayName(serviceEntry.DisplayName, serviceEntry.Id) elem["description"] = serviceEntry.Description igmpEntriesList = append(igmpEntriesList, elem) } else if resourceType == model.ServiceEntry_RESOURCE_TYPE_NESTEDSERVICESERVICEENTRY { @@ -615,7 +679,7 @@ func resourceNsxtPolicyServiceRead(d *schema.ResourceData, m interface{}) error } serviceEntry := nestedEntry.(model.NestedServiceServiceEntry) - elem["display_name"] = filterServiceEntryDisplayName(*serviceEntry.DisplayName, *serviceEntry.Id) + elem["display_name"] = filterServiceEntryDisplayName(serviceEntry.DisplayName, serviceEntry.Id) elem["description"] = serviceEntry.Description elem["nested_service_path"] = serviceEntry.NestedServicePath nestedServiceEntriesList = append(nestedServiceEntriesList, elem) @@ -625,39 +689,14 @@ func resourceNsxtPolicyServiceRead(d *schema.ResourceData, m interface{}) error } } - err = d.Set("icmp_entry", icmpEntriesList) - if err != nil { - return err - } - - err = d.Set("l4_port_set_entry", l4EntriesList) - if err != nil { - return err - } - - err = d.Set("igmp_entry", igmpEntriesList) - if err != nil { - return err - } - - err = d.Set("ether_type_entry", etherEntriesList) - if err != nil { - return err - } - - err = d.Set("ip_protocol_entry", ipProtEntriesList) - if err != nil { - return err - } - - err = d.Set("algorithm_entry", algEntriesList) - if err != nil { - return err - } - - err = d.Set("nested_service_entry", nestedServiceEntriesList) - if err != nil { - return err + setServiceEntryListInSchemaOrMap(d, "icmp_entry", icmpEntriesList) + setServiceEntryListInSchemaOrMap(d, "l4_port_set_entry", l4EntriesList) + setServiceEntryListInSchemaOrMap(d, "igmp_entry", igmpEntriesList) + setServiceEntryListInSchemaOrMap(d, "ether_type_entry", etherEntriesList) + setServiceEntryListInSchemaOrMap(d, "ip_protocol_entry", ipProtEntriesList) + setServiceEntryListInSchemaOrMap(d, "algorithm_entry", algEntriesList) + if nestedSupported { + setServiceEntryListInSchemaOrMap(d, "nested_service_entry", nestedServiceEntriesList) } return nil @@ -676,7 +715,7 @@ func resourceNsxtPolicyServiceUpdate(d *schema.ResourceData, m interface{}) erro description := d.Get("description").(string) revision := int64(d.Get("revision").(int)) tags := getPolicyTagsFromSchema(d) - serviceEntries, errc := resourceNsxtPolicyServiceGetEntriesFromSchema(d) + serviceEntries, errc := getServiceEntriesFromSchema(d) if errc != nil { return fmt.Errorf("Error during Service entries conversion: %v", errc) } diff --git a/nsxt/resource_nsxt_policy_service_test.go b/nsxt/resource_nsxt_policy_service_test.go index d02db1924..91db8e318 100644 --- a/nsxt/resource_nsxt_policy_service_test.go +++ b/nsxt/resource_nsxt_policy_service_test.go @@ -1149,7 +1149,7 @@ func testAccNsxtPolicyNestedServiceMixedTemplate(serviceName string, nestedServi nested_service_entry { display_name = "%s" description = "Entry-1" - nested_service_path = "%s" + nested_service_path = "%s" } l4_port_set_entry { diff --git a/website/docs/r/policy_gateway_policy.html.markdown b/website/docs/r/policy_gateway_policy.html.markdown index 157e91ccd..432a4405f 100644 --- a/website/docs/r/policy_gateway_policy.html.markdown +++ b/website/docs/r/policy_gateway_policy.html.markdown @@ -152,6 +152,30 @@ The following arguments are supported: * `profiles` - (Optional) A list of context profiles for the rule. Note: due to platform issue, this setting is only supported with NSX 3.2 onwards. * `scope` - (Required) List of policy paths where the rule is applied. * `services` - (Optional) List of services to match. + * `service_entries` - (Optional) Set of explicit protocol/port service definition + * `icmp_entry` - (Optional) Set of ICMP type service entries + * `display_name` - (Optional) Display name of the service entry + * `protocol` - (Required) Version of ICMP protocol: `ICMPv4` or `ICMPv6` + * `icmp_code` - (Optional) ICMP message code + * `icmp_type` - (Optional) ICMP message type + * `l4_port_set_entry` - (Optional) Set of L4 ports set service entries + * `display_name` - (Optional) Display name of the service entry + * `protocol` - (Required) L4 protocol: `TCP` or `UDP` + * `destination_ports` - (Optional) Set of destination ports + * `source_ports` - (Optional) Set of source ports + * `igmp_entry` - (Optional) Set of IGMP type service entries + * `display_name` - (Optional) Display name of the service entry + * `ether_type_entry` - (Optional) Set of Ether type service entries + * `display_name` - (Optional) Display name of the service entry + * `ether_type` - (Required) Type of the encapsulated protocol + * `ip_protocol_entry` - (Optional) Set of IP Protocol type service entries + * `display_name` - (Optional) Display name of the service entry + * `protocol` - (Required) IP protocol number + * `algorithm_entry` - (Optional) Set of Algorithm type service entries + * `display_name` - (Optional) Display name of the service entry + * `destination_port` - (Required) a single destination port + * `source_ports` - (Optional) Set of source ports/ranges + * `algorithm` - (Required) Algorithm: `FTP` or `TFTP` * `source_groups` - (Optional) Set of group paths that serve as the source for this rule. IPs, IP ranges, or CIDRs may also be used starting in NSX-T 3.0. An empty set can be used to specify "Any". * `source_excluded` - (Optional) A boolean value indicating negation of source groups. * `log_label` - (Optional) Additional information (string) which will be propagated to the rule syslog. diff --git a/website/docs/r/policy_security_policy.html.markdown b/website/docs/r/policy_security_policy.html.markdown index 6f4df046f..471c24ef3 100644 --- a/website/docs/r/policy_security_policy.html.markdown +++ b/website/docs/r/policy_security_policy.html.markdown @@ -124,7 +124,12 @@ resource "nsxt_policy_security_policy" "policy1" { sources_excluded = true scope = [nsxt_policy_group.aquarium.path] action = "ALLOW" - services = [nsxt_policy_service.udp.path] + service_entries { + l4_port_set_entry { + protocol = "UDP" + destination_ports = ["9080"] + } + } logged = true disabled = true notes = "Disabled by starfish for debugging" @@ -172,6 +177,30 @@ The following arguments are supported: * `profiles` - (Optional) Set of profile paths relevant for this rule. * `scope` - (Optional) Set of policy object paths where the rule is applied. * `services` - (Optional) Set of service paths to match. + * `service_entries` - (Optional) Set of explicit protocol/port service definition + * `icmp_entry` - (Optional) Set of ICMP type service entries + * `display_name` - (Optional) Display name of the service entry + * `protocol` - (Required) Version of ICMP protocol: `ICMPv4` or `ICMPv6` + * `icmp_code` - (Optional) ICMP message code + * `icmp_type` - (Optional) ICMP message type + * `l4_port_set_entry` - (Optional) Set of L4 ports set service entries + * `display_name` - (Optional) Display name of the service entry + * `protocol` - (Required) L4 protocol: `TCP` or `UDP` + * `destination_ports` - (Optional) Set of destination ports + * `source_ports` - (Optional) Set of source ports + * `igmp_entry` - (Optional) Set of IGMP type service entries + * `display_name` - (Optional) Display name of the service entry + * `ether_type_entry` - (Optional) Set of Ether type service entries + * `display_name` - (Optional) Display name of the service entry + * `ether_type` - (Required) Type of the encapsulated protocol + * `ip_protocol_entry` - (Optional) Set of IP Protocol type service entries + * `display_name` - (Optional) Display name of the service entry + * `protocol` - (Required) IP protocol number + * `algorithm_entry` - (Optional) Set of Algorithm type service entries + * `display_name` - (Optional) Display name of the service entry + * `destination_port` - (Required) a single destination port + * `source_ports` - (Optional) Set of source ports/ranges + * `algorithm` - (Required) Algorithm: one of `ORACLE_TNS`, `FTP`, `SUN_RPC_TCP`, `SUN_RPC_UDP`, `MS_RPC_TCP`, `MS_RPC_UDP`, `NBNS_BROADCAST`(Deprecated), `NBDG_BROADCAST`(Deprecated), `TFTP` * `log_label` - (Optional) Additional information (string) which will be propagated to the rule syslog. * `tag` - (Optional) A list of scope + tag pairs to associate with this Rule. * `sequence_number` - (Optional) It is recommended not to specify sequence number for rules, and rely on provider to auto-assign them. If you choose to specify sequence numbers, you must make sure the numbers are consistent with order of the rules in configuration. Please note that sequence numbers should start with 1 and not 0. To avoid confusion, either specify sequence numbers in all rules, or none at all. diff --git a/website/docs/r/policy_security_policy_rule.html.markdown b/website/docs/r/policy_security_policy_rule.html.markdown index f696e6b8a..fcfe9d438 100644 --- a/website/docs/r/policy_security_policy_rule.html.markdown +++ b/website/docs/r/policy_security_policy_rule.html.markdown @@ -69,6 +69,30 @@ The following arguments are supported: * `profiles` - (Optional) Set of profile paths relevant for this rule. * `scope` - (Optional) Set of policy object paths where the rule is applied. * `services` - (Optional) Set of service paths to match. +* `service_entries` - (Optional) Set of explicit protocol/port service definition + * `icmp_entry` - (Optional) Set of ICMP type service entries + * `display_name` - (Optional) Display name of the service entry + * `protocol` - (Required) Version of ICMP protocol: `ICMPv4` or `ICMPv6` + * `icmp_code` - (Optional) ICMP message code + * `icmp_type` - (Optional) ICMP message type + * `l4_port_set_entry` - (Optional) Set of L4 ports set service entries + * `display_name` - (Optional) Display name of the service entry + * `protocol` - (Required) L4 protocol: `TCP` or `UDP` + * `destination_ports` - (Optional) Set of destination ports + * `source_ports` - (Optional) Set of source ports + * `igmp_entry` - (Optional) Set of IGMP type service entries + * `display_name` - (Optional) Display name of the service entry + * `ether_type_entry` - (Optional) Set of Ether type service entries + * `display_name` - (Optional) Display name of the service entry + * `ether_type` - (Required) Type of the encapsulated protocol + * `ip_protocol_entry` - (Optional) Set of IP Protocol type service entries + * `display_name` - (Optional) Display name of the service entry + * `protocol` - (Required) IP protocol number + * `algorithm_entry` - (Optional) Set of Algorithm type service entries + * `display_name` - (Optional) Display name of the service entry + * `destination_port` - (Required) a single destination port + * `source_ports` - (Optional) Set of source ports/ranges + * `algorithm` - (Required) Algorithm: one of `ORACLE_TNS`, `FTP`, `SUN_RPC_TCP`, `SUN_RPC_UDP`, `MS_RPC_TCP`, `MS_RPC_UDP`, `NBNS_BROADCAST`(Deprecated), `NBDG_BROADCAST`(Deprecated), `TFTP` * `log_label` - (Optional) Additional information (string) which will be propagated to the rule syslog.