Most Azure incidents don't start with a zero-day. They start with a storage account someone flipped to public "just to test something," a network security group opened to 0.0.0.0/0 on RDP, or a service principal handed the Owner role on an entire subscription because scoping felt like too much friction on a Friday afternoon. Azure's default posture is permissive by design — Microsoft optimizes for developers getting started quickly, and the burden of tightening that posture falls on you. This guide walks through the controls that actually move the needle, organized the way a platform team should roll them out: identity first, then guardrails, then network, secrets, and detection.
Treat Identity as the Real Perimeter
In Azure, the network is no longer the boundary — Microsoft Entra ID (formerly Azure Active Directory) is. Every meaningful attack path eventually runs through a token, so identity hygiene is the highest-leverage work you can do.
- Enforce phishing-resistant MFA through Conditional Access, not per-user MFA toggles. Require multifactor for all administrative roles and block legacy authentication protocols that can't honor MFA.
- Use managed identities instead of secrets. A system-assigned managed identity lets a VM, Function, or App Service authenticate to Key Vault or Storage with no credential to leak, rotate, or commit to Git.
- Apply Privileged Identity Management (PIM) so roles like Global Administrator and Subscription Owner are eligible rather than permanently assigned, activated just-in-time with approval and an audit trail.
Scope role assignments to the narrowest resource that works. A workload that reads one Key Vault should get Key Vault Secrets User on that vault, not Contributor on the resource group.
Enforce Guardrails with Azure Policy and Management Groups
Manual review doesn't scale. Azure Policy lets you codify rules that deny non-compliant resources at deployment time, and management groups let you inherit those rules across every subscription in the tenant.
{
"if": {
"allOf": [
{ "field": "type", "equals": "Microsoft.Storage/storageAccounts" },
{ "field": "Microsoft.Storage/storageAccounts/allowBlobPublicAccess", "equals": true }
]
},
"then": { "effect": "deny" }
}
Assign policy initiatives like the Microsoft cloud security benchmark at the top-level management group so new subscriptions are governed from the moment they're created. deny effects prevent drift; audit and deployIfNotExists effects catch and remediate what already exists.
Isolate the Network by Default
Public exposure is the misconfiguration that turns a minor bug into a breach. Build private-by-default and open exceptions deliberately.
resource "azurerm_storage_account" "data" {
name = "sgdatastore2026"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
account_tier = "Standard"
account_replication_type = "GRS"
allow_nested_items_to_be_public = false
public_network_access_enabled = false
min_tls_version = "TLS1_2"
}
resource "azurerm_network_security_rule" "no_public_rdp" {
name = "deny-inbound-rdp"
priority = 100
direction = "Inbound"
access = "Deny"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "3389"
source_address_prefix = "Internet"
destination_address_prefix = "*"
network_security_group_name = azurerm_network_security_group.app.name
resource_group_name = azurerm_resource_group.app.name
}
Use Private Endpoints to keep PaaS traffic (Storage, SQL, Key Vault) on the Microsoft backbone rather than the public internet, and reach management planes through Azure Bastion instead of exposing RDP or SSH. Building these controls into your Terraform means they're reviewed in the pull request, which is exactly what infrastructure-as-code scanning validates before merge.
Centralize Secrets and Encryption in Key Vault
Never ship a connection string in an app setting or a .env file baked into a container. Store secrets, keys, and certificates in Azure Key Vault, grant access through Azure RBAC and managed identities, and enable soft-delete and purge protection so a deleted vault can't be permanently destroyed by an attacker or a bad script.
# Audit storage accounts that still allow public blob access
az storage account list \
--query "[?allowBlobPublicAccess].{name:name, rg:resourceGroup}" -o table
# Read a secret using the workload's managed identity (no secret in code)
az keyvault secret show --vault-name sg-app-kv --name db-password --query value -o tsv
Enable encryption at rest with customer-managed keys where compliance requires control over the key lifecycle, and enforce TLS 1.2 or higher on every public endpoint.
Turn On Detection and Keep the Logs
Prevention fails eventually, so you need to see it when it does. Enable Microsoft Defender for Cloud for CSPM and workload protection, and route Activity Logs plus resource diagnostic settings to a Log Analytics workspace feeding Microsoft Sentinel. Without diagnostic settings configured, many resources emit no logs at all — the default is silence.
Azure Hardening Checklist
| Control | What to verify |
|---|---|
| Conditional Access MFA | Enforced for all admin roles; legacy auth blocked |
| Managed identities | Used in place of stored service-principal secrets |
| PIM | Owner/Global Admin roles eligible, not permanent |
| Azure Policy | deny initiatives assigned at management-group root |
| Storage | allowBlobPublicAccess false; public network access disabled |
| Network | No 0.0.0.0/0 inbound on 22/3389; Private Endpoints for PaaS |
| Key Vault | Soft-delete + purge protection on; RBAC access model |
| Defender for Cloud | Enabled; Activity + diagnostic logs to Log Analytics/Sentinel |
How Safeguard Helps
Azure misconfigurations almost always exist in Bicep or Terraform before they exist in the portal, which is the cheapest place to catch them. Safeguard's infrastructure-as-code scanning inspects your Azure templates for public storage accounts, wildcard NSG rules, disabled TLS enforcement, and over-scoped role assignments in the pull request that introduces them — tied to the exact line so a developer fixes it before merge. The application code deploying onto that infrastructure is covered by software composition analysis, which builds an SBOM and flags vulnerable dependencies your functions and containers actually call. Griffin, Safeguard's AI triage engine, then ranks findings by real blast radius so a public endpoint on a production data store outranks a cosmetic warning. Because it all runs through the pipeline-native Safeguard CLI, you can gate merges so a non-compliant resource never ships. If you're weighing this build-time model against an agentless posture platform that evaluates Azure after deployment, the Safeguard vs Wiz comparison breaks down where each vantage point fits.
Start hardening your Azure estate at the pull request — create a free account or read the documentation.