diff --git a/internal/services/servicebus/servicebus_namespace_network_rule_set_resource.go b/internal/services/servicebus/servicebus_namespace_network_rule_set_resource.go index 0d4fe75bc497..db121bcecda0 100644 --- a/internal/services/servicebus/servicebus_namespace_network_rule_set_resource.go +++ b/internal/services/servicebus/servicebus_namespace_network_rule_set_resource.go @@ -30,6 +30,8 @@ func resourceServiceBusNamespaceNetworkRuleSet() *pluginsdk.Resource { Update: resourceServiceBusNamespaceNetworkRuleSetCreateUpdate, Delete: resourceServiceBusNamespaceNetworkRuleSetDelete, + DeprecationMessage: "The `azurerm_servicebus_namespace_network_rule_set` resource is deprecated and will be removed in version 4.0 of the AzureRM provider. Please use `network_rule_set` inside the `azurerm_servicebus_namespace` resource instead.", + Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { _, err := namespaces.ParseNamespaceID(id) return err diff --git a/internal/services/servicebus/servicebus_namespace_resource.go b/internal/services/servicebus/servicebus_namespace_resource.go index 66c898d09d7b..746fa14f7191 100644 --- a/internal/services/servicebus/servicebus_namespace_resource.go +++ b/internal/services/servicebus/servicebus_namespace_resource.go @@ -22,12 +22,14 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/features" keyVaultParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/services/servicebus/migration" "github.com/hashicorp/terraform-provider-azurerm/internal/services/servicebus/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tags" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/suppress" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" "github.com/hashicorp/terraform-provider-azurerm/utils" @@ -175,6 +177,68 @@ func resourceServiceBusNamespace() *pluginsdk.Resource { ForceNew: true, }, + "network_rule_set": { + Type: pluginsdk.TypeList, + Optional: true, + Computed: !features.FourPointOhBeta(), + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "default_action": { + Type: pluginsdk.TypeString, + Optional: true, + Default: string(namespaces.DefaultActionAllow), + ValidateFunc: validation.StringInSlice([]string{ + string(namespaces.DefaultActionAllow), + string(namespaces.DefaultActionDeny), + }, false), + }, + + "public_network_access_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, + + "ip_rules": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "trusted_services_allowed": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + + "network_rules": { + Type: pluginsdk.TypeSet, + Optional: true, + Set: networkRuleHash, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "subnet_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: commonids.ValidateSubnetID, + // The subnet ID returned from the service will have `resourceGroup/{resourceGroupName}` all in lower cases... + DiffSuppressFunc: suppress.CaseDifference, + }, + "ignore_missing_vnet_service_endpoint": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + }, + }, + }, + }, + "endpoint": { Type: pluginsdk.TypeString, Computed: true, @@ -277,6 +341,25 @@ func resourceServiceBusNamespaceCreateUpdate(d *pluginsdk.ResourceData, meta int } d.SetId(id.ID()) + + if d.HasChange("network_rule_set") { + oldNetworkRuleSet, newNetworkRuleSet := d.GetChange("network_rule_set") + // if the network rule set has been removed from config, reset it instead as there is no way to remove a rule set + if len(oldNetworkRuleSet.([]interface{})) == 1 && len(newNetworkRuleSet.([]interface{})) == 0 { + log.Printf("[DEBUG] Resetting Network Rule Set associated with %s..", id) + if err = resetNetworkRuleSetForNamespace(ctx, client, id); err != nil { + return err + } + log.Printf("[DEBUG] Reset the Existing Network Rule Set associated with %s", id) + } else { + log.Printf("[DEBUG] Creating the Network Rule Set associated with %s..", id) + if err = createNetworkRuleSetForNamespace(ctx, client, id, newNetworkRuleSet.([]interface{})); err != nil { + return err + } + log.Printf("[DEBUG] Created the Network Rule Set associated with %s", id) + } + } + return resourceServiceBusNamespaceRead(d, meta) } @@ -366,6 +449,18 @@ func resourceServiceBusNamespaceRead(d *pluginsdk.ResourceData, meta interface{} d.Set("default_secondary_key", keysModel.SecondaryKey) } } + + networkRuleSet, err := client.GetNetworkRuleSet(ctx, *id) + if err != nil { + return fmt.Errorf("retrieving network rule set %s: %+v", *id, err) + } + + if model := networkRuleSet.Model; model != nil { + if props := model.Properties; props != nil { + d.Set("network_rule_set", flattenServiceBusNamespaceNetworkRuleSet(*props)) + } + } + return nil } @@ -490,3 +585,96 @@ func servicebusTLSVersionDiff(ctx context.Context, d *pluginsdk.ResourceDiff, _ } return } + +func createNetworkRuleSetForNamespace(ctx context.Context, client *namespaces.NamespacesClient, id namespaces.NamespaceId, input []interface{}) error { + if len(input) < 1 || input[0] == nil { + return nil + } + item := input[0].(map[string]interface{}) + + defaultAction := namespaces.DefaultAction(item["default_action"].(string)) + vnetRule := expandServiceBusNamespaceVirtualNetworkRules(item["network_rules"].(*pluginsdk.Set).List()) + ipRule := expandServiceBusNamespaceIPRules(item["ip_rules"].(*pluginsdk.Set).List()) + publicNetworkAcc := "Disabled" + if item["public_network_access_enabled"].(bool) { + publicNetworkAcc = "Enabled" + } + + // API doesn't accept "Deny" to be set for "default_action" if no "ip_rules" or "network_rules" is defined and returns no error message to the user + if defaultAction == namespaces.DefaultActionDeny && vnetRule == nil && ipRule == nil { + return fmt.Errorf(" The `default_action` of `network_rule_set` can only be set to `Allow` if no `ip_rules` or `network_rules` is set") + } + + publicNetworkAccess := namespaces.PublicNetworkAccessFlag(publicNetworkAcc) + + parameters := namespaces.NetworkRuleSet{ + Properties: &namespaces.NetworkRuleSetProperties{ + DefaultAction: &defaultAction, + VirtualNetworkRules: vnetRule, + IPRules: ipRule, + PublicNetworkAccess: &publicNetworkAccess, + TrustedServiceAccessEnabled: utils.Bool(item["trusted_services_allowed"].(bool)), + }, + } + + if _, err := client.CreateOrUpdateNetworkRuleSet(ctx, id, parameters); err != nil { + return fmt.Errorf("creating/updating %s: %+v", id, err) + } + + return nil +} + +func resetNetworkRuleSetForNamespace(ctx context.Context, client *namespaces.NamespacesClient, id namespaces.NamespaceId) error { + defaultAction := namespaces.DefaultActionAllow + parameters := namespaces.NetworkRuleSet{ + Properties: &namespaces.NetworkRuleSetProperties{ + DefaultAction: &defaultAction, + }, + } + + if _, err := client.CreateOrUpdateNetworkRuleSet(ctx, id, parameters); err != nil { + return fmt.Errorf("resetting %s: %+v", id, err) + } + + return nil +} + +func flattenServiceBusNamespaceNetworkRuleSet(networkRuleSet namespaces.NetworkRuleSetProperties) []interface{} { + defaultAction := "" + if v := networkRuleSet.DefaultAction; v != nil { + defaultAction = string(*v) + } + publicNetworkAccess := namespaces.PublicNetworkAccessFlagEnabled + if v := networkRuleSet.PublicNetworkAccess; v != nil { + publicNetworkAccess = *v + } + + trustedServiceEnabled := false + if networkRuleSet.TrustedServiceAccessEnabled != nil { + trustedServiceEnabled = *networkRuleSet.TrustedServiceAccessEnabled + } + + networkRules := flattenServiceBusNamespaceVirtualNetworkRules(networkRuleSet.VirtualNetworkRules) + ipRules := flattenServiceBusNamespaceIPRules(networkRuleSet.IPRules) + + // only set network rule set if the values are different than what they are defaulted to during namespace creation + // this has to wait until 4.0 due to `azurerm_servicebus_namespace_network_rule_set` which forces `network_rule_set` to be Optional/Computed + if features.FourPointOhBeta() { + if defaultAction == string(namespaces.DefaultActionAllow) && + publicNetworkAccess == namespaces.PublicNetworkAccessFlagEnabled && + !trustedServiceEnabled && + len(networkRules) == 0 && + len(ipRules) == 0 { + + return []interface{}{} + } + } + + return []interface{}{map[string]interface{}{ + "default_action": defaultAction, + "trusted_services_allowed": trustedServiceEnabled, + "public_network_access_enabled": publicNetworkAccess == namespaces.PublicNetworkAccessFlagEnabled, + "network_rules": pluginsdk.NewSet(networkRuleHash, networkRules), + "ip_rules": ipRules, + }} +} diff --git a/internal/services/servicebus/servicebus_namespace_resource_test.go b/internal/services/servicebus/servicebus_namespace_resource_test.go index 7d4422c81f61..ccd2142b03eb 100644 --- a/internal/services/servicebus/servicebus_namespace_resource_test.go +++ b/internal/services/servicebus/servicebus_namespace_resource_test.go @@ -269,6 +269,37 @@ func TestAccAzureRMServiceBusNamespace_endpoint(t *testing.T) { }) } +func TestAccAzureRMServiceBusNamespace_networkRuleSet(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_servicebus_namespace", "test") + r := ServiceBusNamespaceResource{} + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.networkRuleSet(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("network_rule_set.0.default_action").HasValue("Deny"), + ), + }, + data.ImportStep(), + { + Config: r.networkRuleSetComplete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("network_rule_set.0.trusted_services_allowed").HasValue("true"), + ), + }, + data.ImportStep(), + { + Config: r.networkRuleSetEmpty(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("network_rule_set.0.default_action").HasValue("Allow"), + ), + }, + data.ImportStep(), + }) +} + func (t ServiceBusNamespaceResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := namespaces.ParseNamespaceID(state.ID) if err != nil { @@ -663,3 +694,141 @@ resource "azurerm_servicebus_namespace" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } + +func (ServiceBusNamespaceResource) networkRuleSet(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctest-vnet-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + address_space = ["172.17.0.0/16"] + dns_servers = ["10.0.0.4", "10.0.0.5"] +} + +resource "azurerm_subnet" "test" { + name = "${azurerm_virtual_network.test.name}-default" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["172.17.0.0/24"] + + service_endpoints = ["Microsoft.ServiceBus"] +} + +resource "azurerm_servicebus_namespace" "test" { + name = "acctestservicebusnamespace-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku = "Premium" + capacity = 1 + + network_rule_set { + default_action = "Deny" + + network_rules { + subnet_id = azurerm_subnet.test.id + ignore_missing_vnet_service_endpoint = false + } + } +} +`, data.RandomInteger, data.Locations.Primary) +} + +func (ServiceBusNamespaceResource) networkRuleSetComplete(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctest-vnet-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + address_space = ["172.17.0.0/16"] + dns_servers = ["10.0.0.4", "10.0.0.5"] +} + +resource "azurerm_subnet" "test" { + name = "${azurerm_virtual_network.test.name}-default" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["172.17.0.0/24"] + + service_endpoints = ["Microsoft.ServiceBus"] +} + +resource "azurerm_servicebus_namespace" "test" { + name = "acctestservicebusnamespace-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku = "Premium" + capacity = 1 + + network_rule_set { + default_action = "Deny" + trusted_services_allowed = true + public_network_access_enabled = true + + ip_rules = ["1.1.1.1"] + + network_rules { + subnet_id = azurerm_subnet.test.id + ignore_missing_vnet_service_endpoint = false + } + } +} +`, data.RandomInteger, data.Locations.Primary) +} + +func (ServiceBusNamespaceResource) networkRuleSetEmpty(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctest-vnet-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + address_space = ["172.17.0.0/16"] + dns_servers = ["10.0.0.4", "10.0.0.5"] +} + +resource "azurerm_subnet" "test" { + name = "${azurerm_virtual_network.test.name}-default" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["172.17.0.0/24"] + + service_endpoints = ["Microsoft.ServiceBus"] +} + +resource "azurerm_servicebus_namespace" "test" { + name = "acctestservicebusnamespace-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku = "Premium" + capacity = 1 + + network_rule_set {} +} +`, data.RandomInteger, data.Locations.Primary) +} diff --git a/website/docs/r/servicebus_namespace.html.markdown b/website/docs/r/servicebus_namespace.html.markdown index 1441ebaf0864..038095813da1 100644 --- a/website/docs/r/servicebus_namespace.html.markdown +++ b/website/docs/r/servicebus_namespace.html.markdown @@ -61,6 +61,8 @@ The following arguments are supported: * `zone_redundant` - (Optional) Whether or not this resource is zone redundant. `sku` needs to be `Premium`. Changing this forces a new resource to be created. +* `network_rule_set` - (Optional) An `network_rule_set` block as defined below. + * `tags` - (Optional) A mapping of tags to assign to the resource. --- @@ -87,6 +89,28 @@ A `customer_managed_key` block supports the following: * `infrastructure_encryption_enabled` - (Optional) Used to specify whether enable Infrastructure Encryption (Double Encryption). Changing this forces a new resource to be created. +--- + +A `network_rule-set` block supports the following: + +* `default_action` - (Optional) Specifies the default action for the Network Rule Set. Possible values are `Allow` and `Deny`. Defaults to `Deny`. + +* `public_network_access_enabled` - (Optional) Whether to allow traffic over public network. Possible values are `true` and `false`. Defaults to `true`. + +* `trusted_services_allowed` - (Optional) Are Azure Services that are known and trusted for this resource type are allowed to bypass firewall configuration? See [Trusted Microsoft Services](https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/service-bus-messaging/includes/service-bus-trusted-services.md) + +* `ip_rules` - (Optional) One or more IP Addresses, or CIDR Blocks which should be able to access the ServiceBus Namespace. + +* `network_rules` - (Optional) One or more `network_rules` blocks as defined below. + +--- + +A `network_rules` block supports the following: + +* `subnet_id` - (Required) The Subnet ID which should be able to access this ServiceBus Namespace. + +* `ignore_missing_vnet_service_endpoint` - (Optional) Should the ServiceBus Namespace Network Rule Set ignore missing Virtual Network Service Endpoint option in the Subnet? Defaults to `false`. + ## Attributes Reference In addition to the Arguments listed above - the following Attributes are exported: