diff --git a/nsxt/policy_common.go b/nsxt/policy_common.go index f7726497e..92b87d25b 100644 --- a/nsxt/policy_common.go +++ b/nsxt/policy_common.go @@ -275,6 +275,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", @@ -334,6 +343,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(), @@ -489,6 +509,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) } @@ -583,6 +611,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 34d0860a6..85e65dc05 100644 --- a/nsxt/resource_nsxt_policy_gateway_policy_test.go +++ b/nsxt/resource_nsxt_policy_gateway_policy_test.go @@ -502,6 +502,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"), ), }, { @@ -561,6 +562,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"), ), }, }, @@ -953,48 +955,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 = "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" + } } - - 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" - } + } }`, 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 7f1bd19a0..cfba54ddd 100644 --- a/nsxt/resource_nsxt_policy_security_policy_rule.go +++ b/nsxt/resource_nsxt_policy_security_policy_rule.go @@ -101,7 +101,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, @@ -123,6 +123,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) { @@ -211,6 +219,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 1d38e6a3f..7f6ad8049 100644 --- a/nsxt/resource_nsxt_policy_security_policy_rule_test.go +++ b/nsxt/resource_nsxt_policy_security_policy_rule_test.go @@ -71,6 +71,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"), ), }, { @@ -283,6 +286,21 @@ resource "nsxt_policy_security_policy_rule" "%s" { ip_version = "%s" sequence_number = %s + service_entries { + igmp_entry { + } + + l4_port_set_entry { + protocol = "TCP" + destination_ports = [ "443" ] + } + + l4_port_set_entry { + 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 51afd7908..dd2c58043 100644 --- a/nsxt/resource_nsxt_policy_security_policy_test.go +++ b/nsxt/resource_nsxt_policy_security_policy_test.go @@ -296,8 +296,10 @@ func TestAccResourceNsxtPolicySecurityPolicy_withIPCidrRange(t *testing.T) { updatedPolicyCidr := "10.10.40.0/22" updatedPolicyRange := "10.10.40.6-10.10.40.7" + // a bug in NSX would cause permadiff when display_name is used in service_entries + // this issue is fixed in 4.0.0 onwards resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t); testAccNSXVersion(t, "4.0.0") }, Providers: testAccProviders, CheckDestroy: func(state *terraform.State) error { return testAccNsxtPolicySecurityPolicyCheckDestroy(state, name, defaultDomain) @@ -360,6 +362,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"), ), }, { @@ -419,6 +422,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"), ), }, }, @@ -871,7 +875,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" { @@ -964,44 +967,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 dd7b9ec80..dde53ad22 100644 --- a/nsxt/resource_nsxt_policy_service.go +++ b/nsxt/resource_nsxt_policy_service.go @@ -22,6 +22,185 @@ import ( var servicePathExample = getMultitenancyPathExample("/infra/services/[service]") +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, @@ -41,176 +220,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 @@ -257,7 +310,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) @@ -287,7 +340,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) @@ -311,7 +364,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) @@ -337,7 +390,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) @@ -363,7 +416,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) @@ -394,7 +447,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) @@ -442,11 +495,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 { @@ -461,7 +517,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) } @@ -513,7 +569,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{} @@ -524,7 +588,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 @@ -539,7 +603,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)) @@ -560,7 +624,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 @@ -573,7 +637,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) @@ -584,7 +648,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) @@ -595,7 +659,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] @@ -608,7 +672,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 { @@ -618,7 +682,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) @@ -628,39 +692,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 @@ -679,7 +718,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 b63bb22f5..6e0148709 100644 --- a/nsxt/resource_nsxt_policy_service_test.go +++ b/nsxt/resource_nsxt_policy_service_test.go @@ -1150,7 +1150,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..b59d89cc9 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. @@ -172,6 +196,8 @@ In addition to arguments listed above, the following attributes are exported: * `sequence_number` - Sequence number for the rule. * `rule_id` - Unique positive number that is assigned by the system and is useful for debugging. +~> **NOTE:** `display_name` argument for service entries is not supported for NSX 3.2.x and below. + ## Importing An existing Gateway Policy can be [imported][docs-import] into this resource, via the following command: diff --git a/website/docs/r/policy_security_policy.html.markdown b/website/docs/r/policy_security_policy.html.markdown index 6f4df046f..0ee699b20 100644 --- a/website/docs/r/policy_security_policy.html.markdown +++ b/website/docs/r/policy_security_policy.html.markdown @@ -124,10 +124,15 @@ resource "nsxt_policy_security_policy" "policy1" { sources_excluded = true scope = [nsxt_policy_group.aquarium.path] action = "ALLOW" - services = [nsxt_policy_service.udp.path] - logged = true - disabled = true - notes = "Disabled by starfish for debugging" + service_entries { + l4_port_set_entry { + protocol = "UDP" + destination_ports = ["9080"] + } + } + logged = true + disabled = true + notes = "Disabled by starfish for debugging" } lifecycle { @@ -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. @@ -190,6 +219,8 @@ In addition to arguments listed above, the following attributes are exported: * `sequence_number` - Sequence number for the rule. * `rule_id` - Unique positive number that is assigned by the system and is useful for debugging. +~> **NOTE:** `display_name` argument for rule service entries is not supported for NSX 3.2.x and below. + ## Importing An existing security policy can be [imported][docs-import] into this resource, via the following command: diff --git a/website/docs/r/policy_security_policy_rule.html.markdown b/website/docs/r/policy_security_policy_rule.html.markdown index f696e6b8a..4a1c6dbd7 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. @@ -80,6 +104,8 @@ In addition to arguments listed above, the following attributes are exported: * `path` - The NSX path of the policy resource. * `rule_id` - Unique positive number that is assigned by the system and is useful for debugging. +~> **NOTE:** `display_name` argument for service entries is not supported for NSX 3.2.x and below. + ## Importing An existing security policy can be [imported][docs-import] into this resource, via the following command: