Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_firewall: supports dns_setting #8878

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ func resourceArmFirewallNetworkRuleCollection() *schema.Resource {
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"destination_fqdns": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"protocols": {
Type: schema.TypeSet,
Required: true,
Expand Down Expand Up @@ -412,8 +418,13 @@ func expandArmFirewallNetworkRules(input *schema.Set) (*[]network.AzureFirewallN
destinationIpGroups = append(destinationIpGroups, v.(string))
}

if len(destinationAddresses) == 0 && len(destinationIpGroups) == 0 {
return nil, fmt.Errorf("at least one of %q and %q must be specified for each rule", "destination_addresses", "destination_ip_groups")
destinationFqdns := make([]string, 0)
for _, v := range rule["destination_fqdns"].(*schema.Set).List() {
destinationFqdns = append(destinationFqdns, v.(string))
}

if len(destinationAddresses) == 0 && len(destinationIpGroups) == 0 && len(destinationFqdns) == 0 {
return nil, fmt.Errorf("at least one of %q, %q and %q must be specified for each rule", "destination_addresses", "destination_ip_groups", "destination_fqdns")
}

destinationPorts := make([]string, 0)
Expand All @@ -429,6 +440,7 @@ func expandArmFirewallNetworkRules(input *schema.Set) (*[]network.AzureFirewallN
DestinationAddresses: &destinationAddresses,
DestinationIPGroups: &destinationIpGroups,
DestinationPorts: &destinationPorts,
DestinationFqdns: &destinationFqdns,
}

nrProtocols := make([]network.AzureFirewallNetworkRuleProtocol, 0)
Expand All @@ -451,36 +463,58 @@ func flattenFirewallNetworkRuleCollectionRules(rules *[]network.AzureFirewallNet
}

for _, rule := range *rules {
output := make(map[string]interface{})
var (
name string
description string
sourceAddresses *schema.Set
sourceIPGroups *schema.Set
destAddresses *schema.Set
destIPGroups *schema.Set
destPorts *schema.Set
destFqdns *schema.Set
)

if rule.Name != nil {
output["name"] = *rule.Name
name = *rule.Name
}
if rule.Description != nil {
output["description"] = *rule.Description
description = *rule.Description
}
if rule.SourceAddresses != nil {
output["source_addresses"] = set.FromStringSlice(*rule.SourceAddresses)
sourceAddresses = set.FromStringSlice(*rule.SourceAddresses)
}
if rule.SourceIPGroups != nil {
output["source_ip_groups"] = set.FromStringSlice(*rule.SourceIPGroups)
sourceIPGroups = set.FromStringSlice(*rule.SourceIPGroups)
}
if rule.DestinationAddresses != nil {
output["destination_addresses"] = set.FromStringSlice(*rule.DestinationAddresses)
destAddresses = set.FromStringSlice(*rule.DestinationAddresses)
}
if rule.DestinationIPGroups != nil {
output["destination_ip_groups"] = set.FromStringSlice(*rule.DestinationIPGroups)
destIPGroups = set.FromStringSlice(*rule.DestinationIPGroups)
}
if rule.DestinationPorts != nil {
output["destination_ports"] = set.FromStringSlice(*rule.DestinationPorts)
destPorts = set.FromStringSlice(*rule.DestinationPorts)
}
if rule.DestinationFqdns != nil {
destFqdns = set.FromStringSlice(*rule.DestinationFqdns)
}
magodo marked this conversation as resolved.
Show resolved Hide resolved
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)
outputs = append(outputs, map[string]interface{}{
"name": name,
"description": description,
"source_addresses": sourceAddresses,
"source_ip_groups": sourceIPGroups,
"destination_addresses": destAddresses,
"destination_ip_groups": destIPGroups,
"destination_ports": destPorts,
"destination_fqdns": destFqdns,
"protocols": set.FromStringSlice(protocols),
})
}
return outputs
}
61 changes: 59 additions & 2 deletions azurerm/internal/services/network/firewall_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"log"
"regexp"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-05-01/network"
Expand Down Expand Up @@ -121,6 +122,16 @@ func resourceArmFirewall() *schema.Resource {
}, false),
},

"dns_servers": {
Type: schema.TypeList,
Optional: true,
MinItems: 1,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.IsIPAddress,
},
},

"zones": azure.SchemaZones(),

"tags": tags.Schema(),
Expand Down Expand Up @@ -168,8 +179,9 @@ func resourceArmFirewallCreateUpdate(d *schema.ResourceData, meta interface{}) e
Location: &location,
Tags: tags.Expand(t),
AzureFirewallPropertiesFormat: &network.AzureFirewallPropertiesFormat{
IPConfigurations: ipConfigs,
ThreatIntelMode: network.AzureFirewallThreatIntelMode(d.Get("threat_intel_mode").(string)),
IPConfigurations: ipConfigs,
ThreatIntelMode: network.AzureFirewallThreatIntelMode(d.Get("threat_intel_mode").(string)),
AdditionalProperties: expandArmFirewallDNSServers(d.Get("dns_servers").([]interface{})),
},
Zones: zones,
}
Expand Down Expand Up @@ -283,7 +295,12 @@ func resourceArmFirewallRead(d *schema.ResourceData, meta interface{}) error {
if err := d.Set("management_ip_configuration", managementIPConfigs); err != nil {
return fmt.Errorf("Error setting `management_ip_configuration`: %+v", err)
}

d.Set("threat_intel_mode", string(props.ThreatIntelMode))

if err := d.Set("dns_servers", flattenArmFirewallDNSServers(props.AdditionalProperties)); err != nil {
return fmt.Errorf("Error setting `dns_servers`: %+v", err)
}
}

if err := d.Set("zones", azure.FlattenZones(read.Zones)); err != nil {
Expand Down Expand Up @@ -467,6 +484,46 @@ func flattenArmFirewallIPConfigurations(input *[]network.AzureFirewallIPConfigur
return result
}

func expandArmFirewallDNSServers(input []interface{}) map[string]*string {
if len(input) == 0 {
return map[string]*string{
"Network.DNS.EnableProxy": utils.String("false"),
}
}

var servers []string
for _, server := range input {
servers = append(servers, server.(string))
}

// Swagger issue asking finalize these properties: https://github.com/Azure/azure-rest-api-specs/issues/11278
return map[string]*string{
"Network.DNS.EnableProxy": utils.String("true"),
"Network.DNS.Servers": utils.String(strings.Join(servers, ",")),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we file a Swagger bug about these? Since this is a dictionary there's no guarantees there won't be breaking changes here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we link to this in the code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I have added them in my last commit.

}
}

func flattenArmFirewallDNSServers(input map[string]*string) []interface{} {
if len(input) == 0 {
return nil
}

enabled := false
if enabledPtr := input["Network.DNS.EnableProxy"]; enabledPtr != nil {
enabled = *enabledPtr == "true"
}

if !enabled {
return nil
}

servers := []string{}
if serversPtr := input["Network.DNS.Servers"]; serversPtr != nil {
servers = strings.Split(*serversPtr, ",")
}
return utils.FlattenStringSlice(&servers)
}

func ValidateAzureFirewallName(v interface{}, k string) (warnings []string, errors []error) {
value := v.(string)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,25 @@ func TestAccAzureRMFirewallNetworkRuleCollection_ipGroup(t *testing.T) {
})
}

func TestAccAzureRMFirewallNetworkRuleCollection_fqdns(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_firewall_network_rule_collection", "test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acceptance.PreCheck(t) },
Providers: acceptance.SupportedProviders,
CheckDestroy: testCheckAzureRMFirewallDestroy,
Steps: []resource.TestStep{
{
Config: testAccAzureRMFirewallNetworkRuleCollection_fqdns(data),
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMFirewallNetworkRuleCollectionExists(data.ResourceName),
),
},
data.ImportStep(),
},
})
}

func TestAccAzureRMFirewallNetworkRuleCollection_noSource(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_firewall_network_rule_collection", "test")

Expand All @@ -350,7 +369,7 @@ func TestAccAzureRMFirewallNetworkRuleCollection_noDestination(t *testing.T) {
Steps: []resource.TestStep{
{
Config: testAccAzureRMFirewallNetworkRuleCollection_noDestination(data),
ExpectError: regexp.MustCompile(fmt.Sprintf("at least one of %q and %q must be specified", "destination_addresses", "destination_ip_groups")),
ExpectError: regexp.MustCompile(fmt.Sprintf("at least one of %q, %q and %q must be specified", "destination_addresses", "destination_ip_groups", "destination_fqdns")),
},
},
})
Expand Down Expand Up @@ -869,6 +888,41 @@ resource "azurerm_firewall_network_rule_collection" "test" {
`, template)
}

func testAccAzureRMFirewallNetworkRuleCollection_fqdns(data acceptance.TestData) string {
template := testAccAzureRMFirewall_enableDNS(data)
return fmt.Sprintf(`
%s

resource "azurerm_firewall_network_rule_collection" "test" {
name = "acctestnrc"
azure_firewall_name = azurerm_firewall.test.name
resource_group_name = azurerm_resource_group.test.name
priority = 100
action = "Allow"

rule {
name = "rule1"

source_addresses = [
"10.0.0.0/16",
]

destination_fqdns = [
"time.windows.com"
]

destination_ports = [
"8080",
]

protocols = [
"Any",
]
}
}
`, template)
}

func testAccAzureRMFirewallNetworkRuleCollection_noSource(data acceptance.TestData) string {
template := testAccAzureRMFirewall_basic(data)
return fmt.Sprintf(`
Expand Down
94 changes: 94 additions & 0 deletions azurerm/internal/services/network/tests/firewall_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,46 @@ func TestAccAzureRMFirewall_basic(t *testing.T) {
})
}

func TestAccAzureRMFirewall_enableDNS(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_firewall", "test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acceptance.PreCheck(t) },
Providers: acceptance.SupportedProviders,
CheckDestroy: testCheckAzureRMFirewallDestroy,
Steps: []resource.TestStep{
{
Config: testAccAzureRMFirewall_basic(data),
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMFirewallExists(data.ResourceName),
),
},
data.ImportStep(),
{
Config: testAccAzureRMFirewall_enableDNS(data, "1.1.1.1", "8.8.8.8"),
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMFirewallExists(data.ResourceName),
),
},
data.ImportStep(),
{
Config: testAccAzureRMFirewall_enableDNS(data, "1.1.1.1"),
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMFirewallExists(data.ResourceName),
),
},
data.ImportStep(),
{
Config: testAccAzureRMFirewall_basic(data),
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMFirewallExists(data.ResourceName),
),
},
data.ImportStep(),
},
})
}

func TestAccAzureRMFirewall_withManagementIp(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_firewall", "test")

Expand Down Expand Up @@ -373,6 +413,60 @@ resource "azurerm_firewall" "test" {
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger)
}

func testAccAzureRMFirewall_enableDNS(data acceptance.TestData, dnsServers ...string) string {
servers := make([]string, len(dnsServers))
for idx, server := range dnsServers {
servers[idx] = fmt.Sprintf(`"%s"`, server)
}

return fmt.Sprintf(`
provider "azurerm" {
features {}
}

resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
location = "%s"
}

resource "azurerm_virtual_network" "test" {
name = "acctestvirtnet%d"
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_prefixes = ["10.0.1.0/24"]
}

resource "azurerm_public_ip" "test" {
name = "acctestpip%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
allocation_method = "Static"
sku = "Standard"
}

resource "azurerm_firewall" "test" {
name = "acctestfirewall%d"
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
}
threat_intel_mode = "Deny"
dns_servers = [%s]
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger, strings.Join(servers, ","))
}

func testAccAzureRMFirewall_withManagementIp(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
Expand Down
2 changes: 2 additions & 0 deletions website/docs/r/firewall.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ The following arguments are supported:

* `ip_configuration` - (Required) An `ip_configuration` block as documented below.

* `dns_servers` - (Optional) A list of DNS servers that the Azure Firewall will direct DNS traffic to the for name resolution.

* `management_ip_configuration` - (Optional) A `management_ip_configuration` block as documented below, which allows force-tunnelling of traffic to be performed by the firewall. Adding or removing this block or changing the `subnet_id` in an existing block forces a new resource to be created.

* `threat_intel_mode` - (Optional) The operation mode for threat intelligence-based filtering. Possible values are: `Off`, `Alert` and `Deny`. Defaults to `Alert`
Expand Down
Loading