Skip to content

Commit

Permalink
Add option for excluding subnets from NSG #2572 (#2624)
Browse files Browse the repository at this point in the history
* Add option for excluding subnets from NSG #2572

* Fix typo
  • Loading branch information
BernieWhite authored Jan 3, 2024
1 parent 84e8c48 commit 4067ebf
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 25 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@
"VNET",
"VNETs",
"webhook",
"webhooks"
"webhooks",
"xunit"
],
"cSpell.enabledLanguageIds": [
"csharp",
Expand Down
3 changes: 3 additions & 0 deletions docs/CHANGELOG-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ What's changed since v1.32.1:
- General improvements:
- Quality updates to rules and documentation by @BernieWhite.
[#1772](https://github.com/Azure/PSRule.Rules.Azure/issues/1772)
- Added option for excluding subnets to `Azure.VNET.UseNSGs` by @BernieWhite.
[#2572](https://github.com/Azure/PSRule.Rules.Azure/issues/2572)
- To add a subnet exclusion, set the `AZURE_VNET_SUBNET_EXCLUDED_FROM_NSG` option.
- Engineering:
- Bump xunit to v2.6.4.
[#2618](https://github.com/Azure/PSRule.Rules.Azure/pull/2618)
Expand Down
24 changes: 21 additions & 3 deletions docs/en/rules/Azure.VNET.UseNSGs.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
reviewed: 2023-09-10
reviewed: 2024-01-02
severity: Critical
pillar: Security
category: Network segmentation
category: SE:06 Network controls
resource: Virtual Network
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.VNET.UseNSGs/
---
Expand Down Expand Up @@ -146,13 +146,31 @@ $nsg = Get-AzNetworkSecurityGroup -Name '<nsg_name>' -ResourceGroupName '<resour
Set-AzVirtualNetworkSubnetConfig -Name '<subnet>' -VirtualNetwork $vnet -AddressPrefix '10.0.1.0/24' -NetworkSecurityGroup $nsg
```
## NOTES
If you identify a false postive for an Azure service that does not support NSGs,
please [open an issue](https://github.com/Azure/PSRule.Rules.Azure/issues/new) to help us improve this rule.
To exclude subnets that are specific to your environment, use the `AZURE_VNET_SUBNET_EXCLUDED_FROM_NSG` configuration option.
Any subnet names specified by this option will be ignored by this rule.
For example:
```yaml
configuration:
AZURE_VNET_SUBNET_EXCLUDED_FROM_NSG:
- subnet-1
- subnet-2
```
## LINKS
- [Implement network segmentation patterns on Azure](https://learn.microsoft.com/azure/well-architected/security/design-network-segmentation)
- [SE:06 Network controls](https://learn.microsoft.com/azure/well-architected/security/networking)
- [Network Security Best Practices](https://learn.microsoft.com/azure/security/fundamentals/network-best-practices#logically-segment-subnets)
- [Azure Firewall FAQ](https://learn.microsoft.com/azure/firewall/firewall-faq#are-network-security-groups--nsgs--supported-on-the-azurefirewallsubnet)
- [Forced tunneling configuration](https://learn.microsoft.com/azure/firewall/forced-tunneling#forced-tunneling-configuration)
- [Azure Route Server FAQ](https://learn.microsoft.com/azure/route-server/route-server-faq#can-i-associate-a-network-security-group-nsg-to-the-routeserversubnet)
- [Azure Dedicated HSM networking](https://learn.microsoft.com/azure/dedicated-hsm/networking#subnets)
- [NS-1: Establish network segmentation boundaries](https://learn.microsoft.com/security/benchmark/azure/baselines/virtual-network-security-baseline#ns-1-establish-network-segmentation-boundaries)
- [Azure VNET deployment reference](https://learn.microsoft.com/azure/templates/microsoft.network/virtualnetworks?pivots=deployment-language-bicep)
- [Azure NSG deployment reference](https://learn.microsoft.com/azure/templates/microsoft.network/networksecuritygroups)
35 changes: 35 additions & 0 deletions docs/setup/configuring-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -665,3 +665,38 @@ Example:
configuration:
AZURE_VNET_DNS_WITH_IDENTITY: true
```

### AZURE_VNET_SUBNET_EXCLUDED_FROM_NSG

:octicons-milestone-24: v1.33.0

> Applies to [Azure.VNET.UseNSGs](../en/rules/Azure.VNET.UseNSGs.md).

This configuration option excludes subnets from requiring a Network Security Group (NSG).
You can use this configuration option to exclude subnets that are specific to your environment.
To configure this option, specify a list of subnet names to exclude.

Syntax:

```yaml
configuration:
AZURE_VNET_SUBNET_EXCLUDED_FROM_NSG: array
```

Default:

```yaml
# YAML: The default AZURE_VNET_SUBNET_EXCLUDED_FROM_NSG configuration option
configuration:
AZURE_VNET_SUBNET_EXCLUDED_FROM_NSG: []
```

Example:

```yaml
# YAML: Set the AZURE_VNET_SUBNET_EXCLUDED_FROM_NSG configuration option with two user defined subnets.
configuration:
AZURE_VNET_SUBNET_EXCLUDED_FROM_NSG:
- subnet-1
- subnet-2
```
5 changes: 3 additions & 2 deletions src/PSRule.Rules.Azure/rules/Azure.VNET.Rule.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@
# Synopsis: Virtual network (VNET) subnets should have Network Security Groups (NSGs) assigned.
Rule 'Azure.VNET.UseNSGs' -Ref 'AZR-000263' -Type 'Microsoft.Network/virtualNetworks', 'Microsoft.Network/virtualNetworks/subnets' -Tag @{ release = 'GA'; ruleSet = '2020_06'; 'Azure.WAF/pillar' = 'Security'; 'Azure.MCSB.v1/control' = 'NS-1' } {
$excludedSubnets = @('GatewaySubnet', 'AzureFirewallSubnet', 'AzureFirewallManagementSubnet', 'RouteServerSubnet');
$customExcludedSubnets = $Configuration.GetStringValues('AZURE_VNET_SUBNET_EXCLUDED_FROM_NSG');
$subnet = @($TargetObject);
if ($PSRule.TargetType -eq 'Microsoft.Network/virtualNetworks') {
# Get subnets
$subnet = @($TargetObject.properties.subnets | Where-Object {
$_.Name -notin $excludedSubnets -and @($_.properties.delegations | Where-Object { $_.properties.serviceName -eq 'Microsoft.HardwareSecurityModules/dedicatedHSMs' }).Length -eq 0
$_.Name -notin $excludedSubnets -and $_.Name -notin $customExcludedSubnets -and @($_.properties.delegations | Where-Object { $_.properties.serviceName -eq 'Microsoft.HardwareSecurityModules/dedicatedHSMs' }).Length -eq 0
});
if ($subnet.Length -eq 0 -or !$Assert.HasFieldValue($TargetObject, 'properties.subnets').Result) {
return $Assert.Pass();
}
}
elseif ($PSRule.TargetType -eq 'Microsoft.Network/virtualNetworks/subnets' -and
($PSRule.TargetName -in $excludedSubnets -or @($TargetObject.properties.delegations | Where-Object { $_.properties.serviceName -eq 'Microsoft.HardwareSecurityModules/dedicatedHSMs' }).Length -gt 0)) {
($PSRule.TargetName -in $excludedSubnets -or $PSRule.TargetName -in $customExcludedSubnets -or @($TargetObject.properties.delegations | Where-Object { $_.properties.serviceName -eq 'Microsoft.HardwareSecurityModules/dedicatedHSMs' }).Length -gt 0)) {
return $Assert.Pass();
}
foreach ($sn in $subnet) {
Expand Down
16 changes: 13 additions & 3 deletions src/PSRule.Rules.Azure/rules/Config.Rule.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,21 @@ spec:
subscriptionId: [ 'subscriptionId' ]
resourceGroupName: [ 'resourceGroupName' ]
configuration:
# Enable expansion from .json files.
AZURE_PARAMETER_FILE_EXPANSION: false
AZURE_PARAMETER_FILE_METADATA_LINK: false

# Enable expansion from .bicep files.
AZURE_BICEP_FILE_EXPANSION: false

# Enable expansion from .bicepparam files.
AZURE_BICEP_PARAMS_FILE_EXPANSION: false

# Check for a minimum version of the Bicep CLI.
AZURE_BICEP_MINIMUM_VERSION: '0.4.451'
AZURE_BICEP_CHECK_TOOL: false

# Configure minimum AKS cluster version
# Configure minimum AKS cluster version.
AZURE_AKS_CLUSTER_MINIMUM_VERSION: '1.27.7'

AZURE_DEPLOYMENT_SENSITIVE_PROPERTY_NAMES:
Expand All @@ -42,12 +49,15 @@ spec:

AZURE_DEPLOYMENT_NONSENSITIVE_PARAMETER_NAMES: []

# Configure Container Apps external ingress
# Configure Container Apps external ingress.
AZURE_CONTAINERAPPS_RESTRICT_INGRESS: false

# Configure DNS is within the identity subscription
# Configure DNS is within the identity subscription.
AZURE_VNET_DNS_WITH_IDENTITY: false

# Exclude subnets by name from requiring and NSG.
AZURE_VNET_SUBNET_EXCLUDED_FROM_NSG: []

convention:
include:
- Azure.Context
Expand Down
35 changes: 19 additions & 16 deletions tests/PSRule.Rules.Azure.Tests/Azure.VNET.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ Describe 'Azure.VNET' -Tag 'Network', 'VNET' {
Module = 'PSRule.Rules.Azure'
WarningAction = 'Ignore'
ErrorAction = 'Stop'
Option = @{
'Configuration.AZURE_VNET_SUBNET_EXCLUDED_FROM_NSG' = @('subnet-ZZ')
}
}
$dataPath = Join-Path -Path $here -ChildPath 'Resources.VirtualNetwork.json';
$result = Invoke-PSRule @invokeParams -InputPath $dataPath -Outcome All;
Expand Down Expand Up @@ -71,8 +74,8 @@ Describe 'Azure.VNET' -Tag 'Network', 'VNET' {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 3;
$ruleResult.TargetName | Should -BeIn 'vnet-A', 'vnet-E', 'vnet-F';
$ruleResult.Length | Should -Be 4;
$ruleResult.TargetName | Should -BeIn 'vnet-A', 'vnet-E', 'vnet-F', 'vnet-G';
}

It 'Azure.VNET.SingleDNS' {
Expand All @@ -87,8 +90,8 @@ Describe 'Azure.VNET' -Tag 'Network', 'VNET' {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 5;
$ruleResult.TargetName | Should -Be 'vnet-A', 'vnet-C', 'vnet-D', 'vnet-E', 'vnet-F';
$ruleResult.Length | Should -Be 6;
$ruleResult.TargetName | Should -Be 'vnet-A', 'vnet-C', 'vnet-D', 'vnet-E', 'vnet-F', 'vnet-G';
}

It 'Azure.VNET.LocalDNS' {
Expand All @@ -97,8 +100,8 @@ Describe 'Azure.VNET' -Tag 'Network', 'VNET' {
# Fail
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 4;
$ruleResult.TargetName | Should -Be 'vnet-B', 'vnet-D', 'vnet-E', 'vnet-F';
$ruleResult.Length | Should -Be 5;
$ruleResult.TargetName | Should -Be 'vnet-B', 'vnet-D', 'vnet-E', 'vnet-F', 'vnet-G';

# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
Expand All @@ -125,8 +128,8 @@ Describe 'Azure.VNET' -Tag 'Network', 'VNET' {
# None
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' -and $_.TargetObject.ResourceType -eq 'Microsoft.Network/virtualNetworks' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 3;
$ruleResult.TargetName | Should -Be 'vnet-D', 'vnet-E', 'vnet-F';
$ruleResult.Length | Should -Be 4;
$ruleResult.TargetName | Should -Be 'vnet-D', 'vnet-E', 'vnet-F', 'vnet-G';
}

It 'Azure.VNET.Name' {
Expand All @@ -139,8 +142,8 @@ Describe 'Azure.VNET' -Tag 'Network', 'VNET' {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 6;
$ruleResult.TargetName | Should -BeIn 'vnet-A', 'vnet-B', 'vnet-C', 'vnet-D', 'vnet-E', 'vnet-F';
$ruleResult.Length | Should -Be 7;
$ruleResult.TargetName | Should -BeIn 'vnet-A', 'vnet-B', 'vnet-C', 'vnet-D', 'vnet-E', 'vnet-F', 'vnet-G';
}

It 'Azure.VNET.SubnetName' {
Expand All @@ -153,8 +156,8 @@ Describe 'Azure.VNET' -Tag 'Network', 'VNET' {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 6;
$ruleResult.TargetName | Should -BeIn 'vnet-A', 'vnet-B', 'vnet-C', 'vnet-D', 'vnet-E', 'vnet-F';
$ruleResult.Length | Should -Be 7;
$ruleResult.TargetName | Should -BeIn 'vnet-A', 'vnet-B', 'vnet-C', 'vnet-D', 'vnet-E', 'vnet-F', 'vnet-G';
}

It 'Azure.VNET.BastionSubnet' {
Expand All @@ -178,8 +181,8 @@ Describe 'Azure.VNET' -Tag 'Network', 'VNET' {
# None
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' -and $_.TargetObject.ResourceType -eq 'Microsoft.Network/virtualNetworks' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -BeIn 'vnet-F';
$ruleResult.Length | Should -Be 2;
$ruleResult.TargetName | Should -BeIn 'vnet-F', 'vnet-G';
}

It 'Azure.VNET.FirewallSubnet' {
Expand All @@ -203,8 +206,8 @@ Describe 'Azure.VNET' -Tag 'Network', 'VNET' {
# None
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' -and $_.TargetObject.ResourceType -eq 'Microsoft.Network/virtualNetworks' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -BeIn 'vnet-F';
$ruleResult.Length | Should -Be 2;
$ruleResult.TargetName | Should -BeIn 'vnet-F', 'vnet-G';
}
}

Expand Down
40 changes: 40 additions & 0 deletions tests/PSRule.Rules.Azure.Tests/Resources.VirtualNetwork.json
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,46 @@
"Tags": null,
"SubscriptionId": "00000000-0000-0000-0000-000000000000"
},
{
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/vnet-G",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/vnet-G",
"Location": "region",
"ResourceName": "vnet-G",
"Name": "vnet-G",
"Properties": {
"addressSpace": {
"addressPrefixes": [
"10.6.0.0/24"
]
},
"dhcpOptions": {
"dnsServers": [
"10.99.0.36",
"10.99.0.37"
]
},
"virtualNetworkPeerings": [],
"enableDdosProtection": false,
"enableVmProtection": false,
"subnets": [
{
"name": "subnet-ZZ",
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/vnet-G/subnets/subnet-ZZ",
"properties": {
"addressPrefix": "10.6.0.32/28",
"serviceEndpoints": [],
"delegations": []
},
"type": "Microsoft.Network/virtualNetworks/subnets"
}
]
},
"ResourceGroupName": "test-rg",
"Type": "Microsoft.Network/virtualNetworks",
"ResourceType": "Microsoft.Network/virtualNetworks",
"Tags": null,
"SubscriptionId": "00000000-0000-0000-0000-000000000000"
},
{
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/networkSecurityGroups/nsg-A",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/networkSecurityGroups/nsg-A",
Expand Down

0 comments on commit 4067ebf

Please sign in to comment.