Skip to content

Support for FQDNs in Network and NAT Rules for Azure Firewall #10388

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

Merged
merged 7 commits into from
Oct 25, 2019
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
86 changes: 82 additions & 4 deletions src/Network/Network.Test/ScenarioTests/AzureFirewallTests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@ function Test-AzureFirewallCRUD {
$networkRule1Protocol3 = "ICMP"
$networkRule1DestinationPort1 = "90"

# AzureFirewallNetworkRule 2
$networkRule2Name = "networkRule2"
$networkRule2Desc = "desc2"
$networkRule2SourceAddress1 = "10.0.0.0"
$networkRule2SourceAddress2 = "111.1.0.0/24"
$networkRule2DestinationFqdn1 = "www.bing.com"
$networkRule2Protocol1 = "UDP"
$networkRule2Protocol2 = "TCP"
$networkRule2Protocol3 = "ICMP"
$networkRule2DestinationPort1 = "80"

# AzureFirewallNatRuleCollection
$natRcName = "natRc"
$natRcPriority = 200
Expand All @@ -96,6 +107,17 @@ function Test-AzureFirewallCRUD {
$natRule1TranslatedAddress = "10.1.2.3"
$natRule1TranslatedPort = "91"

# AzureFirewallNatRule 2
$natRule2Name = "natRule2"
$natRule2Desc = "desc2"
$natRule2SourceAddress1 = "10.0.0.0"
$natRule2SourceAddress2 = "111.1.0.0/24"
$natRule2Protocol1 = "UDP"
$natRule2Protocol2 = "TCP"
$natRule2DestinationPort1 = "95"
$natRule2TranslatedFqdn = "server1.internal.com"
$natRule2TranslatedPort = "96"

try {
# Create the resource group
$resourceGroup = New-AzResourceGroup -Name $rgname -Location $location -Tags @{ testtag = "testval" }
Expand Down Expand Up @@ -189,6 +211,13 @@ function Test-AzureFirewallCRUD {
# Create Network Rule Collection
$netRc = New-AzFirewallNetworkRuleCollection -Name $networkRcName -Priority $networkRcPriority -Rule $networkRule -ActionType $networkRcActionType

# Create Second Network Rule
$networkRule2 = New-AzFirewallNetworkRule -Name $networkRule2Name -Description $networkRule2Desc -Protocol $networkRule2Protocol1, $networkRule2Protocol2 -SourceAddress $networkRule2SourceAddress1, $networkRule2SourceAddress2 -DestinationFqdn $networkRule2DestinationFqdn1 -DestinationPort $networkRule2DestinationPort1
$networkRule2.AddProtocol($networkRule2Protocol3)

# Add this second Network Rule to the rule collection
$netRc.AddRule($networkRule2)

# Create a NAT rule
$natRule = New-AzFirewallNatRule -Name $natRule1Name -Description $natRule1Desc -Protocol $natRule1Protocol1 -SourceAddress $natRule1SourceAddress1, $natRule1SourceAddress2 -DestinationAddress $publicip.IpAddress -DestinationPort $natRule1DestinationPort1 -TranslatedAddress $natRule1TranslatedAddress -TranslatedPort $natRule1TranslatedPort
$natRule.AddProtocol($natRule1Protocol2)
Expand All @@ -203,9 +232,16 @@ function Test-AzureFirewallCRUD {
} "The argument `"ICMP`" does not belong to the set"
Assert-ThrowsContains { $natRule.AddProtocol("ICMP") } "Invalid protocol"

# Create second NAT rule
$natRule2 = New-AzFirewallNatRule -Name $natRule2Name -Description $natRule2Desc -Protocol $natRule2Protocol1 -SourceAddress $natRule2SourceAddress1, $natRule2SourceAddress2 -DestinationAddress $publicip.IpAddress -DestinationPort $natRule2DestinationPort1 -TranslatedFqdn $natRule2TranslatedFqdn -TranslatedPort $natRule2TranslatedPort
$natRule2.AddProtocol($natRule2Protocol2)

# Create a NAT Rule Collection
$natRc = New-AzFirewallNatRuleCollection -Name $natRcName -Priority $natRcPriority -Rule $natRule

# Add second NAT Rule to rule Collection
$natRc.AddRule($natRule2)

# Add ApplicationRuleCollections to the Firewall using method AddApplicationRuleCollection
$azureFirewall.AddApplicationRuleCollection($appRc)
$azureFirewall.AddApplicationRuleCollection($appRc2)
Expand Down Expand Up @@ -245,10 +281,10 @@ function Test-AzureFirewallCRUD {
Assert-AreEqual 1 @($getAzureFirewall.ApplicationRuleCollections[1].Rules).Count

Assert-AreEqual 1 @($getAzureFirewall.NatRuleCollections).Count
Assert-AreEqual 1 @($getAzureFirewall.NatRuleCollections[0].Rules).Count
Assert-AreEqual 2 @($getAzureFirewall.NatRuleCollections[0].Rules).Count

Assert-AreEqual 1 @($getAzureFirewall.NetworkRuleCollections).Count
Assert-AreEqual 1 @($getAzureFirewall.NetworkRuleCollections[0].Rules).Count
Assert-AreEqual 2 @($getAzureFirewall.NetworkRuleCollections[0].Rules).Count

$appRc = $getAzureFirewall.GetApplicationRuleCollectionByName($appRcName)
$appRule = $appRc.GetRuleByName($appRule1Name)
Expand Down Expand Up @@ -329,7 +365,7 @@ function Test-AzureFirewallCRUD {
Assert-AreEqual $appRule1Fqdn1 $appRule.TargetFqdns[0]
Assert-AreEqual $appRule1Fqdn2 $appRule.TargetFqdns[1]

# Verify NAT rule collection and NAT rule
# Verify NAT rule collection and NAT rules
$natRc = $getAzureFirewall.GetNatRuleCollectionByName($natRcName)
$natRule = $natRc.GetRuleByName($natRule1Name)

Expand All @@ -356,7 +392,29 @@ function Test-AzureFirewallCRUD {
Assert-AreEqual $natRule1TranslatedAddress $natRule.TranslatedAddress
Assert-AreEqual $natRule1TranslatedPort $natRule.TranslatedPort

# Verify network rule collection and network rule
$natRule2 = $natRc.GetRuleByName($natRule2Name)

Assert-AreEqual $natRule2Name $natRule2.Name
Assert-AreEqual $natRule2Desc $natRule2.Description

Assert-AreEqual 2 $natRule2.SourceAddresses.Count
Assert-AreEqual $natRule2SourceAddress1 $natRule2.SourceAddresses[0]
Assert-AreEqual $natRule2SourceAddress2 $natRule2.SourceAddresses[1]

Assert-AreEqual 1 $natRule2.DestinationAddresses.Count
Assert-AreEqual $publicip.IpAddress $natRule2.DestinationAddresses[0]

Assert-AreEqual 2 $natRule2.Protocols.Count
Assert-AreEqual $natRule2Protocol1 $natRule2.Protocols[0]
Assert-AreEqual $natRule2Protocol2 $natRule2.Protocols[1]

Assert-AreEqual 1 $natRule2.DestinationPorts.Count
Assert-AreEqual $natRule2DestinationPort1 $natRule2.DestinationPorts[0]

Assert-AreEqual $natRule2TranslatedFqdn $natRule2.TranslatedFqdn
Assert-AreEqual $natRule2TranslatedPort $natRule2.TranslatedPort

# Verify network rule collection and network rules
$networkRc = $getAzureFirewall.GetNetworkRuleCollectionByName($networkRcName)
$networkRule = $networkRc.GetRuleByName($networkRule1Name)

Expand All @@ -382,6 +440,26 @@ function Test-AzureFirewallCRUD {
Assert-AreEqual 1 $networkRule.DestinationPorts.Count
Assert-AreEqual $networkRule1DestinationPort1 $networkRule.DestinationPorts[0]

$networkRule2 = $networkRc.GetRuleByName($networkRule2Name)

Assert-AreEqual $networkRule2Name $networkRule2.Name
Assert-AreEqual $networkRule2Desc $networkRule2.Description

Assert-AreEqual 2 $networkRule2.SourceAddresses.Count
Assert-AreEqual $networkRule2SourceAddress1 $networkRule2.SourceAddresses[0]
Assert-AreEqual $networkRule2SourceAddress2 $networkRule2.SourceAddresses[1]

Assert-AreEqual 1 $networkRule2.DestinationFqdns.Count
Assert-AreEqual $networkRule2DestinationFqdn1 $networkRule2.DestinationFqdns[0]

Assert-AreEqual 3 $networkRule2.Protocols.Count
Assert-AreEqual $networkRule2Protocol1 $networkRule2.Protocols[0]
Assert-AreEqual $networkRule2Protocol2 $networkRule2.Protocols[1]
Assert-AreEqual $networkRule2Protocol3 $networkRule2.Protocols[2]

Assert-AreEqual 1 $networkRule2.DestinationPorts.Count
Assert-AreEqual $networkRule2DestinationPort1 $networkRule2.DestinationPorts[0]

# Delete AzureFirewall
$delete = Remove-AzFirewall -ResourceGroupName $rgname -name $azureFirewallName -PassThru -Force
Assert-AreEqual true $delete
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,17 @@ public class NewAzureFirewallNatRuleCommand : NetworkBaseCmdlet
public string[] Protocol { get; set; }

[Parameter(
Mandatory = true,
Mandatory = false,
HelpMessage = "The translated address for this NAT rule")]
[ValidateNotNullOrEmpty]
public string TranslatedAddress { get; set; }

[Parameter(
Mandatory = false,
HelpMessage = "The translated FQDN for this NAT rule")]
[ValidateNotNullOrEmpty]
public string TranslatedFqdn { get; set; }

[Parameter(
Mandatory = true,
HelpMessage = "The translated port for this NAT rule")]
Expand All @@ -95,7 +101,26 @@ public override void Execute()
}

ValidateIsSingleIpNotRange(DestinationAddress.Single());
ValidateIsSingleIpNotRange(TranslatedAddress);
if (TranslatedAddress != null)
{
ValidateIsSingleIpNotRange(TranslatedAddress);
}
if (TranslatedFqdn != null)
{
ValidateIsFqdn(TranslatedFqdn);
}

// Only one of TranslatedAddress or TranslatedFqdn is allowed
if ((TranslatedAddress != null) && (TranslatedFqdn != null))
{
throw new ArgumentException("Both TranslatedAddress and TranslatedFqdn not allowed");
}

// One of TranslatedAddress or TranslatedFqdn must be present
if ((TranslatedAddress == null) && (TranslatedFqdn == null))
{
throw new ArgumentException("Either TranslatedAddress or TranslatedFqdn is required");
}

ValidateIsSinglePortNotRange(DestinationPort.Single());
ValidateIsSinglePortNotRange(TranslatedPort);
Expand All @@ -110,6 +135,7 @@ public override void Execute()
DestinationAddresses = this.DestinationAddress?.ToList(),
DestinationPorts = this.DestinationPort?.ToList(),
TranslatedAddress = this.TranslatedAddress,
TranslatedFqdn = this.TranslatedFqdn,
TranslatedPort = this.TranslatedPort
};
WriteObject(networkRule);
Expand All @@ -133,5 +159,15 @@ private void ValidateIsSinglePortNotRange(string portStr)
throw new ArgumentException($"Invalid value {portStr}. Only a single port value is accepted (e.g. 8080).");
}
}

private void ValidateIsFqdn(string fqdn)
{
var fqdnRegEx = new Regex("^[a-zA-Z0-9]+(([a-zA-Z0-9_\\-]*[a-zA-Z0-9]+)*\\.)*(?:[a-zA-Z0-9]{2,})$");

if (!fqdnRegEx.IsMatch(fqdn))
{
throw new ArgumentException($"Invalid value {fqdn}.");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using System.Linq;
using System.Management.Automation;
using Microsoft.Azure.Commands.Network.Models;
using System.Text.RegularExpressions;
using MNM = Microsoft.Azure.Management.Network.Models;

namespace Microsoft.Azure.Commands.Network
Expand All @@ -43,11 +44,17 @@ public class NewAzureFirewallNetworkRuleCommand : NetworkBaseCmdlet
public string[] SourceAddress { get; set; }

[Parameter(
Mandatory = true,
Mandatory = false,
HelpMessage = "The destination addresses of the rule")]
[ValidateNotNullOrEmpty]
public string[] DestinationAddress { get; set; }

[Parameter(
Mandatory = false,
HelpMessage = "The destination FQDN of the rule")]
[ValidateNotNullOrEmpty]
public string[] DestinationFqdn { get; set; }

[Parameter(
Mandatory = true,
HelpMessage = "The destination ports of the rule")]
Expand All @@ -68,17 +75,48 @@ public class NewAzureFirewallNetworkRuleCommand : NetworkBaseCmdlet
public override void Execute()
{
base.Execute();


if (DestinationFqdn != null)
{
foreach (string fqdn in DestinationFqdn)
{
ValidateIsFqdn(fqdn);
}
}

// Only one of DestinationAddress or DestinationFqdns is allowed
if ((DestinationAddress != null) && (DestinationFqdn != null))
{
throw new ArgumentException("Both DestinationAddress and DestinationFqdns not allowed");
}

// One of DestinationAddress or DestinationFqdns must be present
if ((DestinationAddress == null) && (DestinationFqdn == null))
{
throw new ArgumentException("Either DestinationAddress or DestinationFqdns is required");
}

var networkRule = new PSAzureFirewallNetworkRule
{
Name = this.Name,
Description = this.Description,
Protocols = this.Protocol?.ToList(),
SourceAddresses = this.SourceAddress?.ToList(),
DestinationAddresses = this.DestinationAddress?.ToList(),
DestinationFqdns = this.DestinationFqdn?.ToList(),
DestinationPorts = this.DestinationPort?.ToList()
};
WriteObject(networkRule);
}

private void ValidateIsFqdn(string fqdn)
{
var fqdnRegEx = new Regex("^\\*$|^[a-zA-Z0-9]+(([a-zA-Z0-9_\\-]*[a-zA-Z0-9]+)*\\.)*(?:[a-zA-Z0-9]{2,})$");

if (!fqdnRegEx.IsMatch(fqdn))
{
throw new ArgumentException($"Invalid value {fqdn}.");
}
}
}
}
1 change: 1 addition & 0 deletions src/Network/Network/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
- New-AzureRmExpressRouteConnection : added parameter EnableInternetSecurity
- Set-AzureRmExpressRouteConnection : added parameter EnableInternetSecurity
* Fix required subnet with name AzureBastionSubnet in `PSBastion` can be case insensitive
* Support for Destination FQDNs in Network Rules and Translated FQDN in NAT Rules for Azure Firewall

## Version 1.15.0
* Add new cmdlet Get-AzAvailableServiceAlias which can be called to get the aliases that can be used for Service Endpoint Policies.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public class PSAzureFirewallNatRule

public string TranslatedAddress { get; set; }

public string TranslatedFqdn { get; set; }

public string TranslatedPort { get; set; }

[JsonIgnore]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ public class PSAzureFirewallNetworkRule
public List<string> SourceAddresses { get; set; }

public List<string> DestinationAddresses { get; set; }


public List<string> DestinationFqdns { get; set; }

public List<string> DestinationPorts { get; set; }

[JsonIgnore]
Expand All @@ -49,7 +51,13 @@ public string DestinationAddressesText
{
get { return JsonConvert.SerializeObject(DestinationAddresses, Formatting.Indented); }
}


[JsonIgnore]
public string DestinationFqdnsText
{
get { return JsonConvert.SerializeObject(DestinationFqdns, Formatting.Indented); }
}

[JsonIgnore]
public string DestinationPortsText
{
Expand Down