diff --git a/internal/services/containers/kubernetes_cluster_network_resource_test.go b/internal/services/containers/kubernetes_cluster_network_resource_test.go index 74b839e81fc5..424f81bbe522 100644 --- a/internal/services/containers/kubernetes_cluster_network_resource_test.go +++ b/internal/services/containers/kubernetes_cluster_network_resource_test.go @@ -101,6 +101,64 @@ func TestAccKubernetesCluster_advancedNetworkingNetworkDataplane(t *testing.T) { }) } +func TestAccKubernetesCluster_advancedNetworkingPolicies(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_kubernetes_cluster", "test") + r := KubernetesClusterResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.advancedNetworkingWithPolicies(data, "FQDN"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.advancedNetworkingWithPolicies(data, "L7"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccKubernetesCluster_advancedNetworkingPoliciesFQDNRequiresSecurity(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_kubernetes_cluster", "test") + r := KubernetesClusterResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.advancedNetworkingWithPoliciesSecurityDisabled(data, "FQDN"), + ExpectError: regexp.MustCompile("`network_profile.0.advanced_networking.0.advanced_network_policies` can only be set when `network_profile.0.advanced_networking.0.security_enabled` is set to `true`"), + }, + }) +} + +func TestAccKubernetesCluster_advancedNetworkingPoliciesL7RequiresSecurity(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_kubernetes_cluster", "test") + r := KubernetesClusterResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.advancedNetworkingWithPoliciesSecurityDisabled(data, "L7"), + ExpectError: regexp.MustCompile("`network_profile.0.advanced_networking.0.advanced_network_policies` can only be set when `network_profile.0.advanced_networking.0.security_enabled` is set to `true`"), + }, + }) +} + +func TestAccKubernetesCluster_advancedNetworkingPoliciesConflictsWithIstio(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_kubernetes_cluster", "test") + r := KubernetesClusterResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.advancedNetworkingWithPoliciesAndIstio(data, "L7"), + ExpectError: regexp.MustCompile("`network_profile.0.advanced_networking.0.advanced_network_policies` cannot be set when `service_mesh_profile.0.mode` is set to `Istio`"), + }, + }) +} + func TestAccKubernetesCluster_serviceMeshProfile(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_kubernetes_cluster", "test") r := KubernetesClusterResource{} @@ -1549,6 +1607,211 @@ resource "azurerm_kubernetes_cluster" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, networkPlugin) } +func (KubernetesClusterResource) advancedNetworkingWithPolicies(data acceptance.TestData, advancedNetworkPolicies string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-aks-%[1]d" + location = "%[2]s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvirtnet%[1]d" + address_space = ["10.0.0.0/8"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test" { + name = "acctestsubnet%[1]d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.1.0.0/16"] +} + +resource "azurerm_kubernetes_cluster" "test" { + name = "acctestaks%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + dns_prefix = "acctestaks%[1]d" + + linux_profile { + admin_username = "acctestuser%[1]d" + + ssh_key { + key_data = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqaZoyiz1qbdOQ8xEf6uEu1cCwYowo5FHtsBhqLoDnnp7KUTEBN+L2NxRIfQ781rxV6Iq5jSav6b2Q8z5KiseOlvKA/RF2wqU0UPYqQviQhLmW6THTpmrv/YkUCuzxDpsH7DUDhZcwySLKVVe0Qm3+5N2Ta6UYH3lsDf9R9wTP2K/+vAnflKebuypNlmocIvakFWoZda18FOmsOoIVXQ8HWFNCuw9ZCunMSN62QGamCe3dL5cXlkgHYv7ekJE15IA9aOJcM7e90oeTqo+7HTcWfdu0qQqPWY5ujyMw/llas8tsXY85LFqRnr3gJ02bAscjc477+X+j/gkpFoN1QEmt terraform@demo.tld" + } + } + + default_node_pool { + name = "default" + node_count = 2 + vm_size = "Standard_DS2_v2" + vnet_subnet_id = azurerm_subnet.test.id + upgrade_settings { + max_surge = "10%%" + } + } + + identity { + type = "SystemAssigned" + } + + network_profile { + network_plugin = "azure" + network_data_plane = "cilium" + + advanced_networking { + observability_enabled = true + security_enabled = true + advanced_network_policies = "%[3]s" + } + } +} +`, data.RandomInteger, data.Locations.Primary, advancedNetworkPolicies) +} + +func (KubernetesClusterResource) advancedNetworkingWithPoliciesSecurityDisabled(data acceptance.TestData, advancedNetworkPolicies string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-aks-%[1]d" + location = "%[2]s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvirtnet%[1]d" + address_space = ["10.0.0.0/8"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test" { + name = "acctestsubnet%[1]d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.1.0.0/16"] +} + +resource "azurerm_kubernetes_cluster" "test" { + name = "acctestaks%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + dns_prefix = "acctestaks%[1]d" + + linux_profile { + admin_username = "acctestuser%[1]d" + + ssh_key { + key_data = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqaZoyiz1qbdOQ8xEf6uEu1cCwYowo5FHtsBhqLoDnnp7KUTEBN+L2NxRIfQ781rxV6Iq5jSav6b2Q8z5KiseOlvKA/RF2wqU0UPYqQviQhLmW6THTpmrv/YkUCuzxDpsH7DUDhZcwySLKVVe0Qm3+5N2Ta6UYH3lsDf9R9wTP2K/+vAnflKebuypNlmocIvakFWoZda18FOmsOoIVXQ8HWFNCuw9ZCunMSN62QGamCe3dL5cXlkgHYv7ekJE15IA9aOJcM7e90oeTqo+7HTcWfdu0qQqPWY5ujyMw/llas8tsXY85LFqRnr3gJ02bAscjc477+X+j/gkpFoN1QEmt terraform@demo.tld" + } + } + + default_node_pool { + name = "default" + node_count = 2 + vm_size = "Standard_DS2_v2" + vnet_subnet_id = azurerm_subnet.test.id + upgrade_settings { + max_surge = "10%%" + } + } + + identity { + type = "SystemAssigned" + } + + network_profile { + network_plugin = "azure" + network_data_plane = "cilium" + + advanced_networking { + observability_enabled = true + security_enabled = false + advanced_network_policies = "%[3]s" + } + } +} +`, data.RandomInteger, data.Locations.Primary, advancedNetworkPolicies) +} + +func (KubernetesClusterResource) advancedNetworkingWithPoliciesAndIstio(data acceptance.TestData, advancedNetworkPolicies string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-aks-%[1]d" + location = "%[2]s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvirtnet%[1]d" + address_space = ["10.0.0.0/8"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test" { + name = "acctestsubnet%[1]d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.1.0.0/16"] +} + +resource "azurerm_kubernetes_cluster" "test" { + name = "acctestaks%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + dns_prefix = "acctestaks%[1]d" + + linux_profile { + admin_username = "acctestuser%[1]d" + + ssh_key { + key_data = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqaZoyiz1qbdOQ8xEf6uEu1cCwYowo5FHtsBhqLoDnnp7KUTEBN+L2NxRIfQ781rxV6Iq5jSav6b2Q8z5KiseOlvKA/RF2wqU0UPYqQviQhLmW6THTpmrv/YkUCuzxDpsH7DUDhZcwySLKVVe0Qm3+5N2Ta6UYH3lsDf9R9wTP2K/+vAnflKebuypNlmocIvakFWoZda18FOmsOoIVXQ8HWFNCuw9ZCunMSN62QGamCe3dL5cXlkgHYv7ekJE15IA9aOJcM7e90oeTqo+7HTcWfdu0qQqPWY5ujyMw/llas8tsXY85LFqRnr3gJ02bAscjc477+X+j/gkpFoN1QEmt terraform@demo.tld" + } + } + + default_node_pool { + name = "default" + node_count = 2 + vm_size = "Standard_DS2_v2" + vnet_subnet_id = azurerm_subnet.test.id + upgrade_settings { + max_surge = "10%%" + } + } + + identity { + type = "SystemAssigned" + } + + network_profile { + network_plugin = "azure" + network_data_plane = "cilium" + + advanced_networking { + observability_enabled = true + security_enabled = true + advanced_network_policies = "%[3]s" + } + } + + service_mesh_profile { + mode = "Istio" + } +} +`, data.RandomInteger, data.Locations.Primary, advancedNetworkPolicies) +} + func (KubernetesClusterResource) serviceMeshProfile(data acceptance.TestData, internalIngressEnabled bool, externalIngressEnabled bool) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/internal/services/containers/kubernetes_cluster_resource.go b/internal/services/containers/kubernetes_cluster_resource.go index 32389d4f0ad3..c33214151610 100644 --- a/internal/services/containers/kubernetes_cluster_resource.go +++ b/internal/services/containers/kubernetes_cluster_resource.go @@ -185,6 +185,15 @@ func resourceKubernetesCluster() *pluginsdk.Resource { return fmt.Errorf("when `network_profile.0.advanced_networking` has `security_enabled` set to `true`, `network_profile.0.network_plugin` must be set to `%s`", managedclusters.NetworkPluginAzure) } } + advancedNetworkPolicies := d.Get("network_profile.0.advanced_networking.0.advanced_network_policies").(string) + if !securityEnabled && advancedNetworkPolicies != "" { + return fmt.Errorf("`network_profile.0.advanced_networking.0.advanced_network_policies` can only be set when `network_profile.0.advanced_networking.0.security_enabled` is set to `true`") + } + if advancedNetworkPolicies != "" { + if mode := d.Get("service_mesh_profile.0.mode").(string); mode == string(managedclusters.ServiceMeshModeIstio) { + return fmt.Errorf("`network_profile.0.advanced_networking.0.advanced_network_policies` cannot be set when `service_mesh_profile.0.mode` is set to `%s`", managedclusters.ServiceMeshModeIstio) + } + } } return nil }, @@ -1366,6 +1375,15 @@ func resourceKubernetesCluster() *pluginsdk.Resource { Default: false, AtLeastOneOf: []string{"network_profile.0.advanced_networking.0.observability_enabled", "network_profile.0.advanced_networking.0.security_enabled"}, }, + "advanced_network_policies": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + string(managedclusters.AdvancedNetworkPoliciesFQDN), + string(managedclusters.AdvancedNetworkPoliciesLSeven), + // Note: Whilst the `None` value exists it's handled in the Create/Update and Read functions. + }, false), + }, }, }, }, @@ -3739,14 +3757,21 @@ func expandKubernetesClusterAdvancedNetworking(input []interface{}, d *pluginsdk observabilityEnabled := config["observability_enabled"].(bool) securityEnabled := config["security_enabled"].(bool) + advancedNetworkPolicies := managedclusters.AdvancedNetworkPoliciesNone + if v := config["advanced_network_policies"].(string); v != "" { + advancedNetworkPolicies = managedclusters.AdvancedNetworkPolicies(v) + } + security := &managedclusters.AdvancedNetworkingSecurity{ + Enabled: pointer.To(securityEnabled), + AdvancedNetworkPolicies: pointer.To(advancedNetworkPolicies), + } + return &managedclusters.AdvancedNetworking{ Enabled: pointer.To(true), Observability: &managedclusters.AdvancedNetworkingObservability{ Enabled: pointer.To(observabilityEnabled), }, - Security: &managedclusters.AdvancedNetworkingSecurity{ - Enabled: pointer.To(securityEnabled), - }, + Security: security, } } @@ -3761,14 +3786,19 @@ func flattenKubernetesClusterAdvancedNetworking(advancedNetworking *managedclust } securityEnabled := false + advancedNetworkPolicies := "" if advancedNetworking.Security != nil { securityEnabled = pointer.From(advancedNetworking.Security.Enabled) + if v := advancedNetworking.Security.AdvancedNetworkPolicies; v != nil && *v != managedclusters.AdvancedNetworkPoliciesNone { + advancedNetworkPolicies = string(*v) + } } return []interface{}{ map[string]interface{}{ - "observability_enabled": observabilityEnabled, - "security_enabled": securityEnabled, + "observability_enabled": observabilityEnabled, + "security_enabled": securityEnabled, + "advanced_network_policies": advancedNetworkPolicies, }, } } diff --git a/website/docs/r/kubernetes_cluster.html.markdown b/website/docs/r/kubernetes_cluster.html.markdown index dff76fb79783..c3fcf32dea0d 100644 --- a/website/docs/r/kubernetes_cluster.html.markdown +++ b/website/docs/r/kubernetes_cluster.html.markdown @@ -736,6 +736,8 @@ An `advanced_networking` block supports the following: * `security_enabled` - (Optional) Is security enabled? Defaults to `false`. This can only be enabled (set to `true`) when `network_plugin` is set to `azure` and `network_data_plane` is set to `cilium`. +* `advanced_network_policies` - (Optional) Specifies the advanced network policy for the cluster. Possible values are `FQDN` and `L7`. This can only be set when `security_enabled` is set to `true` and `network_data_plane` is set to `cilium`. This cannot be set when `service_mesh_profile` `mode` is set to `Istio`. Omitting this field disables advanced network policy enforcement (equivalent to `None`). + --- A `load_balancer_profile` block supports the following: