Insight Tech APAC Blog Logo

Anti-Pattern 6: Being lazy with IaC and not doing things because it's too hard or gets in the way of things like Azure Policy

trentsteenholdt
December 6, 2024

10 minutes to read

Azure Bicep Anti-Patterns

Welcome to the sixth instalment of the series on Azure Bicep anti-patterns. Today’s post covers several critical areas often neglected in Infrastructure as Code (IaC) practices using Azure Bicep. These are Role-Based Access Control (RBAC), An over-reliance on Azure Policy, Skipping Documentation, and Overlooking Resource Cleanup. Neglecting these aspects can lead to security vulnerabilities, compliance issues, simple confusion, and even increased costs. Let’s explore why these are anti-patterns and how to address them effectively.

Why neglecting these areas are an anti-pattern

Tl;dr? They’re lazy cloud engineers

Neglecting RBAC

RBAC and access control are commonly left to manual configuration, often referred to as “click ops”; A phrase I use regurarly to call out lazy engineering! However, RBAC and access control are among the most important elements to include in your IaC and if anything, should be in the code first before resource deployment. Ignoring them in code leads to inconsistent security configurations, making your infrastructure vulnerable, prone to error and breadcrumb trail of “click ops” debt!

Over-Reliance on Azure Policy

Relying heavily on Azure Policy’s deployIfNotExists capability can create a false sense of security and completeness. This policy effect works inconsistently and has its well-known caveats, such as not updating compliance status when policy values change but the policy has already deployed its configuration. Treating Azure Policy as a primary configuration tool instead of a safety net can result in non-compliant resources, delayed enforcement or outright broken code.

Skipping Documentation

Some developers believe that “the code is the documentation,” which is a significant misconception. I personally hate it when people say that because I have yet to see IaC code become a New York Best Seller?. Skipping documentation often leads to confusion and makes maintenance more challenging.

Overlooking Resource Cleanup

Azure Bicep is not stateful like its neighbour in crime Terraform, and unless you’re running deployments in ‘complete’ mode, your code continually and incrementally adds resources. Without proper cleanup mechanisms, unused resources can accumulate over time, leading to increased costs and potential security risks.

How do we solve these anti-patterns?

Let’s dive into each area and learn how we can avoid these anti-patterns.

Neglecting RBAC in IaC

Automating RBAC ensures consistent, repeatable, and auditable security configurations across your infrastructure.

Best Practices for RBAC in IaC

  1. Use Built-in Azure Roles: Keep things simple for yourself, always prefer Azure built-in roles over custom roles whenever possible. Built-in roles are thoroughly tested and updated by Microsoft.

  2. Avoid Creating Custom Roles: Custom roles can introduce complexity and may not be necessary. If a built-in role doesn’t perfectly match your needs, consider:

    • Scoping Assignments at Different Levels: Assign roles at the most appropriate scope (subscription, resource group, resource) to limit permissions effectively.

        // Assign the same principal elevated rights to a direct resource
        var roleDefinitionId = 'b12aa53e-6015-4669-85d0-8515ebb3ae7f' // Private DNS Zone Contributor
        resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = {
        name: guid(resourceId('Microsoft.Network/privateDnsZones', 'myPrivateDnsZone'), roleDefinitionId, principalId)
        properties: {
          roleDefinitionId: roleDefinitionId
          principalId: principalId
          scope: resourceId('Microsoft.Network/privateDnsZones', 'myPrivateDnsZone')
        }
      }
      
    • Using Attribute-Based Access Control (ABAC): ABAC allows you to add conditions to role assignments for fine-grained access control.

      // Example of a role assignment with conditions (ABAC)
      var roleDefinitionId = '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9' // User Access Administrator
      resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = {
        name: guid(resourceId, roleDefinitionId, principalId)
        properties: {
          roleDefinitionId: roleDefinitionId
          principalId: principalId
          condition: '( ( !(ActionMatches{\'Microsoft.Authorization/roleAssignments/write\'}) ) OR ( @Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {befefa01-2a29-4197-83a8-272ff33ce314} ) ) AND ( ( !(ActionMatches{\'Microsoft.Authorization/roleAssignments/delete\'}) ) OR ( @Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {befefa01-2a29-4197-83a8-272ff33ce314} ) )' // This is a custom condition to allow the principalId the right to add/remove DNS Zone Contributor role assignments only
          conditionVersion: '2.0'
        }
      }
      
  3. Combine Roles Where Necessary: Assign multiple built-in roles to cover the required permissions instead of creating a custom role.

       // Assigning a least privelege role at a higher level like a resource group.
       resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = {
       name: guid(resourceGroup().id, 'Reader', principalId)
       properties: {
         roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') // Reader role ID
         principalId: principalId
         scope: resourceGroup().id
       }
     }
       // Assign the same principal elevated rights to a direct resource
       var roleDefinitionId = 'b12aa53e-6015-4669-85d0-8515ebb3ae7f' // Private DNS Zone Contributor
       resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = {
       name: guid(resourceId('Microsoft.Network/privateDnsZones', 'myPrivateDnsZone'), roleDefinitionId, principalId)
       properties: {
         roleDefinitionId: roleDefinitionId
         principalId: principalId
         scope: resourceId('Microsoft.Network/privateDnsZones', 'myPrivateDnsZone')
       }
     }
    

