Insight Tech APAC Blog Logo

Anti-Pattern 7: Overcomplicating Networking Configurations

trentsteenholdt
December 9, 2024

7 minutes to read

Azure Bicep Anti-Patterns

Welcome to the seventh instalment of the series on Azure Bicep anti-patterns. Today, is about the anti-patterns of overcomplicating networking configurations. Whether it’s overly verbose naming conventions, misuse of service tags, or just overly specific NSG rules, networking often becomes a mess and ends up in the “I’ll do this with click-ops” basket.

Let’s explore why this happens and how to simplify your networking configurations for clarity, consistency, and long-term success.

Naming Standards Gone Rogue

One of the most glaring issues with networking configurations is convoluted naming conventions for Network Security Group (NSG) rules. A common example is:

name: 'INBOUND-FROM-virtualNetwork-TO-virtualNetwork-PORT-custom-PROT-Tcp-ALLOW'

While this style tries to conform to Microsoft’s Cloud Adoption Framework (CAF), it often spirals into unmanageable territory. It’s CAF gone mad, obscuring the rule’s intent and making the code difficult to read or debug.

Example of Bad Naming

resource nsgRule 'Microsoft.Network/networkSecurityGroups/securityRules@2020-11-01' = {
  name: 'INBOUND-FROM-virtualNetwork-TO-virtualNetwork-PORT-custom-PROT-Tcp-ALLOW'
  properties: {
    protocol: 'Tcp'
    sourcePortRange: '*'
    destinationPortRanges: [
      '3389'
      '22'
    ]
    sourceAddressPrefix: '10.204.2.0/24'
    destinationAddressPrefix: 'VirtualNetwork'
    access: 'Allow'
    priority: 100
    direction: 'Inbound'
    description: 'Allow RDP and SSH from 10.204.2.0/24 to VNet'
  }
}

Why This Is a Problem

  • Lack of Clarity: A name like this provides little actionable context for a reader. In the Azure Portal, it’s often truncated.
  • Hard to Debug: It takes extra effort to decipher what this rule does even though the rule is really simple.
  • Cluttered Codebase: When multiplied across multiple rules and sometimes thousands of rules, this style becomes unmanageable to read.

Solution: Keep It Simple

Use clear and concise names that indicate the rule’s purpose without unnecessary verbosity.

name: 'Allow_JumpHost_SSH_RDP'

Not Using Service Tags Effectively

Azure offers networking service tags like VirtualNetwork, Internet, and even AustraliaEast.Storage to simplify NSG configurations. Despite this, many developers hardcode IP ranges or allow too much permission, which makes configurations less reusable across environments.

Example of a bad NSG rule properties

properties: {
  sourceAddressPrefix: '10.204.2.0/24'
  destinationAddressPrefix: '10.204.3.0/24'
}

Why is this a problem?

  • Hard to Reuse: The rule must be rewritten for each environment.
  • Prone to Errors: Hardcoded IP ranges can lead to misconfigurations.

Solution: Leverage Service Tags

Using service tags like VirtualNetwork improves flexibility and reduces maintenance.

properties: {
  sourceAddressPrefix: '10.204.2.0/24'
  destinationAddressPrefix: 'VirtualNetwork'
}

This rule can now be used not just on the one subnet NSG, but multiple NSGs to different subnets, making your code not have to duplicate the same rule.

var nsgRuleProperties = {
  protocol: 'Tcp'
  sourcePortRange: '*'
  destinationPortRanges: [
    '3389'
    '22'
  ]
  sourceAddressPrefix: '10.204.2.0/24'
  destinationAddressPrefix: 'VirtualNetwork'
  access: 'Allow'
  priority: 100
  direction: 'Inbound'
  description: 'Allow RDP and SSH from 10.204.2.0/24 to VNet'
}

resource nsg1forSubnetB 'Microsoft.Network/networkSecurityGroups@2020-11-01' = {
  name: 'nsg1'
  properties: {
    securityRules: [
      {
        name: 'Allow_JumpHost_SSH_RDP'
        properties: nsgRuleProperties
      }
    ]
  }
}

resource nsg2forSubnetC 'Microsoft.Network/networkSecurityGroups@2020-11-01' = {
  name: 'nsg2'
  properties: {
    securityRules: [
      {
        name: 'Allow_JumpHost_SSH_RDP'
        properties: nsgRuleProperties
      }
    ]
  }
}

You can expand this approach to have the same code work for multiple environments like dev, test and prd without needing to duplicate massive amounts of code.

param env = 'dev'

var sourceIP = {
  dev: '10.204.2.0/24'
  test: '10.204.3.0/24'
  prd: '10.204.2.0/24'
}

var nsgRuleProperties = {
  protocol: 'Tcp'
  sourcePortRange: '*'
  destinationPortRanges: [
    '3389'
    '22'
  ]
  sourceAddressPrefix: sourceIP[env]
  destinationAddressPrefix: 'VirtualNetwork'
  access: 'Allow'
  priority: 100
  direction: 'Inbound'
  description: 'Allow RDP and SSH from ${sourceIP[env]} to VNet'
}

resource nsg1forSubnetB 'Microsoft.Network/networkSecurityGroups@2020-11-01' = {
  name: 'nsg1'
  properties: {
    securityRules: [
      {
        name: 'Allow_JumpHost_SSH_RDP'
        properties: nsgRuleProperties
      }
    ]
  }
}

resource nsg2forSubnetC 'Microsoft.Network/networkSecurityGroups@2020-11-01' = {
  name: 'nsg2'
  properties: {
    securityRules: [
      {
        name: 'Allow_JumpHost_SSH_RDP'
        properties: nsgRuleProperties
      }
    ]
  }
}

Benefit

  • Consistency Across Environments: A single NSG rule can work across dev, test, and prod with minimal changes.
  • Simplified Management: No need to update IP ranges for redeployments.

Making NSG Rules Too Specific

Another anti-pattern is creating overly specific rules, such as those tied to single IP addresses. Azure IPs can change during resource redeployments, making such rules fragile.

Example of an Overly Specific Rule

properties: {
  sourceAddressPrefix: '10.204.2.42'
  destinationAddressPrefix: '10.204.3.15'
}

Why is this a problem?

  • Fragility: Changes in IP addresses during redeployments make the rule redundant and break the service.
  • Hard to Scale: Single IP rules are not scalable or maintainable.

Solution: Use more subnets

If segmentation is needed an ip level, create a dedicated subnet rather than relying on single IP-based rules.

properties: {
  sourceAddressPrefix: '10.204.2.0/25'
  destinationAddressPrefix: '10.204.2.128/25'
}

Mismanaging UDRs

Default User-Defined Routes (UDRs) are often added through shared configuration files using the import method in Azure Bicep or Azure Policy with deployIfNotExists. While this seems convenient, it leads to hidden dependencies and inconsistent behaviour.

Why is this a problem?

  • Lack of Visibility: UDRs applied via policies or defaults aren’t immediately visible in the code.
  • Debugging Nightmares: Hidden routes can cause unexpected networking issues.

Solution: Always define UDRs explicitly in code

Ensure all UDRs are part of your IaC templates for full traceability.

resource route 'Microsoft.Network/virtualNetworks/subnets/routeTables/routes@2020-11-01' = {
  name: 'DefaultRoute'
  properties: {
    addressPrefix: '0.0.0.0/0'
    nextHopType: 'Internet'
  }
}

Conclusion

Overcomplicating networking configurations is a common anti-pattern that can lead to unmanageable IaC code, fragile infrastructure, and increased operational overhead. By adopting simpler and clear naming conventions, using service tags effectively, avoiding overly specific rules, and managing UDRs explicitly in code, you can simplify your networking configurations for more maintainable and reliable deployments.

What’s Next?

In the next post of this series, we’ll explore why Azure What-If is dying (if not already dead), and why you should never rely on it for testing your Azure Bicep.