From 4e7ebdc39ce95d49dfcd721adbf19beff6cc5d07 Mon Sep 17 00:00:00 2001 From: Matthew Frahry Date: Fri, 6 Dec 2019 09:32:08 -0800 Subject: [PATCH] New Resource: `azurerm_storage_account_network_rules` (#5082) --- azurerm/provider.go | 1 + azurerm/resource_arm_storage_account.go | 9 + ...ource_arm_storage_account_network_rules.go | 297 ++++++++++++++++++ ..._arm_storage_account_network_rules_test.go | 235 ++++++++++++++ website/azurerm.erb | 4 + website/docs/r/storage_account.html.markdown | 2 + ...torage_account_network_rules.html.markdown | 94 ++++++ 7 files changed, 642 insertions(+) create mode 100644 azurerm/resource_arm_storage_account_network_rules.go create mode 100644 azurerm/resource_arm_storage_account_network_rules_test.go create mode 100644 website/docs/r/storage_account_network_rules.html.markdown diff --git a/azurerm/provider.go b/azurerm/provider.go index 1c64b5517be0..e13550b6c597 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -456,6 +456,7 @@ func Provider() terraform.ResourceProvider { "azurerm_sql_server": resourceArmSqlServer(), "azurerm_sql_virtual_network_rule": resourceArmSqlVirtualNetworkRule(), "azurerm_storage_account": resourceArmStorageAccount(), + "azurerm_storage_account_network_rules": resourceArmStorageAccountNetworkRules(), "azurerm_storage_blob": resourceArmStorageBlob(), "azurerm_storage_container": resourceArmStorageContainer(), "azurerm_storage_data_lake_gen2_filesystem": resourceArmStorageDataLakeGen2FileSystem(), diff --git a/azurerm/resource_arm_storage_account.go b/azurerm/resource_arm_storage_account.go index 37bee03f7fa6..f6c7dcf120b3 100644 --- a/azurerm/resource_arm_storage_account.go +++ b/azurerm/resource_arm_storage_account.go @@ -625,6 +625,9 @@ func resourceArmStorageAccountCreate(d *schema.ResourceData, meta interface{}) e storageAccountName := d.Get("name").(string) resourceGroupName := d.Get("resource_group_name").(string) + locks.ByName(storageAccountName, storageAccountResourceName) + defer locks.UnlockByName(storageAccountName, storageAccountResourceName) + if features.ShouldResourcesBeImported() { existing, err := client.GetProperties(ctx, resourceGroupName, storageAccountName, "") if err != nil { @@ -795,6 +798,9 @@ func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) e storageAccountName := id.Path["storageAccounts"] resourceGroupName := id.ResourceGroup + locks.ByName(storageAccountName, iothubResourceName) + defer locks.UnlockByName(storageAccountName, iothubResourceName) + accountTier := d.Get("account_tier").(string) replicationType := d.Get("account_replication_type").(string) storageType := fmt.Sprintf("%s_%s", accountTier, replicationType) @@ -1188,6 +1194,9 @@ func resourceArmStorageAccountDelete(d *schema.ResourceData, meta interface{}) e name := id.Path["storageAccounts"] resourceGroup := id.ResourceGroup + locks.ByName(name, storageAccountResourceName) + defer locks.UnlockByName(name, storageAccountResourceName) + read, err := client.GetProperties(ctx, resourceGroup, name, "") if err != nil { if utils.ResponseWasNotFound(read.Response) { diff --git a/azurerm/resource_arm_storage_account_network_rules.go b/azurerm/resource_arm_storage_account_network_rules.go new file mode 100644 index 000000000000..3c14e5d93627 --- /dev/null +++ b/azurerm/resource_arm_storage_account_network_rules.go @@ -0,0 +1,297 @@ +package azurerm + +import ( + "fmt" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-04-01/storage" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +var storageAccountResourceName = "azurerm_storage_account" + +func resourceArmStorageAccountNetworkRules() *schema.Resource { + return &schema.Resource{ + Create: resourceArmStorageAccountNetworkRulesCreateUpdate, + Read: resourceArmStorageAccountNetworkRulesRead, + Update: resourceArmStorageAccountNetworkRulesCreateUpdate, + Delete: resourceArmStorageAccountNetworkRulesDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(60 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(60 * time.Minute), + Delete: schema.DefaultTimeout(60 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "resource_group_name": azure.SchemaResourceGroupName(), + + "storage_account_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateArmStorageAccountName, + }, + + "bypass": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + ConfigMode: schema.SchemaConfigModeAttr, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string(storage.AzureServices), + string(storage.Logging), + string(storage.Metrics), + string(storage.None), + }, false), + }, + Set: schema.HashString, + }, + + "ip_rules": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + ConfigMode: schema.SchemaConfigModeAttr, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validate.IPv4Address, + }, + Set: schema.HashString, + }, + + "virtual_network_subnet_ids": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + ConfigMode: schema.SchemaConfigModeAttr, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: azure.ValidateResourceID, + }, + Set: schema.HashString, + }, + + "default_action": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(storage.DefaultActionAllow), + string(storage.DefaultActionDeny), + }, false), + }, + }, + } +} + +func resourceArmStorageAccountNetworkRulesCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).Storage.AccountsClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*ArmClient).StopContext, d) + defer cancel() + + storageAccountName := d.Get("storage_account_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + locks.ByName(storageAccountName, storageAccountResourceName) + defer locks.UnlockByName(storageAccountName, storageAccountResourceName) + + storageAccount, err := client.GetProperties(ctx, resourceGroup, storageAccountName, "") + if err != nil { + if utils.ResponseWasNotFound(storageAccount.Response) { + return fmt.Errorf("Storage Account %q (Resource Group %q) was not found", storageAccountName, resourceGroup) + } + + return fmt.Errorf("Error loading Storage Account %q (Resource Group %q): %+v", storageAccountName, resourceGroup, err) + } + + if features.ShouldResourcesBeImported() { + if checkForNonDefaultStorageAccountNetworkRule(storageAccount.NetworkRuleSet) { + return tf.ImportAsExistsError("azurerm_storage_account_network_rule", *storageAccount.ID) + } + } + + rules := storageAccount.NetworkRuleSet + if rules == nil { + rules = &storage.NetworkRuleSet{} + } + + rules.DefaultAction = storage.DefaultAction(d.Get("default_action").(string)) + + if v, ok := d.GetOk("bypass"); ok { + rules.Bypass = expandStorageAccountNetworkRuleBypass(v.(*schema.Set).List()) + } + + if v, ok := d.GetOk("ip_rules"); ok { + rules.IPRules = expandStorageAccountNetworkRuleIpRules(v.(*schema.Set).List()) + } + + if v, ok := d.GetOk("virtual_network_subnet_ids"); ok { + rules.VirtualNetworkRules = expandStorageAccountNetworkRuleVirtualRules(v.(*schema.Set).List()) + } + + opts := storage.AccountUpdateParameters{ + AccountPropertiesUpdateParameters: &storage.AccountPropertiesUpdateParameters{ + NetworkRuleSet: rules, + }, + } + + if _, err := client.Update(ctx, resourceGroup, storageAccountName, opts); err != nil { + return fmt.Errorf("Error updating Azure Storage Account Network Rules %q (Resource Group %q): %+v", storageAccountName, resourceGroup, err) + } + + d.SetId(*storageAccount.ID) + + return resourceArmStorageAccountNetworkRulesRead(d, meta) +} + +func resourceArmStorageAccountNetworkRulesRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).Storage.AccountsClient + ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resourceGroup := id.ResourceGroup + storageAccountName := id.Path["storageAccounts"] + + storageAccount, err := client.GetProperties(ctx, resourceGroup, storageAccountName, "") + if err != nil { + return fmt.Errorf("Error reading Storage Account Network Rules %q (Resource Group %q): %+v", storageAccountName, resourceGroup, err) + } + + d.Set("storage_account_name", storageAccountName) + d.Set("resource_group_name", resourceGroup) + + if rules := storageAccount.NetworkRuleSet; rules != nil { + if err := d.Set("ip_rules", schema.NewSet(schema.HashString, flattenStorageAccountIPRules(rules.IPRules))); err != nil { + return fmt.Errorf("Error setting `ip_rules`: %+v", err) + } + if err := d.Set("virtual_network_subnet_ids", schema.NewSet(schema.HashString, flattenStorageAccountVirtualNetworks(rules.VirtualNetworkRules))); err != nil { + return fmt.Errorf("Error setting `virtual_network_subnet_ids`: %+v", err) + } + if err := d.Set("bypass", schema.NewSet(schema.HashString, flattenStorageAccountBypass(rules.Bypass))); err != nil { + return fmt.Errorf("Error setting `bypass`: %+v", err) + } + d.Set("default_action", string(rules.DefaultAction)) + } + + return nil +} + +func resourceArmStorageAccountNetworkRulesDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).Storage.AccountsClient + ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d) + defer cancel() + + parsedStorageAccountNetworkRuleId, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resourceGroup := parsedStorageAccountNetworkRuleId.ResourceGroup + storageAccountName := parsedStorageAccountNetworkRuleId.Path["storageAccounts"] + + locks.ByName(storageAccountName, storageAccountResourceName) + defer locks.UnlockByName(storageAccountName, storageAccountResourceName) + + storageAccount, err := client.GetProperties(ctx, resourceGroup, storageAccountName, "") + if err != nil { + if utils.ResponseWasNotFound(storageAccount.Response) { + return fmt.Errorf("Storage Account %q (Resource Group %q) was not found", storageAccountName, resourceGroup) + } + + return fmt.Errorf("Error loading Storage Account %q (Resource Group %q): %+v", storageAccountName, resourceGroup, err) + } + + if storageAccount.NetworkRuleSet == nil { + return nil + } + + // We can't delete a network rule set so we'll just update it back to the default instead + opts := storage.AccountUpdateParameters{ + AccountPropertiesUpdateParameters: &storage.AccountPropertiesUpdateParameters{ + NetworkRuleSet: &storage.NetworkRuleSet{ + Bypass: storage.AzureServices, + DefaultAction: storage.DefaultActionDeny, + }, + }, + } + + if _, err := client.Update(ctx, resourceGroup, storageAccountName, opts); err != nil { + return fmt.Errorf("Error deleting Azure Storage Account Network Rule %q (Resource Group %q): %+v", storageAccountName, resourceGroup, err) + } + + return nil +} + +// To make sure that someone isn't overriding their existing network rules, we'll check for a non default network rule +func checkForNonDefaultStorageAccountNetworkRule(rule *storage.NetworkRuleSet) bool { + if rule == nil { + return false + } + + if rule.IPRules != nil || len(*rule.IPRules) != 0 || + rule.VirtualNetworkRules != nil || len(*rule.VirtualNetworkRules) == 0 || + rule.Bypass != "AzureServices" || rule.DefaultAction != "Allow" { + return true + } + + return false +} + +func expandStorageAccountNetworkRuleBypass(bypass []interface{}) storage.Bypass { + var bypassValues []string + for _, bypassConfig := range bypass { + bypassValues = append(bypassValues, bypassConfig.(string)) + } + + return storage.Bypass(strings.Join(bypassValues, ", ")) +} + +func expandStorageAccountNetworkRuleIpRules(ipRulesInfo []interface{}) *[]storage.IPRule { + ipRules := make([]storage.IPRule, len(ipRulesInfo)) + + for i, ipRuleConfig := range ipRulesInfo { + attrs := ipRuleConfig.(string) + ipRule := storage.IPRule{ + IPAddressOrRange: utils.String(attrs), + Action: storage.Allow, + } + ipRules[i] = ipRule + } + + return &ipRules +} + +func expandStorageAccountNetworkRuleVirtualRules(virtualNetworkInfo []interface{}) *[]storage.VirtualNetworkRule { + virtualNetworks := make([]storage.VirtualNetworkRule, len(virtualNetworkInfo)) + + for i, virtualNetworkConfig := range virtualNetworkInfo { + attrs := virtualNetworkConfig.(string) + virtualNetwork := storage.VirtualNetworkRule{ + VirtualNetworkResourceID: utils.String(attrs), + Action: storage.Allow, + } + virtualNetworks[i] = virtualNetwork + } + + return &virtualNetworks +} diff --git a/azurerm/resource_arm_storage_account_network_rules_test.go b/azurerm/resource_arm_storage_account_network_rules_test.go new file mode 100644 index 000000000000..5dce52d5c351 --- /dev/null +++ b/azurerm/resource_arm_storage_account_network_rules_test.go @@ -0,0 +1,235 @@ +package azurerm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" +) + +func TestAccAzureRMStorageAccountNetworkRules_basic(t *testing.T) { + resourceName := "azurerm_storage_account_network_rules.test" + rInt := tf.AccRandTimeInt() + rs := acctest.RandString(4) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMStorageAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageAccountNetworkRules_basic(rInt, rs, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageAccountExists("azurerm_storage_account.testsa"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMStorageAccountNetworkRules_update(t *testing.T) { + resourceName := "azurerm_storage_account_network_rules.test" + rInt := tf.AccRandTimeInt() + rs := acctest.RandString(4) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMStorageAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageAccountNetworkRules_basic(rInt, rs, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageAccountExists("azurerm_storage_account.testsa"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAzureRMStorageAccountNetworkRules_update(rInt, rs, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageAccountExists("azurerm_storage_account.testsa"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAzureRMStorageAccountNetworkRules_basic(rInt, rs, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageAccountExists("azurerm_storage_account.testsa"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMStorageAccountNetworkRules_empty(t *testing.T) { + resourceName := "azurerm_storage_account_network_rules.test" + rInt := tf.AccRandTimeInt() + rs := acctest.RandString(4) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMStorageAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMStorageAccountNetworkRules_empty(rInt, rs, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMStorageAccountExists("azurerm_storage_account.testsa"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAzureRMStorageAccountNetworkRules_basic(rInt int, rString string, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "testrg" { + name = "acctestRG-storage-%d" + location = "%s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvirtnet%d" + address_space = ["10.0.0.0/16"] + location = "${azurerm_resource_group.testrg.location}" + resource_group_name = "${azurerm_resource_group.testrg.name}" +} + +resource "azurerm_subnet" "test" { + name = "acctestsubnet%d" + resource_group_name = "${azurerm_resource_group.testrg.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.2.0/24" + service_endpoints = ["Microsoft.Storage"] +} + +resource "azurerm_storage_account" "testsa" { + name = "unlikely23exst2acct%s" + resource_group_name = "${azurerm_resource_group.testrg.name}" + location = "${azurerm_resource_group.testrg.location}" + account_tier = "Standard" + account_replication_type = "LRS" + + tags = { + environment = "production" + } +} + +resource "azurerm_storage_account_network_rules" "test" { + resource_group_name = "${azurerm_resource_group.testrg.name}" + storage_account_name = "${azurerm_storage_account.testsa.name}" + + default_action = "Deny" + ip_rules = ["127.0.0.1"] + virtual_network_subnet_ids = ["${azurerm_subnet.test.id}"] +} +`, rInt, location, rInt, rInt, rString) +} + +func testAccAzureRMStorageAccountNetworkRules_update(rInt int, rString string, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "testrg" { + name = "acctestRG-storage-%d" + location = "%s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctestvirtnet%d" + address_space = ["10.0.0.0/16"] + location = "${azurerm_resource_group.testrg.location}" + resource_group_name = "${azurerm_resource_group.testrg.name}" +} + +resource "azurerm_subnet" "test" { + name = "acctestsubnet%d" + resource_group_name = "${azurerm_resource_group.testrg.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.2.0/24" + service_endpoints = ["Microsoft.Storage"] +} + +resource "azurerm_subnet" "test2" { + name = "acctestsubnet2%d" + resource_group_name = "${azurerm_resource_group.testrg.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.3.0/24" + service_endpoints = ["Microsoft.Storage"] +} + +resource "azurerm_storage_account" "testsa" { + name = "unlikely23exst2acct%s" + resource_group_name = "${azurerm_resource_group.testrg.name}" + location = "${azurerm_resource_group.testrg.location}" + account_tier = "Standard" + account_replication_type = "LRS" + + tags = { + environment = "production" + } +} + +resource "azurerm_storage_account_network_rules" "test" { + resource_group_name = "${azurerm_resource_group.testrg.name}" + storage_account_name = "${azurerm_storage_account.testsa.name}" + + default_action = "Allow" + ip_rules = ["127.0.0.2", "127.0.0.3"] + virtual_network_subnet_ids = ["${azurerm_subnet.test.id}", "${azurerm_subnet.test2.id}"] + bypass = ["Metrics"] +} +`, rInt, location, rInt, rInt, rInt, rString) +} + +func testAccAzureRMStorageAccountNetworkRules_empty(rInt int, rString string, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "testrg" { + name = "acctestRG-storage-%d" + location = "%s" +} + +resource "azurerm_storage_account" "testsa" { + name = "unlikely23exst2acct%s" + resource_group_name = "${azurerm_resource_group.testrg.name}" + location = "${azurerm_resource_group.testrg.location}" + account_tier = "Standard" + account_replication_type = "LRS" + + tags = { + environment = "production" + } +} + +resource "azurerm_storage_account_network_rules" "test" { + resource_group_name = "${azurerm_resource_group.testrg.name}" + storage_account_name = "${azurerm_storage_account.testsa.name}" + + default_action = "Deny" + bypass = ["Metrics"] +} +`, rInt, location, rString) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 33389c6dd345..fa351d5e2bfa 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -1971,6 +1971,10 @@ azurerm_storage_account +
  • + azurerm_storage_account_network_rules +
  • +
  • azurerm_storage_blob
  • diff --git a/website/docs/r/storage_account.html.markdown b/website/docs/r/storage_account.html.markdown index 3fb65883970d..3e9a6626b4f3 100644 --- a/website/docs/r/storage_account.html.markdown +++ b/website/docs/r/storage_account.html.markdown @@ -200,6 +200,8 @@ any combination of `Logging`, `Metrics`, `AzureServices`, or `None`. ~> **Note:** If specifying `network_rules`, one of either `ip_rules` or `virtual_network_subnet_ids` must be specified and `default_action` must be set to `Deny`. +~> **NOTE:** Network Rules can be defined either directly on the `azurerm_storage_account` resource, or using the `azurerm_storage_account_network_rules` resource - but the two cannot be used together. If both are used against the same Storage Account, spurious changes will occur. + ~> **Note:** [More information on Validation is available here](https://docs.microsoft.com/en-gb/azure/storage/blobs/storage-custom-domain-name) --- diff --git a/website/docs/r/storage_account_network_rules.html.markdown b/website/docs/r/storage_account_network_rules.html.markdown new file mode 100644 index 000000000000..83568b21ede4 --- /dev/null +++ b/website/docs/r/storage_account_network_rules.html.markdown @@ -0,0 +1,94 @@ +--- +subcategory: "Storage" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_storage_account_network_rules" +sidebar_current: "docs-azurerm-resource-storage-account-network-rules" +description: |- + Manages network rules inside of a Azure Storage Account. +--- + +# azurerm_storage_account + +Manages network rules inside of a Azure Storage Account. + +~> **NOTE:** Network Rules can be defined either directly on the `azurerm_storage_account` resource, or using the `azurerm_storage_account_network_rules` resource - but the two cannot be used together. Spurious changes will occur if both are used against the same Storage Account. + +~> **NOTE:** Only one `azurerm_storage_account_network_rules` can be tied to an `azurerm_storage_account`. Spurious changes will occur if more than `azurerm_storage_account_network_rules` is tied to the same `azurerm_storage_account`. + +~> **NOTE:** Deleting this resource updates the storage account back to the default values it had when the storage account was created. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_virtual_network" "example" { + name = "example-vnet" + address_space = ["10.0.0.0/16"] + location = "${azurerm_resource_group.example.location}" + resource_group_name = "${azurerm_resource_group.example.name}" +} + +resource "azurerm_subnet" "example" { + name = "example-subnet" + resource_group_name = "${azurerm_resource_group.example.name}" + virtual_network_name = "${azurerm_virtual_network.example.name}" + address_prefix = "10.0.2.0/24" + service_endpoints = ["Microsoft.Storage"] +} + +resource "azurerm_storage_account" "example" { + name = "storageaccountname" + resource_group_name = "${azurerm_resource_group.example.name}" + location = "${azurerm_resource_group.example.location}" + account_tier = "Standard" + account_replication_type = "GRS" + + tags = { + environment = "staging" + } +} + +resource "azurerm_storage_account_network_rules" "test" { + resource_group_name = "${azurerm_resource_group.testrg.name}" + storage_account_name = "${azurerm_storage_account.testsa.name}" + + default_action = "Allow" + ip_rules = ["127.0.0.1"] + virtual_network_subnet_ids = ["${azurerm_subnet.test.id}"] + bypass = ["Metrics"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `storage_account_name` - (Required) Specifies the name of the storage account. Changing this forces a new resource to be created. This must be unique across the entire Azure service, not just within the resource group. + +* `resource_group_name` - (Required) The name of the resource group in which to create the storage account. Changing this forces a new resource to be created. + +* `default_action` - (Required) Specifies the default action of allow or deny when no other rules match. Valid options are `Deny` or `Allow`. + +* `bypass` - (Optional) Specifies whether traffic is bypassed for Logging/Metrics/AzureServices. Valid options are any combination of `Logging`, `Metrics`, `AzureServices`, or `None`. + +* `ip_rules` - (Optional) List of public IP or IP ranges in CIDR Format. Only IPV4 addresses are allowed. Private IP address ranges (as defined in [RFC 1918](https://tools.ietf.org/html/rfc1918#section-3)) are not allowed. + +* `virtual_network_subnet_ids` - (Optional) A list of virtual network subnet ids to to secure the storage account. + +## Attributes Reference + +The following attributes are exported in addition to the arguments listed above: + +* `id` - The storage account Resource ID. + +## Import + +Storage Account Network Rules can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_storage_account_network_rules.storageAcc1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myresourcegroup/providers/Microsoft.Storage/storageAccounts/myaccount +```