Over-Reliance on Azure Policy deployIfNotExists

While Azure Policy is powerful for enforcing compliance, it shouldn’t replace explicit resource definitions in your code.

Best Practices for Using Azure Policy

  1. Treat deployIfNotExists as a Fallback: Your code should explicitly define and configure all resources. Use Azure Policy as an additional safety net.

  2. Include Configurations in Your Code: Define resource configuration like diagnostics settings, resource locks, and other configurations directly in your Bicep templates.

    // Diagnostics settings for a storage account
    resource diagnosticSetting 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
      name: '${storageAccount.name}-diagnostics'
      scope: storageAccount
      properties: {
        storageAccountId: logStorageAccount.id
        logs: [
          {
            category: 'StorageRead'
            enabled: true
            retentionPolicy: {
              days: 0
              enabled: false
            }
          }
          // ... other log categories
        ]
        metrics: [
          {
            category: 'Transaction'
            enabled: true
            retentionPolicy: {
              days: 0
              enabled: false
            }
          }
          // ... other metric categories
        ]
      }
    }
    
  3. Shift Left in the SDLC: Incorporate compliance and governance policies into your development and testing phases.

Skipping Documentation

Best Practices for Documentation in IaC

  1. Add Comments to Your Bicep Code: Use comments to explain complex logic, parameters, and resource configurations.

    // Creates a storage account for storing application logs
    resource logStorageAccount 'Microsoft.Storage/storageAccounts@2021-02-01' = {
      name: 'appLogStorage'
      location: location
      kind: 'StorageV2'
      sku: {
        name: 'Standard_LRS'
      }
    }
    
  2. Utilise Descriptions for Parameters and Outputs: Bicep allows you to add descriptions to parameters and outputs, enhancing readability.

    @description('The location where all resources will be deployed')
    param location string = resourceGroup().location
    
  3. Generate Documentation Automatically: Consider tools like PSDocs to generate documentation from your Bicep code automatically. Check out my other blog post about PSDocs here.

  4. Use freely available AI Tools for Documentation: Tools like ChatGPT and GitHub Copilot can assist in generating markdown files or README documents from your code. Simply prompt it with, “Write a readme file for this code”.

  5. Maintain Up-to-Date README Files: Include README files in your repositories that explain the structure, deployment steps, and any prerequisites.

Overlooking Resource Cleanup

Best Practices for Resource Cleanup

  1. Implement Cleanup Scripts: Use scripts to identify and remove resources that are no longer defined in your Bicep code. The scripts could be as simple as.

    # Example PowerShell script to remove resources not in Bicep code
    $definedResources = Get-Content -Path '.\definedResources.txt'
    $currentResources = Get-AzResource -ResourceGroupName 'myResourceGroup'
    
    foreach ($resource in $currentResources) {
      if ($resource.Name -notin $definedResources) {
        Remove-AzResource -ResourceId $resource.ResourceId -Force
      }
    }
    
  2. Use Deployment Modes Appropriately:

    • Incremental Mode: Adds resources without removing existing ones. Suitable for adding new resources but can lead to leftover resources.
    • Complete Mode: Replaces the entire resource group with what’s defined in your Bicep code. ⚠️ Use with caution to avoid accidental deletions.
    # Deploy in complete mode
    az deployment group create --mode Complete --resource-group myResourceGroup --template-file main.bicep
    
  3. Implement Resource Tags: Tag resources with deployment or expiration information to track and manage them effectively.

    resource vm 'Microsoft.Compute/virtualMachines@2021-07-01' = {
      name: 'myVM'
      location: location
      tags: {
        Environment: 'Development'
        DeployedBy: 'Bicep via GitHub Actions'
        FirstDeploymentDate: '2024-12-31'
      }
      // VM properties
    }
    
  4. Manual Review and Cleanup: Regularly audit your resources to identify and remove any that are no longer needed.

  5. Use Infrastructure as Code Tools: Consider using tools that support state management, like Terraform, if you require more advanced resource tracking.

Conclusion

Neglecting essential practices like those mentioned in this post can have major negative impacts on your IaC projects. By incorporating these practices into your code, you enhance the security, compliance, maintainability, and cost-effectiveness of your deployments.

Remember, well-documented code is easier for everyone to understand and maintain. Additionally, proactively managing your resources ensures that your infrastructure remains clean and efficient. Security and governance should be integral parts of your IaC, not afterthoughts.

What’s Next?

In the next post of this series, we’ll explore how over-engineered networking setups in Bicep code can lead to confusion and errors in your environment.