diff --git a/nutanix/data_source_nutanix_network_security_rule.go b/nutanix/data_source_nutanix_network_security_rule.go index 05c30a42b..650c514f6 100644 --- a/nutanix/data_source_nutanix_network_security_rule.go +++ b/nutanix/data_source_nutanix_network_security_rule.go @@ -122,6 +122,8 @@ func dataSourceNutanixNetworkSecurityRule() *schema.Resource { }, }, }, + "service_group_list": referenceListSchema(), + "address_group_inclusion_list": referenceListSchema(), "filter_kind_list": { Type: schema.TypeList, Computed: true, @@ -529,6 +531,8 @@ func dataSourceNutanixNetworkSecurityRule() *schema.Resource { }, }, }, + "service_group_list": referenceListSchema(), + "address_group_inclusion_list": referenceListSchema(), "filter_kind_list": { Type: schema.TypeList, Computed: true, @@ -703,6 +707,8 @@ func dataSourceNutanixNetworkSecurityRule() *schema.Resource { }, }, }, + "service_group_list": referenceListSchema(), + "address_group_inclusion_list": referenceListSchema(), "filter_kind_list": { Type: schema.TypeList, Computed: true, @@ -849,6 +855,8 @@ func dataSourceNutanixNetworkSecurityRule() *schema.Resource { }, }, }, + "service_group_list": referenceListSchema(), + "address_group_inclusion_list": referenceListSchema(), "filter_kind_list": { Type: schema.TypeList, Computed: true, @@ -1016,6 +1024,14 @@ func dataSourceNutanixNetworkSecurityRuleRead(ctx context.Context, d *schema.Res qroaItem["udp_port_range_list"] = udpprList } + if v.AddressGroupInclusionList != nil { + qroaItem["address_group_inclusion_list"] = flattenArrayReferenceValues(v.AddressGroupInclusionList) + } + + if v.ServiceGroupList != nil { + qroaItem["service_group_list"] = flattenArrayReferenceValues(v.ServiceGroupList) + } + if v.Filter != nil { qroaItem["filter_kind_list"] = utils.StringValueSlice(v.Filter.KindList) qroaItem["filter_type"] = utils.StringValue(v.Filter.Type) @@ -1118,6 +1134,14 @@ func dataSourceNutanixNetworkSecurityRuleRead(ctx context.Context, d *schema.Res qriaItem["udp_port_range_list"] = udpprList } + if v.AddressGroupInclusionList != nil { + qriaItem["address_group_inclusion_list"] = flattenArrayReferenceValues(v.AddressGroupInclusionList) + } + + if v.ServiceGroupList != nil { + qriaItem["service_group_list"] = flattenArrayReferenceValues(v.ServiceGroupList) + } + if v.Filter != nil { if v.Filter.KindList != nil { fkl := v.Filter.KindList @@ -1351,6 +1375,8 @@ func resourceNutanixDatasourceNetworkSecurityRuleResourceV0() *schema.Resource { }, }, }, + "service_group_list": referenceListSchema(), + "address_group_inclusion_list": referenceListSchema(), "filter_kind_list": { Type: schema.TypeList, Computed: true, @@ -1497,6 +1523,8 @@ func resourceNutanixDatasourceNetworkSecurityRuleResourceV0() *schema.Resource { }, }, }, + "service_group_list": referenceListSchema(), + "address_group_inclusion_list": referenceListSchema(), "filter_kind_list": { Type: schema.TypeList, Computed: true, @@ -1612,6 +1640,8 @@ func resourceNutanixDatasourceNetworkSecurityRuleResourceV0() *schema.Resource { }, }, }, + "service_group_list": referenceListSchema(), + "address_group_inclusion_list": referenceListSchema(), "filter_kind_list": { Type: schema.TypeList, Computed: true, @@ -1758,6 +1788,8 @@ func resourceNutanixDatasourceNetworkSecurityRuleResourceV0() *schema.Resource { }, }, }, + "service_group_list": referenceListSchema(), + "address_group_inclusion_list": referenceListSchema(), "filter_kind_list": { Type: schema.TypeList, Computed: true, diff --git a/nutanix/resource_nutanix_network_security_rule.go b/nutanix/resource_nutanix_network_security_rule.go index 02c954459..9409129c7 100644 --- a/nutanix/resource_nutanix_network_security_rule.go +++ b/nutanix/resource_nutanix_network_security_rule.go @@ -118,8 +118,10 @@ func resourceNutanixNetworkSecurityRule() *schema.Resource { Optional: true, Computed: true, }, - "tcp_port_range_list": portRangeSchema(), - "udp_port_range_list": portRangeSchema(), + "tcp_port_range_list": portRangeSchema(), + "udp_port_range_list": portRangeSchema(), + "service_group_list": referenceListSchema(), + "address_group_inclusion_list": referenceListSchema(), "filter_kind_list": { Type: schema.TypeList, Optional: true, @@ -250,8 +252,10 @@ func resourceNutanixNetworkSecurityRule() *schema.Resource { Optional: true, Computed: true, }, - "tcp_port_range_list": portRangeSchema(), - "udp_port_range_list": portRangeSchema(), + "tcp_port_range_list": portRangeSchema(), + "udp_port_range_list": portRangeSchema(), + "service_group_list": referenceListSchema(), + "address_group_inclusion_list": referenceListSchema(), "filter_kind_list": { Type: schema.TypeList, Optional: true, @@ -414,8 +418,10 @@ func resourceNutanixNetworkSecurityRule() *schema.Resource { Optional: true, Computed: true, }, - "tcp_port_range_list": portRangeSchema(), - "udp_port_range_list": portRangeSchema(), + "tcp_port_range_list": portRangeSchema(), + "udp_port_range_list": portRangeSchema(), + "service_group_list": referenceListSchema(), + "address_group_inclusion_list": referenceListSchema(), "filter_kind_list": { Type: schema.TypeList, Optional: true, @@ -546,8 +552,10 @@ func resourceNutanixNetworkSecurityRule() *schema.Resource { Optional: true, Computed: true, }, - "tcp_port_range_list": portRangeSchema(), - "udp_port_range_list": portRangeSchema(), + "tcp_port_range_list": portRangeSchema(), + "udp_port_range_list": portRangeSchema(), + "service_group_list": referenceListSchema(), + "address_group_inclusion_list": referenceListSchema(), "filter_kind_list": { Type: schema.TypeList, Optional: true, @@ -1129,6 +1137,7 @@ func expandNetworkRule(prefix string, d *schema.ResourceData) *v3.NetworkSecurit for k, v := range oal { nr := v.(map[string]interface{}) nrItem := &v3.NetworkRule{} + setIPSubnet := false iPSubnet := &v3.IPSubnet{} filter := &v3.CategoryFilter{} @@ -1138,14 +1147,20 @@ func expandNetworkRule(prefix string, d *schema.ResourceData) *v3.NetworkSecurit if ip, ipok := nr["ip_subnet"]; ipok && ip.(string) != "" { iPSubnet.IP = utils.StringPtr(ip.(string)) + setIPSubnet = true } if ippl, ipok := nr["ip_subnet_prefix_length"]; ipok && ippl.(string) != "" { if i, err := strconv.Atoi(ippl.(string)); err == nil { iPSubnet.PrefixLength = utils.Int64Ptr(int64(i)) + setIPSubnet = true } } + if setIPSubnet { + nrItem.IPSubnet = iPSubnet + } + if t, tcpok := nr["tcp_port_range_list"]; tcpok { nrItem.TCPPortRangeList = expandPortRangeList(t) } @@ -1154,6 +1169,14 @@ func expandNetworkRule(prefix string, d *schema.ResourceData) *v3.NetworkSecurit nrItem.UDPPortRangeList = expandPortRangeList(u) } + if a, agok := nr["address_group_inclusion_list"]; agok { + nrItem.AddressGroupInclusionList = expandReferencesList(a) + } + + if s, sgok := nr["service_group_list"]; sgok { + nrItem.ServiceGroupList = expandReferencesList(s) + } + if f, fok := nr["filter_kind_list"]; fok && len(f.([]interface{})) > 0 { filter.KindList = expandStringList(f.([]interface{})) } @@ -1203,7 +1226,6 @@ func expandNetworkRule(prefix string, d *schema.ResourceData) *v3.NetworkSecurit nrItem.IcmpTypeCodeList = expandIcmpTypeCodeList(icmp) } - nrItem.IPSubnet = iPSubnet if !reflect.DeepEqual(*filter, v3.CategoryFilter{}) { nrItem.Filter = filter } @@ -1284,6 +1306,14 @@ func expandNetworkRule(prefix string, d *schema.ResourceData) *v3.NetworkSecurit nrItem.UDPPortRangeList = expandPortRangeList(u) } + if a, agok := nr["address_group_inclusion_list"]; agok { + nrItem.AddressGroupInclusionList = expandReferencesList(a) + } + + if s, sgok := nr["service_group_list"]; sgok { + nrItem.ServiceGroupList = expandReferencesList(s) + } + if f, fok := nr["filter_kind_list"]; fok && len(f.([]interface{})) > 0 { filter.KindList = expandStringList(f.([]interface{})) } @@ -1353,6 +1383,18 @@ func expandNetworkRule(prefix string, d *schema.ResourceData) *v3.NetworkSecurit return appRule } +func expandReferencesList(rl interface{}) []*v3.Reference { + refList := rl.([]interface{}) + refs := make([]*v3.Reference, len(refList)) + + for i, r := range refList { + reff := r.(map[string]interface{}) + refs[i] = expandReference(reff) + } + + return refs +} + func expandFilterParams(fp map[string][]string) []map[string]interface{} { fpList := make([]map[string]interface{}, 0) if len(fp) > 0 { @@ -1449,6 +1491,14 @@ func flattenNetworkRuleList(networkRules []*v3.NetworkRule) []map[string]interfa ruleItem["udp_port_range_list"] = udpprList } + if v.AddressGroupInclusionList != nil { + ruleItem["address_group_inclusion_list"] = flattenArrayReferenceValues(v.AddressGroupInclusionList) + } + + if v.ServiceGroupList != nil { + ruleItem["service_group_list"] = flattenArrayReferenceValues(v.ServiceGroupList) + } + if v.Filter != nil { ruleItem["filter_kind_list"] = utils.StringValueSlice(v.Filter.KindList) ruleItem["filter_type"] = utils.StringValue(v.Filter.Type) @@ -1483,6 +1533,32 @@ func flattenNetworkRuleList(networkRules []*v3.NetworkRule) []map[string]interfa return ruleList } +func referenceListSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kind": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + "uuid": { + Type: schema.TypeString, + Computed: true, + Optional: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + } +} + func portRangeSchema() *schema.Schema { return &schema.Schema{ Type: schema.TypeList, @@ -1585,8 +1661,10 @@ func resourceNutanixSecurityRuleInstanceResourceV0() *schema.Resource { Optional: true, Computed: true, }, - "tcp_port_range_list": portRangeSchema(), - "udp_port_range_list": portRangeSchema(), + "tcp_port_range_list": portRangeSchema(), + "udp_port_range_list": portRangeSchema(), + "service_group_list": referenceListSchema(), + "address_group_inclusion_list": referenceListSchema(), "filter_kind_list": { Type: schema.TypeList, Optional: true, @@ -1717,8 +1795,10 @@ func resourceNutanixSecurityRuleInstanceResourceV0() *schema.Resource { Optional: true, Computed: true, }, - "tcp_port_range_list": portRangeSchema(), - "udp_port_range_list": portRangeSchema(), + "tcp_port_range_list": portRangeSchema(), + "udp_port_range_list": portRangeSchema(), + "service_group_list": referenceListSchema(), + "address_group_inclusion_list": referenceListSchema(), "filter_kind_list": { Type: schema.TypeList, Optional: true, diff --git a/nutanix/resource_nutanix_network_security_rule_test.go b/nutanix/resource_nutanix_network_security_rule_test.go index a9e632e22..1ed1a3837 100644 --- a/nutanix/resource_nutanix_network_security_rule_test.go +++ b/nutanix/resource_nutanix_network_security_rule_test.go @@ -109,6 +109,78 @@ func TestAccNutanixNetworkSecurityRule_adrule(t *testing.T) { }) } +func TestAccNutanixNetworkSecurityRuleWithServiceAndAddressGroupsInInbound(t *testing.T) { + // Skipped because this test didn't pass in GCP environment + if isGCPEnvironment() { + t.Skip() + } + + rInt := acctest.RandInt() + sgName := fmt.Sprintf("tf-service-group-%d", rInt) + agName := fmt.Sprintf("tf-addr-group-%d", rInt) + securityPolicyName := fmt.Sprintf("tf-sec-policy-%d", rInt) + + resourceName := "nutanix_network_security_rule.VDI" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNutanixNetworkSecurityRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testNetworkSecurityRuleConfigWithServiceAndAddressGroupsInInbound(sgName, agName, securityPolicyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckNutanixNetworkSecurityRuleExists(resourceName), + ), + }, + // { + // Config: testNetworkSecurityRuleConfigWithServiceAndAddressGroupsInOutbound(sgName, agName, securityPolicyName), + // Check: resource.ComposeTestCheckFunc( + // testAccCheckNutanixNetworkSecurityRuleExists(resourceName), + // ), + // }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccNutanixNetworkSecurityRuleWithServiceAndAddressGroupsInOutbound(t *testing.T) { + // Skipped because this test didn't pass in GCP environment + if isGCPEnvironment() { + t.Skip() + } + + rInt := acctest.RandInt() + sgName := fmt.Sprintf("tf-service-group-%d", rInt) + agName := fmt.Sprintf("tf-addr-group-%d", rInt) + securityPolicyName := fmt.Sprintf("tf-sec-policy-%d", rInt) + + resourceName := "nutanix_network_security_rule.VDI" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNutanixNetworkSecurityRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testNetworkSecurityRuleConfigWithServiceAndAddressGroupsInOutbound(sgName, agName, securityPolicyName), + Check: resource.ComposeTestCheckFunc( + testAccCheckNutanixNetworkSecurityRuleExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckNutanixNetworkSecurityRuleExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -455,3 +527,146 @@ func testAccNutanixNetworkSecurityRuleConfigAdRuleUpdate(r int) string { } `, testVars.AdRuleTarget.Values, r, testVars.AdRuleTarget.Values) } + +func testNetworkSecurityRuleConfigWithServiceAndAddressGroupsInInbound(sgName, agName, securityPolicyName string) string { + return fmt.Sprintf(` + resource "nutanix_category_value" "ad-group-user-1" { + name = "ADGroup" + description = "group user category value" + value = "%s" + } + resource "nutanix_network_security_rule" "VDI" { + name = "%s-in" + ad_rule_action = "APPLY" + description = "test" + # app_rule_action = "APPLY" + ad_rule_inbound_allow_list { + peer_specification_type = "ALL" + service_group_list { + kind = "service_group" + uuid = nutanix_service_group.service1.id + } + address_group_inclusion_list { + kind = "address_group" + uuid = nutanix_address_group.address1.id + } + } + ad_rule_target_group_default_internal_policy = "DENY_ALL" + ad_rule_target_group_filter_kind_list = [ + "vm" + ] + ad_rule_target_group_filter_params { + name = "ADGroup" + values = [ + "%s" + ] + } + ad_rule_target_group_filter_type = "CATEGORIES_MATCH_ALL" + ad_rule_target_group_peer_specification_type = "FILTER" + ad_rule_outbound_allow_list { + ip_subnet = "10.0.0.0" + ip_subnet_prefix_length = "8" + peer_specification_type = "IP_SUBNET" + protocol = "ALL" + } + depends_on = [nutanix_category_value.ad-group-user-1] + } + resource "nutanix_service_group" "service1" { + name = "%s-in" + description = "test" + + service_list { + protocol = "TCP" + tcp_port_range_list { + start_port = 22 + end_port = 22 + } + tcp_port_range_list { + start_port = 2222 + end_port = 2222 + } + } + } + resource "nutanix_address_group" "address1" { + name = "%s-in" + description = "test" + + ip_address_block_list { + ip = "10.0.0.0" + prefix_length = 24 + } + } +`, testVars.AdRuleTarget.Values, securityPolicyName, testVars.AdRuleTarget.Values, sgName, agName) +} + +func testNetworkSecurityRuleConfigWithServiceAndAddressGroupsInOutbound(sgName, agName, securityPolicyName string) string { + return fmt.Sprintf(` + resource "nutanix_category_value" "ad-group-user-1" { + name = "ADGroup" + description = "group user category value" + value = "%s" + } + resource "nutanix_network_security_rule" "VDI" { + name = "%s-out" + ad_rule_action = "APPLY" + description = "test" + # app_rule_action = "APPLY" + ad_rule_inbound_allow_list { + ip_subnet = "10.0.0.0" + ip_subnet_prefix_length = "8" + peer_specification_type = "IP_SUBNET" + protocol = "ALL" + } + ad_rule_target_group_default_internal_policy = "DENY_ALL" + ad_rule_target_group_filter_kind_list = [ + "vm" + ] + ad_rule_target_group_filter_params { + name = "ADGroup" + values = [ + "%s" + ] + } + ad_rule_target_group_filter_type = "CATEGORIES_MATCH_ALL" + ad_rule_target_group_peer_specification_type = "FILTER" + ad_rule_outbound_allow_list { + peer_specification_type = "ALL" + service_group_list { + kind = "service_group" + uuid = nutanix_service_group.service1.id + } + + address_group_inclusion_list { + kind = "address_group" + uuid = nutanix_address_group.address1.id + } + } + depends_on = [nutanix_category_value.ad-group-user-1] + } + resource "nutanix_service_group" "service1" { + name = "%s-out" + description = "test" + + service_list { + protocol = "TCP" + tcp_port_range_list { + start_port = 22 + end_port = 22 + } + tcp_port_range_list { + start_port = 2222 + end_port = 2222 + } + } + } + resource "nutanix_address_group" "address1" { + name = "%s-out" + description = "test" + + ip_address_block_list { + ip = "10.0.0.0" + prefix_length = 24 + } + } +`, testVars.AdRuleTarget.Values, securityPolicyName, testVars.AdRuleTarget.Values, sgName, agName) +} diff --git a/website/docs/r/network_security_rule.html.markdown b/website/docs/r/network_security_rule.html.markdown index 8a9afbe8c..06d5e95f4 100644 --- a/website/docs/r/network_security_rule.html.markdown +++ b/website/docs/r/network_security_rule.html.markdown @@ -264,6 +264,90 @@ resource "nutanix_network_security_rule" "TEST-TIER" { } ``` +### Usage with service and address groups +```hcl +resource "nutanix_service_group" "service1" { + name = "srv-1" + description = "test" + + service_list { + protocol = "TCP" + tcp_port_range_list { + start_port = 22 + end_port = 22 + } + tcp_port_range_list { + start_port = 2222 + end_port = 2222 + } + } +} + +resource "nutanix_address_group" "address1" { + name = "addr-1" + description = "test" + + ip_address_block_list { + ip = "10.0.0.0" + prefix_length = 24 + } +} + +resource "nutanix_category_value" "ad-group-user-1" { + name = "AD" + description = "group user category value" + value = "AD" +} + +resource "nutanix_network_security_rule" "VDI" { + name = "nsr-1" + ad_rule_action = "APPLY" + description = "test" + # app_rule_action = "APPLY" + ad_rule_inbound_allow_list { + ip_subnet = "10.0.0.0" + ip_subnet_prefix_length = "8" + peer_specification_type = "IP_SUBNET" + protocol = "ALL" + + # peer_specification_type = "ALL" + # service_group_list { + # kind = "service_group" + # uuid = nutanix_service_group.service1.id + # } + # address_group_inclusion_list { + # kind = "address_group" + # uuid = nutanix_address_group.address1.id + # } + } + ad_rule_target_group_default_internal_policy = "DENY_ALL" + ad_rule_target_group_filter_kind_list = [ + "vm" + ] + ad_rule_target_group_filter_params { + name = "AD" + values = [ + "AD" + ] + } + ad_rule_target_group_filter_type = "CATEGORIES_MATCH_ALL" + ad_rule_target_group_peer_specification_type = "FILTER" + ad_rule_outbound_allow_list { + peer_specification_type = "ALL" + service_group_list { + kind = "service_group" + uuid = nutanix_service_group.service1.id + } + + address_group_inclusion_list { + kind = "address_group" + uuid = nutanix_address_group.address1.id + } + } + depends_on = [nutanix_category_value.ad-group-user-1] +} +``` + ## Argument Reference The following arguments are supported: @@ -298,7 +382,7 @@ The following arguments are supported: * `ad_rule_target_group_filter_params`: - (Optional) - A list of category key and list of values. * `ad_rule_inbound_allow_list`: - (Optional) The set of categories that matching VMs need to have. * `isolation_rule_action`: - (Optional) - These rules are used for environmental isolation. -* `app_rule_inbound_allow_list`: - (Optional) +* `app_rule_outbound_allow_list`: - (Optional) * `isolation_rule_first_entity_filter_kind_list`: - (Optional) - List of kinds associated with this filter. * `isolation_rule_first_entity_filter_type`: - (Optional) - The type of the filter being used. * `isolation_rule_first_entity_filter_params`: - (Optional) - A list of category key and list of values.