Back to Blog
Azure
4 min read

Azure Storage Account Security Best Practices

AzureStorageSecurityTerraformBest Practices

Storage accounts often hold sensitive data - backups, logs, application data. But default settings prioritise convenience over security. Here's how to harden them.

The Security Checklist

SettingRecommendedWhy
HTTPS onlyRequiredPrevent eavesdropping
Minimum TLS1.2Disable weak protocols
Public blob accessDisabledPrevent data exposure
Shared key accessDisabled (if possible)Use Azure AD instead
Soft delete (blobs)Enabled, 14+ daysRecovery from accidents
Soft delete (containers)Enabled, 14+ daysRecovery from accidents
VersioningEnabledTrack changes
Network rulesDeny by defaultLimit access

Secure Terraform Configuration

resource "azurerm_storage_account" "secure" {
  name                     = "stsecureexample"
  resource_group_name      = azurerm_resource_group.this.name
  location                 = azurerm_resource_group.this.location
  account_tier             = "Standard"
  account_replication_type = "GRS"

  # Security settings
  min_tls_version                 = "TLS1_2"
  https_traffic_only_enabled      = true
  allow_nested_items_to_be_public = false
  shared_access_key_enabled       = false  # Disable shared keys

  # Blob properties
  blob_properties {
    versioning_enabled = true

    delete_retention_policy {
      days = 30
    }

    container_delete_retention_policy {
      days = 30
    }
  }

  # Network rules - deny by default
  network_rules {
    default_action             = "Deny"
    bypass                     = ["AzureServices"]
    virtual_network_subnet_ids = [azurerm_subnet.app.id]
    ip_rules                   = ["203.0.113.0/24"]  # Office IPs
  }

  # Identity for Azure AD auth
  identity {
    type = "SystemAssigned"
  }
}

Shared Key vs Azure AD Authentication

Shared keys (storage account key) are like root passwords - full access to everything. Azure AD authentication provides:

  • Per-user access control
  • Audit logging of who accessed what
  • Conditional Access policies
  • No long-lived secrets to rotate

Disable shared keys when possible:

shared_access_key_enabled = false

Then grant access via RBAC:

resource "azurerm_role_assignment" "blob_reader" {
  scope                = azurerm_storage_account.secure.id
  role_definition_name = "Storage Blob Data Reader"
  principal_id         = data.azuread_group.readers.object_id
}

Network Isolation

Private Endpoints

For sensitive data, use private endpoints:

resource "azurerm_private_endpoint" "blob" {
  name                = "pe-storage-blob"
  location            = azurerm_resource_group.this.location
  resource_group_name = azurerm_resource_group.this.name
  subnet_id           = azurerm_subnet.private_endpoints.id

  private_service_connection {
    name                           = "psc-blob"
    private_connection_resource_id = azurerm_storage_account.secure.id
    subresource_names              = ["blob"]
    is_manual_connection           = false
  }
}

Service Endpoints (Alternative)

Simpler but less secure than private endpoints:

resource "azurerm_subnet" "app" {
  name                 = "app-subnet"
  resource_group_name  = azurerm_resource_group.this.name
  virtual_network_name = azurerm_virtual_network.this.name
  address_prefixes     = ["10.0.1.0/24"]

  service_endpoints = ["Microsoft.Storage"]
}

Trusted Microsoft Services

Some Azure services need access even with network rules:

network_rules {
  default_action = "Deny"
  bypass         = ["AzureServices"]  # Allows Azure Backup, Defender, etc.
}

Services covered by this bypass:

  • Azure Backup
  • Azure Defender for Storage
  • Azure Event Grid
  • Azure Log Analytics
  • Azure Monitor

Soft Delete and Recovery

Enable recovery options:

blob_properties {
  # Blob soft delete
  delete_retention_policy {
    days = 30
  }

  # Container soft delete
  container_delete_retention_policy {
    days = 30
  }

  # Versioning for accidental overwrites
  versioning_enabled = true
}

# Point-in-time restore (optional, for critical data)
blob_properties {
  restore_policy {
    days = 7
  }

  change_feed_enabled = true  # Required for restore
}

Encryption Options

Microsoft-Managed Keys (Default)

Enabled automatically, no configuration needed.

Customer-Managed Keys (CMK)

For regulatory requirements:

resource "azurerm_storage_account_customer_managed_key" "this" {
  storage_account_id = azurerm_storage_account.secure.id
  key_vault_id       = azurerm_key_vault.this.id
  key_name           = azurerm_key_vault_key.storage.name
}

Infrastructure Encryption (Double Encryption)

Additional encryption layer:

resource "azurerm_storage_account" "double_encrypted" {
  # ... other config ...
  infrastructure_encryption_enabled = true
}

Monitoring and Alerting

Enable diagnostic logging:

resource "azurerm_monitor_diagnostic_setting" "storage" {
  name                       = "storage-diagnostics"
  target_resource_id         = "${azurerm_storage_account.secure.id}/blobServices/default"
  log_analytics_workspace_id = azurerm_log_analytics_workspace.this.id

  enabled_log {
    category = "StorageRead"
  }

  enabled_log {
    category = "StorageWrite"
  }

  enabled_log {
    category = "StorageDelete"
  }

  metric {
    category = "Transaction"
    enabled  = true
  }
}

Need help securing your Azure storage? Get in touch - we help organisations implement security best practices.

Need help with your Azure environment?

Get in touch for a free consultation.

Get in Touch