After every security assessment, there's a scramble to fix things. Storage accounts without TLS 1.2, Key Vaults without purge protection, SQL servers with public access enabled.
The problem isn't that teams don't care about security - it's that the defaults are insecure, and there's no standard approach.
The solution? Terraform modules with security baked in.
The Module Approach
Instead of every team writing their own storage account resource with their own (inconsistent) security settings, create a shared module with sensible defaults:
# Storage Account Module Defaults
min_tls_version = "TLS1_2"
allow_nested_items_to_be_public = false
enable_https_traffic_only = true
blob_soft_delete_days = 30
container_soft_delete_days = 30
network_rules_default_action = "Deny"
Teams use the module, security is automatic. They can override if needed, but the path of least resistance is secure.
Priority Tiers for Security Settings
Not all security recommendations are equal. Here's how we prioritise:
Tier 1: Do Immediately (High Impact, Low Effort)
These should be non-negotiable defaults:
- TLS 1.2 enforcement (Storage, SQL, App Services)
- HTTPS only (App Services, Storage)
- Key Vault purge protection
- Disable anonymous blob access
Tier 2: Plan & Implement (Medium Effort, High Impact)
These need testing but should be standard:
- Network rules "Deny" default (test connectivity first!)
- SQL public network access disabled
- Soft delete enabled (storage, key vault)
Tier 3: Consider Carefully (Higher Effort)
Enable where genuinely needed:
- Blob versioning - only for critical production storage
- Infrastructure encryption - only if compliance requires
Tier 4: Skip Unless Required (High Cost, Low Benefit)
Don't enable by default:
- Customer-managed keys (Storage & SQL) - unless regulatory requirement
- Client certificates (App Services) - VNet-locked apps don't need this
Module Structure
terraform-modules/
├── storage-account/
│ ├── main.tf # Security defaults baked in
│ ├── variables.tf # Overrides available but optional
│ ├── outputs.tf
│ └── README.md # Documents security decisions
├── key-vault/
│ ├── main.tf # Purge protection, RBAC, soft delete
│ └── ...
├── sql-database/
│ ├── main.tf # TLS, auditing, private access
│ └── ...
└── app-service/
├── main.tf # HTTPS only, TLS 1.2, managed identity
└── ...
Why Modules Beat Policy
Azure Policy is great for guardrails, but it has limitations:
State conflicts - Policy remediation can conflict with Terraform state, causing drift and plan failures.
Reactive, not proactive - Policy catches violations after deployment. Modules prevent them before.
Limited flexibility - Policy is binary (allow/deny). Modules can have intelligent defaults with overrides.
Developer experience - Modules are familiar to Terraform users. Policy is a separate skill set.
Use Policy for audit and compliance reporting. Use Modules for enforcement.
Auditing Existing Resources
Before rolling out modules, audit what you have:
$subscriptions = Get-AzSubscription | Where-Object { $_.State -eq "Enabled" }
$results = @()
foreach ($sub in $subscriptions) {
Set-AzContext -SubscriptionId $sub.Id | Out-Null
$storageAccounts = Get-AzStorageAccount
foreach ($sa in $storageAccounts) {
$result = [PSCustomObject]@{
Subscription = $sub.Name
StorageAccount = $sa.StorageAccountName
MinTlsVersion = $sa.MinimumTlsVersion
AllowBlobPublicAccess = $sa.AllowBlobPublicAccess
HttpsOnly = $sa.EnableHttpsTrafficOnly
}
$results += $result
}
}
$results | Export-Csv "storage-audit.csv" -NoTypeInformation
This gives you a baseline. Fix the worst offenders, then prevent new ones with modules.
The Rollout
- Build the modules with security defaults
- Document the decisions - why each default is set
- Pilot with one team - get feedback, refine
- Mandate for new resources - all new infra uses modules
- Migrate existing resources - gradually, with testing
The goal isn't perfection overnight. It's making secure the easy choice.
Need help standardising your Terraform modules or auditing your Azure security posture? Get in touch - we help teams build secure infrastructure.