Skip to content

Commit

Permalink
Fixed false positive with load balancers that use a public IP #2814 (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
BernieWhite authored Apr 10, 2024
1 parent c9f3333 commit fcf0828
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 86 deletions.
6 changes: 6 additions & 0 deletions docs/CHANGELOG-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers

## Unreleased

What's changed since v1.35.2:

- Bug fixes:
- Fixed false positive with load balancers that use a public IP by @BernieWhite.
[#2814](https://github.com/Azure/PSRule.Rules.Azure/issues/2814)

## v1.35.2

What's changed since v1.35.1:
Expand Down
113 changes: 63 additions & 50 deletions docs/en/rules/Azure.LB.AvailabilityZone.md
Original file line number Diff line number Diff line change
@@ -1,100 +1,95 @@
---
reviewed: 2024-04-11
severity: Important
pillar: Reliability
category: RE:05 Regions and availability zones
resource: Load Balancer
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.LB.AvailabilityZone/
---

# Load balancers should be zone-redundant
# Internal load balancers should be zone-redundant

## SYNOPSIS

Load balancers deployed with Standard SKU should be zone-redundant for high availability.

## DESCRIPTION

Load balancers using availability zones improve reliability and ensure availability during failure scenarios affecting a data center within a region.
A single zone redundant frontend IP address will survive zone failure.
The frontend IP may be used to reach all (non-impacted) backend pool members no matter the zone.
One or more availability zones can fail and the data path survives as long as one zone in the region remains healthy.
A load balancer is an Azure service that distributes traffic among instances of a service in a backend pool (such as VMs).
Load balancers route traffic to healthy instances in the backend pool based on configured rules.
However if the load balancer itself becomes unavailable, traffic sent through the load balancer may become disrupted.

## RECOMMENDATION
In a region that supports availability zones, Standard Load Balancers can be deployed across multiple zones (zone-redundant).
A zone-redundant Load Balancer allows traffic to be served by a single frontend IP address that can survive zone failure.

Consider using zone-redundant load balancers deployed with Standard SKU.
Also consider the data path to the backend pool, and ensure that the backend pool is deployed with zone-redundancy in mind.

## NOTES
In a region that supports availability zones, Standard Load Balancers should be deployed with zone-redundancy.

This rule applies when analyzing resources deployed to Azure using *pre-flight* and *in-flight* data.
## RECOMMENDATION

This rule fails when `"zones"` is constrained to a single(zonal) zone or is not configured, and passes when set to `["1", "2", "3"]`.
Consider using load balancers deployed across at least two availability zones.

## EXAMPLES

### Configure with Azure template

To configure zone-redundancy for a load balancer.

- Set `sku.name` to `Standard`.
- Set `properties.frontendIPConfigurations[*].zones` to `["1", "2", "3"]`.
- Set the `sku.name` property to `Standard`.
- Set the `properties.frontendIPConfigurations[*].zones` property to at least two availability zones.
e.g. `1`, `2`, `3`.

For example:

```json
{
"apiVersion": "2020-07-01",
"name": "[parameters('name')]",
"type": "Microsoft.Network/loadBalancers",
"location": "[parameters('location')]",
"dependsOn": [],
"tags": {},
"properties": {
"frontendIPConfigurations": [
{
"name": "frontend-ip-config",
"properties": {
"privateIPAddress": null,
"privateIPAddressVersion": "IPv4",
"privateIPAllocationMethod": "Dynamic",
"subnet": {
"id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/lb-rg/providers/Microsoft.Network/virtualNetworks/lb-vnet/subnets/default"
}
},
"zones": [
"1",
"2",
"3"
]
}
],
"backendAddressPools": [],
"probes": [],
"loadBalancingRules": [],
"inboundNatRules": [],
"outboundRules": []
},
"sku": {
"name": "[parameters('sku')]",
"tier": "[parameters('tier')]"
}
"type": "Microsoft.Network/loadBalancers",
"apiVersion": "2023-09-01",
"name": "[parameters('lbName')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard",
"tier": "Regional"
},
"properties": {
"frontendIPConfigurations": [
{
"name": "frontendIPConfig",
"properties": {
"privateIPAllocationMethod": "Dynamic",
"subnet": {
"id": "[reference(resourceId('Microsoft.Network/virtualNetworks', parameters('name')), '2023-09-01').subnets[1].id]"
}
},
"zones": [
"1",
"2",
"3"
]
}
]
}
}
```

### Configure with Bicep

To configure zone-redundancy for a load balancer.

- Set `sku.name` to `Standard`.
- Set `properties.frontendIPConfigurations[*].zones` to `['1', '2', '3']`.
- Set the `sku.name` property to `Standard`.
- Set the `properties.frontendIPConfigurations[*].zones` property to at least two availability zones.
e.g. `1`, `2`, `3`.

For example:

```bicep
resource lb_001 'Microsoft.Network/loadBalancers@2021-02-01' = {
resource internal_lb 'Microsoft.Network/loadBalancers@2023-09-01' = {
name: lbName
location: location
sku: {
name: 'Standard'
tier: 'Regional'
}
properties: {
frontendIPConfigurations: [
Expand All @@ -117,8 +112,26 @@ resource lb_001 'Microsoft.Network/loadBalancers@2021-02-01' = {
}
```

<!-- external:avm avm/res/network/load-balancer frontendIPConfigurations[*].zones -->

## NOTES

This rule applies to internal load balancers deployed with Standard SKU.
Internal load balancers do not have a public IP address and are used to load balance traffic inside a virtual network.

The `zones` property is not supported with:

- Public load balancers, which are load balancers with a public IP address.
To address availability zones for public load balancers, use a Standard tier zone-redundant public IP address.
- Load balancers deployed with Basic SKU, which are not zone-redundant.

For regions that support availability zones, the `zones` property should be set to at least two zones.

## LINKS

- [RE:05 Regions and availability zones](https://learn.microsoft.com/azure/well-architected/reliability/regions-availability-zones)
- [What is Azure Load Balancer?](https://learn.microsoft.com/azure/load-balancer/load-balancer-overview)
- [Azure Load Balancer components](https://learn.microsoft.com/azure/load-balancer/components#frontend-ip-configurations)
- [Reliability in Load Balancer](https://learn.microsoft.com/azure/reliability/reliability-load-balancer)
- [Zone redundant load balancer](https://learn.microsoft.com/azure/reliability/reliability-load-balancer#zone-redundant-load-balancer)
- [Azure deployment reference](https://learn.microsoft.com/azure/templates/microsoft.network/loadbalancers)
49 changes: 45 additions & 4 deletions docs/examples-vnet.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ param name string
@description('The location resources will be deployed.')
param location string = resourceGroup().location

param asgName string = 'asg-001'
param nsgName string = 'nsg-001'
param lbName string = 'lb-001'

// An example virtual network (VNET) with NSG configured.
Expand Down Expand Up @@ -139,12 +137,13 @@ resource asg 'Microsoft.Network/applicationSecurityGroups@2023-09-01' = {
properties: {}
}

// An example internal load balancer.
resource lb_001 'Microsoft.Network/loadBalancers@2023-09-01' = {
// An example internal load balancer with availability zones configured.
resource internal_lb 'Microsoft.Network/loadBalancers@2023-09-01' = {
name: lbName
location: location
sku: {
name: 'Standard'
tier: 'Regional'
}
properties: {
frontendIPConfigurations: [
Expand All @@ -166,6 +165,48 @@ resource lb_001 'Microsoft.Network/loadBalancers@2023-09-01' = {
}
}

// An example zone redundant public IP address.
resource pip 'Microsoft.Network/publicIPAddresses@2023-09-01' = {
name: 'pip-001'
location: location
sku: {
name: 'Standard'
tier: 'Regional'
}
properties: {
publicIPAddressVersion: 'IPv4'
publicIPAllocationMethod: 'Static'
idleTimeoutInMinutes: 4
}
zones: [
'1'
'2'
'3'
]
}

// An example public load balancer.
resource public_lb 'Microsoft.Network/loadBalancers@2023-09-01' = {
name: lbName
location: location
sku: {
name: 'Standard'
tier: 'Regional'
}
properties: {
frontendIPConfigurations: [
{
name: 'frontendIPConfig'
properties: {
publicIPAddress: {
id: pip.id
}
}
}
]
}
}

// An example VNET with a GatewaySubnet, AzureBastionSubnet, and AzureBastionSubnet.
resource spoke 'Microsoft.Network/virtualNetworks@2023-09-01' = {
name: name
Expand Down
60 changes: 49 additions & 11 deletions docs/examples-vnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.25.53.49325",
"templateHash": "15957041710021771979"
"version": "0.26.54.24096",
"templateHash": "10466611904245566781"
}
},
"parameters": {
Expand All @@ -22,14 +22,6 @@
"description": "The location resources will be deployed."
}
},
"asgName": {
"type": "string",
"defaultValue": "asg-001"
},
"nsgName": {
"type": "string",
"defaultValue": "nsg-001"
},
"lbName": {
"type": "string",
"defaultValue": "lb-001"
Expand Down Expand Up @@ -171,7 +163,8 @@
"name": "[parameters('lbName')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard"
"name": "Standard",
"tier": "Regional"
},
"properties": {
"frontendIPConfigurations": [
Expand All @@ -195,6 +188,51 @@
"[resourceId('Microsoft.Network/virtualNetworks', parameters('name'))]"
]
},
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2023-09-01",
"name": "pip-001",
"location": "[parameters('location')]",
"sku": {
"name": "Standard",
"tier": "Regional"
},
"properties": {
"publicIPAddressVersion": "IPv4",
"publicIPAllocationMethod": "Static",
"idleTimeoutInMinutes": 4
},
"zones": [
"1",
"2",
"3"
]
},
{
"type": "Microsoft.Network/loadBalancers",
"apiVersion": "2023-09-01",
"name": "[parameters('lbName')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard",
"tier": "Regional"
},
"properties": {
"frontendIPConfigurations": [
{
"name": "frontendIPConfig",
"properties": {
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', 'pip-001')]"
}
}
}
]
},
"dependsOn": [
"[resourceId('Microsoft.Network/publicIPAddresses', 'pip-001')]"
]
},
{
"type": "Microsoft.Network/virtualNetworks",
"apiVersion": "2023-09-01",
Expand Down
18 changes: 12 additions & 6 deletions src/PSRule.Rules.Azure/rules/Azure.LB.Rule.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,18 @@ Rule 'Azure.LB.AvailabilityZone' -Ref 'AZR-000127' -Type 'Microsoft.Network/load
return $Assert.Pass();
}

foreach ($ipConfig in $TargetObject.Properties.frontendIPConfigurations) {
$Assert.SetOf($ipConfig, 'zones', @('1', '2', '3')).Reason(
$LocalizedData.LBAvailabilityZone,
$TargetObject.name,
$ipConfig.name
)
foreach ($ipconfig in $TargetObject.properties.frontendIPConfigurations) {
# The zones property only applies to internal load balancers.
if ($Assert.HasFieldValue($ipconfig, 'properties.publicIPAddress.id').Result) {
$Assert.Pass()
}
else {
$Assert.GreaterOrEqual($ipconfig, 'zones', 2).Reason(
$LocalizedData.LBAvailabilityZone,
$TargetObject.name,
$ipConfig.name
)
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions tests/PSRule.Rules.Azure.Tests/Azure.LB.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,16 @@ Describe 'Azure.LB' -Tag 'Network', 'LB' {
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -Be 'lb-A';
$ruleResult.TargetName | Should -Be 'lb-B';

$ruleResult[0].Reason | Should -Not -BeNullOrEmpty;
$ruleResult[0].Reason | Should -BeExactly "The load balancer (lb-A) frontend IP configuration (frontend-A) should be zone-redundant.";
$ruleResult[0].Reason | Should -BeExactly "The load balancer (lb-B) frontend IP configuration (frontend-A) should be zone-redundant.";

# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 3;
$ruleResult.TargetName | Should -Be 'kubernetes', 'lb-B', 'lb-C';
$ruleResult.TargetName | Should -Be 'kubernetes', 'lb-A', 'lb-C';

# None
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' -and $_.TargetType -eq 'Microsoft.Network/loadBalancers' });
Expand Down
Loading

0 comments on commit fcf0828

Please sign in to comment.