diff --git a/azurerm/provider.go b/azurerm/provider.go index 20faba118a66..a22f4ecf3b85 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -250,6 +250,7 @@ func Provider() terraform.ResourceProvider { "azurerm_express_route_circuit_peering": resourceArmExpressRouteCircuitPeering(), "azurerm_express_route_circuit": resourceArmExpressRouteCircuit(), "azurerm_firewall_application_rule_collection": resourceArmFirewallApplicationRuleCollection(), + "azurerm_firewall_nat_rule_collection": resourceArmFirewallNatRuleCollection(), "azurerm_firewall_network_rule_collection": resourceArmFirewallNetworkRuleCollection(), "azurerm_firewall": resourceArmFirewall(), "azurerm_function_app": resourceArmFunctionApp(), diff --git a/azurerm/resource_arm_firewall_nat_rule_collection.go b/azurerm/resource_arm_firewall_nat_rule_collection.go new file mode 100644 index 000000000000..1a531376138d --- /dev/null +++ b/azurerm/resource_arm_firewall_nat_rule_collection.go @@ -0,0 +1,450 @@ +package azurerm + +import ( + "fmt" + "log" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-12-01/network" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/set" + "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/utils" +) + +func resourceArmFirewallNatRuleCollection() *schema.Resource { + return &schema.Resource{ + Create: resourceArmFirewallNatRuleCollectionCreateUpdate, + Read: resourceArmFirewallNatRuleCollectionRead, + Update: resourceArmFirewallNatRuleCollectionCreateUpdate, + Delete: resourceArmFirewallNatRuleCollectionDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateAzureFirewallName, + }, + + "azure_firewall_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateAzureFirewallName, + }, + + "resource_group_name": resourceGroupNameSchema(), + + "priority": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(100, 65000), + }, + + "action": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(network.Dnat), + string(network.Snat), + }, false), + }, + + "rule": { + Type: schema.TypeSet, + Required: true, + MinItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "translated_address": { + Type: schema.TypeString, + Required: true, + }, + "translated_port": { + Type: schema.TypeString, + Required: true, + }, + "source_addresses": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "destination_addresses": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "destination_ports": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "protocols": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string(network.Any), + string(network.ICMP), + string(network.TCP), + string(network.UDP), + }, false), + }, + Set: schema.HashString, + }, + }, + }, + }, + }, + } +} + +func resourceArmFirewallNatRuleCollectionCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).azureFirewallsClient + ctx := meta.(*ArmClient).StopContext + + name := d.Get("name").(string) + firewallName := d.Get("azure_firewall_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + azureRMLockByName(firewallName, azureFirewallResourceName) + defer azureRMUnlockByName(firewallName, azureFirewallResourceName) + + firewall, err := client.Get(ctx, resourceGroup, firewallName) + if err != nil { + return fmt.Errorf("Error retrieving Firewall %q (Resource Group %q): %+v", firewallName, resourceGroup, err) + } + + if firewall.AzureFirewallPropertiesFormat == nil { + return fmt.Errorf("Error expanding Firewall %q (Resource Group %q): `properties` was nil.", firewallName, resourceGroup) + } + props := *firewall.AzureFirewallPropertiesFormat + + if props.NatRuleCollections == nil { + return fmt.Errorf("Error expanding Firewall %q (Resource Group %q): `properties.NatRuleCollections` was nil.", firewallName, resourceGroup) + } + + ruleCollections := *props.NatRuleCollections + natRules := expandArmFirewallNatRules(d.Get("rule").(*schema.Set)) + priority := d.Get("priority").(int) + newRuleCollection := network.AzureFirewallNatRuleCollection{ + Name: utils.String(name), + AzureFirewallNatRuleCollectionProperties: &network.AzureFirewallNatRuleCollectionProperties{ + Action: &network.AzureFirewallNatRCAction{ + Type: network.AzureFirewallNatRCActionType(d.Get("action").(string)), + }, + Priority: utils.Int32(int32(priority)), + Rules: &natRules, + }, + } + + index := -1 + var id string + // determine if this already exists + for i, v := range ruleCollections { + if v.Name == nil || v.ID == nil { + continue + } + + if *v.Name == name { + index = i + id = *v.ID + break + } + } + + if !d.IsNewResource() { + if index == -1 { + return fmt.Errorf("Error locating NAT Rule Collection %q (Firewall %q / Resource Group %q)", name, firewallName, resourceGroup) + } + + ruleCollections[index] = newRuleCollection + } else { + if requireResourcesToBeImported && d.IsNewResource() { + if index != -1 { + return tf.ImportAsExistsError("azurerm_firewall_nat_rule_collection", id) + } + } + + // first double check it doesn't already exist + ruleCollections = append(ruleCollections, newRuleCollection) + } + + firewall.AzureFirewallPropertiesFormat.NatRuleCollections = &ruleCollections + future, err := client.CreateOrUpdate(ctx, resourceGroup, firewallName, firewall) + if err != nil { + return fmt.Errorf("Error creating/updating NAT Rule Collection %q in Firewall %q (Resource Group %q): %+v", name, firewallName, resourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for creation/update of NAT Rule Collection %q of Firewall %q (Resource Group %q): %+v", name, firewallName, resourceGroup, err) + } + + read, err := client.Get(ctx, resourceGroup, firewallName) + if err != nil { + return fmt.Errorf("Error retrieving Firewall %q (Resource Group %q): %+v", firewallName, resourceGroup, err) + } + + var collectionID string + if props := read.AzureFirewallPropertiesFormat; props != nil { + if collections := props.NatRuleCollections; collections != nil { + for _, collection := range *collections { + if collection.Name == nil { + continue + } + + if *collection.Name == name { + collectionID = *collection.ID + break + } + } + } + } + + if collectionID == "" { + return fmt.Errorf("Cannot find ID for NAT Rule Collection %q (Azure Firewall %q / Resource Group %q)", name, firewallName, resourceGroup) + } + d.SetId(collectionID) + + return resourceArmFirewallNatRuleCollectionRead(d, meta) +} + +func resourceArmFirewallNatRuleCollectionRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).azureFirewallsClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resourceGroup := id.ResourceGroup + firewallName := id.Path["azureFirewalls"] + name := id.Path["natRuleCollections"] + + read, err := client.Get(ctx, resourceGroup, firewallName) + if err != nil { + if utils.ResponseWasNotFound(read.Response) { + log.Printf("[DEBUG] Azure Firewall %q (Resource Group %q) was not found - removing from state!", name, resourceGroup) + d.SetId("") + return nil + } + return fmt.Errorf("Error retrieving Azure Firewall %q (Resource Group %q): %+v", name, resourceGroup, err) + } + if read.AzureFirewallPropertiesFormat == nil { + return fmt.Errorf("Error retrieving NAT Rule Collection %q (Firewall %q / Resource Group %q): `props` was nil", name, firewallName, resourceGroup) + } + + props := *read.AzureFirewallPropertiesFormat + + if props.NatRuleCollections == nil { + return fmt.Errorf("Error retrieving NAT Rule Collection %q (Firewall %q / Resource Group %q): `props.NetworkRuleCollections` was nil", name, firewallName, resourceGroup) + } + + var rule *network.AzureFirewallNatRuleCollection + for _, r := range *props.NatRuleCollections { + if r.Name == nil { + continue + } + + if *r.Name == name { + rule = &r + break + } + } + + if rule == nil { + log.Printf("[DEBUG] NAT Rule Collection %q was not found on Firewall %q (Resource Group %q) - removing from state!", name, firewallName, resourceGroup) + d.SetId("") + return nil + } + + d.Set("name", rule.Name) + d.Set("azure_firewall_name", firewallName) + d.Set("resource_group_name", resourceGroup) + + if props := rule.AzureFirewallNatRuleCollectionProperties; props != nil { + if action := props.Action; action != nil { + d.Set("action", string(action.Type)) + } + + if priority := props.Priority; priority != nil { + d.Set("priority", int(*priority)) + } + + flattenedRules := flattenFirewallNatRuleCollectionRules(props.Rules) + if err := d.Set("rule", flattenedRules); err != nil { + return fmt.Errorf("Error setting `rule`: %+v", err) + } + } + + return nil +} + +func resourceArmFirewallNatRuleCollectionDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).azureFirewallsClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resourceGroup := id.ResourceGroup + firewallName := id.Path["azureFirewalls"] + name := id.Path["natRuleCollections"] + + azureRMLockByName(firewallName, azureFirewallResourceName) + defer azureRMUnlockByName(firewallName, azureFirewallResourceName) + + firewall, err := client.Get(ctx, resourceGroup, firewallName) + if err != nil { + if utils.ResponseWasNotFound(firewall.Response) { + // assume deleted + return nil + } + + return fmt.Errorf("Error making Read request on Azure Firewall %q (Resource Group %q): %+v", firewallName, resourceGroup, err) + } + + props := firewall.AzureFirewallPropertiesFormat + if props == nil { + return fmt.Errorf("Error retrieving NAT Rule Collection %q (Firewall %q / Resource Group %q): `props` was nil", name, firewallName, resourceGroup) + } + if props.NetworkRuleCollections == nil { + return fmt.Errorf("Error retrieving NAT Rule Collection %q (Firewall %q / Resource Group %q): `props.NatRuleCollections` was nil", name, firewallName, resourceGroup) + } + + natRules := make([]network.AzureFirewallNatRuleCollection, 0) + for _, rule := range *props.NatRuleCollections { + if rule.Name == nil { + continue + } + + if *rule.Name != name { + natRules = append(natRules, rule) + } + } + props.NatRuleCollections = &natRules + + future, err := client.CreateOrUpdate(ctx, resourceGroup, firewallName, firewall) + if err != nil { + return fmt.Errorf("Error deleting NAT Rule Collection %q from Firewall %q (Resource Group %q): %+v", name, firewallName, resourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for deletion of NAT Rule Collection %q from Firewall %q (Resource Group %q): %+v", name, firewallName, resourceGroup, err) + } + + return nil +} + +func expandArmFirewallNatRules(input *schema.Set) []network.AzureFirewallNatRule { + nwRules := input.List() + rules := make([]network.AzureFirewallNatRule, 0) + + for _, nwRule := range nwRules { + rule := nwRule.(map[string]interface{}) + + name := rule["name"].(string) + description := rule["description"].(string) + + sourceAddresses := make([]string, 0) + for _, v := range rule["source_addresses"].(*schema.Set).List() { + sourceAddresses = append(sourceAddresses, v.(string)) + } + + destinationAddresses := make([]string, 0) + for _, v := range rule["destination_addresses"].(*schema.Set).List() { + destinationAddresses = append(destinationAddresses, v.(string)) + } + + destinationPorts := make([]string, 0) + for _, v := range rule["destination_ports"].(*schema.Set).List() { + destinationPorts = append(destinationPorts, v.(string)) + } + + translatedAddress := rule["translated_address"].(string) + translatedPort := rule["translated_port"].(string) + + ruleToAdd := network.AzureFirewallNatRule{ + Name: utils.String(name), + Description: utils.String(description), + SourceAddresses: &sourceAddresses, + DestinationAddresses: &destinationAddresses, + DestinationPorts: &destinationPorts, + TranslatedAddress: &translatedAddress, + TranslatedPort: &translatedPort, + } + + nrProtocols := make([]network.AzureFirewallNetworkRuleProtocol, 0) + protocols := rule["protocols"].(*schema.Set) + for _, v := range protocols.List() { + s := network.AzureFirewallNetworkRuleProtocol(v.(string)) + nrProtocols = append(nrProtocols, s) + } + ruleToAdd.Protocols = &nrProtocols + rules = append(rules, ruleToAdd) + } + + return rules +} + +func flattenFirewallNatRuleCollectionRules(rules *[]network.AzureFirewallNatRule) []map[string]interface{} { + outputs := make([]map[string]interface{}, 0) + if rules == nil { + return outputs + } + + for _, rule := range *rules { + output := make(map[string]interface{}) + if rule.Name != nil { + output["name"] = *rule.Name + } + if rule.Description != nil { + output["description"] = *rule.Description + } + if rule.TranslatedAddress != nil { + output["translated_address"] = *rule.TranslatedAddress + } + if rule.TranslatedPort != nil { + output["translated_port"] = *rule.TranslatedPort + } + if rule.SourceAddresses != nil { + output["source_addresses"] = set.FromStringSlice(*rule.SourceAddresses) + } + if rule.DestinationAddresses != nil { + output["destination_addresses"] = set.FromStringSlice(*rule.DestinationAddresses) + } + if rule.DestinationPorts != nil { + output["destination_ports"] = set.FromStringSlice(*rule.DestinationPorts) + } + protocols := make([]string, 0) + if rule.Protocols != nil { + for _, protocol := range *rule.Protocols { + protocols = append(protocols, string(protocol)) + } + } + output["protocols"] = set.FromStringSlice(protocols) + outputs = append(outputs, output) + } + return outputs +} diff --git a/azurerm/resource_arm_firewall_nat_rule_collection_test.go b/azurerm/resource_arm_firewall_nat_rule_collection_test.go new file mode 100644 index 000000000000..92f818952b20 --- /dev/null +++ b/azurerm/resource_arm_firewall_nat_rule_collection_test.go @@ -0,0 +1,764 @@ +package azurerm + +import ( + "fmt" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-12-01/network" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" +) + +func TestAccAzureRMFirewallNatRuleCollection_basic(t *testing.T) { + resourceName := "azurerm_firewall_nat_rule_collection.test" + ri := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFirewallDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFirewallNatRuleCollection_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFirewallNatRuleCollectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "acctestnrc"), + resource.TestCheckResourceAttr(resourceName, "priority", "100"), + resource.TestCheckResourceAttr(resourceName, "action", "Dnat"), + resource.TestCheckResourceAttr(resourceName, "rule.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMFirewallNatRuleCollection_requiresImport(t *testing.T) { + if !requireResourcesToBeImported { + t.Skip("Skipping since resources aren't required to be imported") + return + } + + resourceName := "azurerm_firewall_nat_rule_collection.test" + ri := tf.AccRandTimeInt() + + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFirewallDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFirewallNatRuleCollection_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFirewallNatRuleCollectionExists(resourceName), + ), + }, + { + Config: testAccAzureRMFirewallNatRuleCollection_requiresImport(ri, location), + ExpectError: testRequiresImportError("azurerm_firewall_nat_rule_collection"), + }, + }, + }) +} + +func TestAccAzureRMFirewallNatRuleCollection_updatedName(t *testing.T) { + resourceName := "azurerm_firewall_nat_rule_collection.test" + ri := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFirewallDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFirewallNatRuleCollection_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFirewallNatRuleCollectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "acctestnrc"), + resource.TestCheckResourceAttr(resourceName, "priority", "100"), + resource.TestCheckResourceAttr(resourceName, "action", "Dnat"), + resource.TestCheckResourceAttr(resourceName, "rule.#", "1"), + resource.TestCheckResourceAttr(resourceName, "rule.3765122797.name", "rule1"), + resource.TestCheckResourceAttr(resourceName, "rule.3765122797.translated_address", "53"), + resource.TestCheckResourceAttr(resourceName, "rule.3765122797.translated_port", "8.8.8.8"), + ), + }, + { + Config: testAccAzureRMFirewallNatRuleCollection_updatedName(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFirewallNatRuleCollectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "acctestnrc"), + resource.TestCheckResourceAttr(resourceName, "priority", "100"), + resource.TestCheckResourceAttr(resourceName, "action", "Dnat"), + resource.TestCheckResourceAttr(resourceName, "rule.#", "1"), + resource.TestCheckResourceAttr(resourceName, "rule.1700340761.name", "rule2"), + ), + }, + }, + }) +} + +func TestAccAzureRMFirewallNatRuleCollection_multipleRuleCollections(t *testing.T) { + firstRule := "azurerm_firewall_nat_rule_collection.test" + secondRule := "azurerm_firewall_nat_rule_collection.test_add" + ri := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFirewallDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFirewallNatRuleCollection_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFirewallNatRuleCollectionExists(firstRule), + resource.TestCheckResourceAttr(firstRule, "name", "acctestnrc"), + resource.TestCheckResourceAttr(firstRule, "priority", "100"), + resource.TestCheckResourceAttr(firstRule, "action", "Dnat"), + resource.TestCheckResourceAttr(firstRule, "rule.#", "1"), + ), + }, + { + Config: testAccAzureRMFirewallNatRuleCollection_multiple(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFirewallNatRuleCollectionExists(firstRule), + resource.TestCheckResourceAttr(firstRule, "name", "acctestnrc"), + resource.TestCheckResourceAttr(firstRule, "priority", "100"), + resource.TestCheckResourceAttr(firstRule, "action", "Dnat"), + resource.TestCheckResourceAttr(firstRule, "rule.#", "1"), + testCheckAzureRMFirewallNatRuleCollectionExists(secondRule), + resource.TestCheckResourceAttr(secondRule, "name", "acctestnrc_add"), + resource.TestCheckResourceAttr(secondRule, "priority", "200"), + resource.TestCheckResourceAttr(secondRule, "action", "Snat"), + resource.TestCheckResourceAttr(secondRule, "rule.#", "1"), + ), + }, + { + Config: testAccAzureRMFirewallNatRuleCollection_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFirewallNatRuleCollectionExists(firstRule), + resource.TestCheckResourceAttr(firstRule, "name", "acctestnrc"), + resource.TestCheckResourceAttr(firstRule, "priority", "100"), + resource.TestCheckResourceAttr(firstRule, "action", "Dnat"), + resource.TestCheckResourceAttr(firstRule, "rule.#", "1"), + testCheckAzureRMFirewallNatRuleCollectionDoesNotExist("azurerm_firewall.test", "acctestnrc_add"), + ), + }, + }, + }) +} + +func TestAccAzureRMFirewallNatRuleCollection_update(t *testing.T) { + firstResourceName := "azurerm_firewall_nat_rule_collection.test" + secondResourceName := "azurerm_firewall_nat_rule_collection.test_add" + ri := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFirewallDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFirewallNatRuleCollection_multiple(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFirewallNatRuleCollectionExists(firstResourceName), + resource.TestCheckResourceAttr(firstResourceName, "name", "acctestnrc"), + resource.TestCheckResourceAttr(firstResourceName, "priority", "100"), + resource.TestCheckResourceAttr(firstResourceName, "action", "Dnat"), + resource.TestCheckResourceAttr(firstResourceName, "rule.#", "1"), + testCheckAzureRMFirewallNatRuleCollectionExists(secondResourceName), + resource.TestCheckResourceAttr(secondResourceName, "name", "acctestnrc_add"), + resource.TestCheckResourceAttr(secondResourceName, "priority", "200"), + resource.TestCheckResourceAttr(secondResourceName, "action", "Snat"), + resource.TestCheckResourceAttr(secondResourceName, "rule.#", "1"), + ), + }, + { + Config: testAccAzureRMFirewallNatRuleCollection_multipleUpdate(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFirewallNatRuleCollectionExists(firstResourceName), + resource.TestCheckResourceAttr(firstResourceName, "name", "acctestnrc"), + resource.TestCheckResourceAttr(firstResourceName, "priority", "300"), + resource.TestCheckResourceAttr(firstResourceName, "action", "Snat"), + resource.TestCheckResourceAttr(firstResourceName, "rule.#", "1"), + testCheckAzureRMFirewallNatRuleCollectionExists(secondResourceName), + resource.TestCheckResourceAttr(secondResourceName, "name", "acctestnrc_add"), + resource.TestCheckResourceAttr(secondResourceName, "priority", "400"), + resource.TestCheckResourceAttr(secondResourceName, "action", "Dnat"), + resource.TestCheckResourceAttr(secondResourceName, "rule.#", "1"), + ), + }, + }, + }) +} + +func TestAccAzureRMFirewallNatRuleCollection_disappears(t *testing.T) { + resourceName := "azurerm_firewall_nat_rule_collection.test" + ri := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFirewallDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFirewallNatRuleCollection_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFirewallNatRuleCollectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "acctestnrc"), + resource.TestCheckResourceAttr(resourceName, "priority", "100"), + resource.TestCheckResourceAttr(resourceName, "action", "Dnat"), + resource.TestCheckResourceAttr(resourceName, "rule.#", "1"), + testCheckAzureRMFirewallNatRuleCollectionDisappears(resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAzureRMFirewallNatRuleCollection_multipleRules(t *testing.T) { + resourceName := "azurerm_firewall_nat_rule_collection.test" + ri := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFirewallDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFirewallNatRuleCollection_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFirewallNatRuleCollectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "acctestnrc"), + resource.TestCheckResourceAttr(resourceName, "priority", "100"), + resource.TestCheckResourceAttr(resourceName, "action", "Dnat"), + resource.TestCheckResourceAttr(resourceName, "rule.#", "1"), + ), + }, + { + Config: testAccAzureRMFirewallNatRuleCollection_multipleRules(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFirewallNatRuleCollectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "acctestnrc"), + resource.TestCheckResourceAttr(resourceName, "priority", "100"), + resource.TestCheckResourceAttr(resourceName, "action", "Dnat"), + resource.TestCheckResourceAttr(resourceName, "rule.#", "2"), + ), + }, + { + Config: testAccAzureRMFirewallNatRuleCollection_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFirewallNatRuleCollectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "acctestnrc"), + resource.TestCheckResourceAttr(resourceName, "priority", "100"), + resource.TestCheckResourceAttr(resourceName, "action", "Dnat"), + resource.TestCheckResourceAttr(resourceName, "rule.#", "1"), + ), + }, + }, + }) +} + +func TestAccAzureRMFirewallNatRuleCollection_updateFirewallTags(t *testing.T) { + resourceName := "azurerm_firewall_nat_rule_collection.test" + ri := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFirewallDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMFirewallNatRuleCollection_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFirewallNatRuleCollectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "acctestnrc"), + resource.TestCheckResourceAttr(resourceName, "priority", "100"), + resource.TestCheckResourceAttr(resourceName, "action", "Dnat"), + resource.TestCheckResourceAttr(resourceName, "rule.#", "1"), + ), + }, + { + Config: testAccAzureRMFirewallNatRuleCollection_updateFirewallTags(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFirewallNatRuleCollectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "acctestnrc"), + resource.TestCheckResourceAttr(resourceName, "priority", "100"), + resource.TestCheckResourceAttr(resourceName, "action", "Dnat"), + resource.TestCheckResourceAttr(resourceName, "rule.#", "1"), + ), + }, + }, + }) +} + +func testCheckAzureRMFirewallNatRuleCollectionExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + name := rs.Primary.Attributes["name"] + firewallName := rs.Primary.Attributes["azure_firewall_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + client := testAccProvider.Meta().(*ArmClient).azureFirewallsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + read, err := client.Get(ctx, resourceGroup, firewallName) + if err != nil { + return err + } + + found := false + for _, collection := range *read.AzureFirewallPropertiesFormat.NatRuleCollections { + if *collection.Name == name { + found = true + break + } + } + + if !found { + return fmt.Errorf("Expected NAT Rule Collection %q (Firewall %q / Resource Group %q) to exist but it didn't", name, firewallName, resourceGroup) + } + + return nil + } +} + +func testCheckAzureRMFirewallNatRuleCollectionDoesNotExist(resourceName string, collectionName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + firewallName := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + client := testAccProvider.Meta().(*ArmClient).azureFirewallsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + read, err := client.Get(ctx, resourceGroup, firewallName) + if err != nil { + return err + } + + for _, collection := range *read.AzureFirewallPropertiesFormat.NatRuleCollections { + if *collection.Name == collectionName { + return fmt.Errorf("NAT Rule Collection %q exists in Firewall %q: %+v", collectionName, firewallName, collection) + } + } + + return nil + } +} + +func testCheckAzureRMFirewallNatRuleCollectionDisappears(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + name := rs.Primary.Attributes["name"] + firewallName := rs.Primary.Attributes["azure_firewall_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + client := testAccProvider.Meta().(*ArmClient).azureFirewallsClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + read, err := client.Get(ctx, resourceGroup, firewallName) + if err != nil { + return err + } + + rules := make([]network.AzureFirewallNatRuleCollection, 0) + for _, collection := range *read.AzureFirewallPropertiesFormat.NatRuleCollections { + if *collection.Name != name { + rules = append(rules, collection) + } + } + + read.AzureFirewallPropertiesFormat.NatRuleCollections = &rules + + future, err := client.CreateOrUpdate(ctx, resourceGroup, firewallName, read) + if err != nil { + return fmt.Errorf("Error removing NAT Rule Collection from Firewall: %+v", err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for the removal of NAT Rule Collection from Firewall: %+v", err) + } + + _, err = client.Get(ctx, resourceGroup, firewallName) + return err + } +} + +func testAccAzureRMFirewallNatRuleCollection_basic(rInt int, location string) string { + template := testAccAzureRMFirewall_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_firewall_nat_rule_collection" "test" { + name = "acctestnrc" + azure_firewall_name = "${azurerm_firewall.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + priority = 100 + action = "Dnat" + + rule { + name = "rule1" + + source_addresses = [ + "10.0.0.0/16", + ] + + destination_ports = [ + "53", + ] + + destination_addresses = [ + "8.8.8.8", + ] + + protocols = [ + "Any", + ] + + translated_port = 53 + translated_address = "8.8.8.8" + } +} +`, template) +} + +func testAccAzureRMFirewallNatRuleCollection_requiresImport(rInt int, location string) string { + template := testAccAzureRMFirewallNatRuleCollection_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_firewall_nat_rule_collection" "import" { + name = "${azurerm_firewall_nat_rule_collection.test.name}" + azure_firewall_name = "${azurerm_firewall_nat_rule_collection.test.azure_firewall_name}" + resource_group_name = "${azurerm_firewall_nat_rule_collection.test.resource_group_name}" + priority = 100 + action = "Dnat" + + rule { + name = "rule1" + + source_addresses = [ + "10.0.0.0/16", + ] + + destination_ports = [ + "53", + ] + + destination_addresses = [ + "8.8.8.8", + ] + + protocols = [ + "Any", + ] + + translated_port = 53 + translated_address = "8.8.8.8" + } +} +`, template) +} + +func testAccAzureRMFirewallNatRuleCollection_updatedName(rInt int, location string) string { + template := testAccAzureRMFirewall_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_firewall_nat_rule_collection" "test" { + name = "acctestnrc" + azure_firewall_name = "${azurerm_firewall.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + priority = 100 + action = "Dnat" + + rule { + name = "rule2" + + source_addresses = [ + "10.0.0.0/16", + ] + + destination_ports = [ + "53", + ] + + destination_addresses = [ + "8.8.8.8", + ] + + protocols = [ + "TCP", + ] + + translated_port = 53 + translated_address = "8.8.8.8" + } +} +`, template) +} + +func testAccAzureRMFirewallNatRuleCollection_multiple(rInt int, location string) string { + template := testAccAzureRMFirewall_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_firewall_nat_rule_collection" "test" { + name = "acctestnrc" + azure_firewall_name = "${azurerm_firewall.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + priority = 100 + action = "Dnat" + + rule { + name = "acctestrule" + + source_addresses = [ + "10.0.0.0/16", + ] + + destination_ports = [ + "53", + ] + + destination_addresses = [ + "8.8.8.8", + ] + + protocols = [ + "TCP", + ] + + translated_port = 53 + translated_address = "8.8.8.8" + } +} + +resource "azurerm_firewall_nat_rule_collection" "test_add" { + name = "acctestnrc_add" + azure_firewall_name = "${azurerm_firewall.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + priority = 200 + action = "Snat" + + rule { + name = "acctestruleadd" + + source_addresses = [ + "10.0.0.0/8", + ] + + destination_ports = [ + "8080", + ] + + destination_addresses = [ + "8.8.4.4", + ] + + protocols = [ + "TCP", + ] + + translated_port = 8080 + translated_address = "8.8.4.4" + } +} +`, template) +} + +func testAccAzureRMFirewallNatRuleCollection_multipleUpdate(rInt int, location string) string { + template := testAccAzureRMFirewall_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_firewall_nat_rule_collection" "test" { + name = "acctestnrc" + azure_firewall_name = "${azurerm_firewall.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + priority = 300 + action = "Snat" + + rule { + name = "acctestrule" + + source_addresses = [ + "10.0.0.0/16", + ] + + destination_ports = [ + "53", + ] + + destination_addresses = [ + "8.8.8.8", + ] + + protocols = [ + "TCP", + ] + + translated_port = 53 + translated_address = "10.0.0.1" + } +} + +resource "azurerm_firewall_nat_rule_collection" "test_add" { + name = "acctestnrc_add" + azure_firewall_name = "${azurerm_firewall.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + priority = 400 + action = "Dnat" + + rule { + name = "acctestruleadd" + + source_addresses = [ + "10.0.0.0/8", + ] + + destination_ports = [ + "8080", + ] + + destination_addresses = [ + "8.8.4.4", + ] + + protocols = [ + "TCP", + ] + + translated_port = 8080 + translated_address = "10.0.0.1" + } +} +`, template) +} + +func testAccAzureRMFirewallNatRuleCollection_multipleRules(rInt int, location string) string { + template := testAccAzureRMFirewall_basic(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_firewall_nat_rule_collection" "test" { + name = "acctestnrc" + azure_firewall_name = "${azurerm_firewall.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + priority = 100 + action = "Dnat" + + rule { + name = "acctestrule" + + source_addresses = [ + "10.0.0.0/16", + ] + + destination_ports = [ + "53", + ] + + destination_addresses = [ + "8.8.8.8", + ] + + protocols = [ + "TCP", + ] + + translated_port = 53 + translated_address = "10.0.0.1" + } + + rule { + name = "acctestrule_add" + + source_addresses = [ + "192.168.0.1", + ] + + destination_ports = [ + "8888", + ] + + destination_addresses = [ + "1.1.1.1", + ] + + protocols = [ + "TCP", + ] + + translated_port = 8888 + translated_address = "192.168.0.1" + } +} +`, template) +} + +func testAccAzureRMFirewallNatRuleCollection_updateFirewallTags(rInt int, location string) string { + template := testAccAzureRMFirewall_withTags(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_firewall_nat_rule_collection" "test" { + name = "acctestnrc" + azure_firewall_name = "${azurerm_firewall.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + priority = 100 + action = "Dnat" + + rule { + name = "rule1" + + source_addresses = [ + "10.0.0.0/16", + ] + + destination_ports = [ + "53", + ] + + destination_addresses = [ + "8.8.8.8", + ] + + protocols = [ + "TCP", + ] + + translated_port = 53 + translated_address = "10.0.0.1" + } +} +`, template) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 10595cf35047..99485b74611d 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -1080,6 +1080,10 @@ azurerm_firewall_application_rule_collection + > + azurerm_firewall_nat_rule_collection + + > azurerm_firewall_network_rule_collection diff --git a/website/docs/r/firewall_nat_rule_collection.html.markdown b/website/docs/r/firewall_nat_rule_collection.html.markdown new file mode 100644 index 000000000000..d4bd6542ec4b --- /dev/null +++ b/website/docs/r/firewall_nat_rule_collection.html.markdown @@ -0,0 +1,129 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_firewall_nat_rule_collection" +sidebar_current: "docs-azurerm-resource-network-firewall-nat-rule-collection" +description: |- + Manages a NAT Rule Collection within an Azure Firewall. + +--- + +# azurerm_firewall_nat_rule_collection + +Manages a NAT Rule Collection within an Azure Firewall. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "test" { + name = "example-resources" + location = "North Europe" +} + +resource "azurerm_virtual_network" "test" { + name = "testvnet" + address_space = ["10.0.0.0/16"] + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_subnet" "test" { + name = "AzureFirewallSubnet" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.1.0/24" +} + +resource "azurerm_public_ip" "test" { + name = "testpip" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + allocation_method = "Static" + sku = "Standard" +} + +resource "azurerm_firewall" "test" { + name = "testfirewall" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + ip_configuration { + name = "configuration" + subnet_id = "${azurerm_subnet.test.id}" + public_ip_address_id = "${azurerm_public_ip.test.id}" + } +} + +resource "azurerm_firewall_nat_rule_collection" "test" { + name = "testcollection" + azure_firewall_name = "${azurerm_firewall.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + priority = 100 + action = "Dnat" + + rule { + name = "testrule" + + source_addresses = [ + "10.0.0.0/16", + ] + + destination_ports = [ + "53", + ] + + destination_addresses = [ + "8.8.8.8", + "8.8.4.4", + ] + + protocols = [ + "TCP", + "UDP", + ] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Specifies the name of the NAT Rule Collection which must be unique within the Firewall. Changing this forces a new resource to be created. + +* `azure_firewall_name` - (Required) Specifies the name of the Firewall in which the NAT Rule Collection should be created. Changing this forces a new resource to be created. + +* `resource_group_name` - (Required) Specifies the name of the Resource Group in which the Firewall exists. Changing this forces a new resource to be created. + +* `priority` - (Required) Specifies the priority of the rule collection. Possible values are between `100` - `65000`. + +* `action` - (Required) Specifies the action the rule will apply to matching traffic. Possible values are `Dnat` and `Snat`. + +* `rule` - (Required) One or more `rule` blocks as defined below. + +--- + +A `rule` block supports the following: + +* `name` - (Required) Specifies the name of the rule. + +* `description` - (Optional) Specifies a description for the rule. + +* `destination_addresses` - (Required) A list of destination IP addresses and/or IP ranges. + +* `destination_ports` - (Required) A list of destination ports. + +* `protocols` - (Required) A list of protocols. Possible values are `Any`, `ICMP`, `TCP` and `UDP`. If `action` is `Dnat`, protocols can only be `TCP` and `UDP`. + +* `source_addresses` - (Required) A list of source IP addresses and/or IP ranges. + +* `translated_address` - (Required) The address of the service behind the Firewall. + +* `translated_port` - (Required) The port of the service behind the Firewall. + +## Import + +Azure Firewall NAT Rule Collections can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_firewall_nat_rule_collection.test /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.Network/azureFirewalls/myfirewall/natRuleCollections/mycollection +```