Back to Checks
azure

Storage account has private endpoint connections

storage_ensure_private_endpoints_in_storage_accounts
Service: storageSeverity: medium
OfficialBuilt-inVerified
Categories:internet-exposedtrust-boundaries
Resource Type:microsoft.storage/storageaccounts

Azure Storage accounts are evaluated for the presence of Private Endpoint connections. When configured, traffic flows over a VNet private IP via Private Link; when absent, access occurs through the storage account's public endpoint.

Prowler logoProwlerprowler-provider-azure

Risk

Relying on the public endpoint widens exposure:

  • Confidentiality: higher risk of key/SAS compromise and unauthorized reads
  • Integrity: abused creds enable writes/deletes
  • Availability: subject to DDoS and internet scanning It can also bypass egress controls, easing covert data exfiltration.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler azure --checks storage_ensure_private_endpoints_in_storage_accounts

Recommendation

Prefer Private Endpoints for storage access and minimize public exposure:

  • Limit or disable Public network access
  • Use private DNS so names resolve to private IPs
  • Enforce least privilege and defense in depth with segmentation and logging
  • Monitor access and rotate keys/SAS as part of routine hygiene.

Remediation

Terraform

# Create a Private Endpoint for a Storage Account to add a private endpoint connection (PASS)
variable "resource_group_name" { type = string }
variable "location" { type = string }
variable "subnet_id" { type = string }
variable "storage_account_id" { type = string }

resource "azurerm_private_endpoint" "<example_resource_name>" {
  name                = "<example_resource_name>"
  resource_group_name = var.resource_group_name
  location            = var.location
  subnet_id           = var.subnet_id

  private_service_connection {
    name                           = "conn"
    private_connection_resource_id = var.storage_account_id  # Critical: links to the storage account
    subresource_names              = ["blob"]                # Critical: targets Blob subresource to create the connection
  }
}

NativeIaC

// Create a Private Endpoint for a Storage Account to add a private endpoint connection (PASS)
param storageAccountId string  // ID of Microsoft.Storage/storageAccounts
param subnetId string          // ID of the subnet to host the Private Endpoint

resource pe 'Microsoft.Network/privateEndpoints@2023-05-01' = {
  name: '<example_resource_name>'
  location: resourceGroup().location
  properties: {
    subnet: { id: subnetId }
    privateLinkServiceConnections: [
      {
        name: 'conn'
        properties: {
          privateLinkServiceId: storageAccountId  // Critical: links the Private Endpoint to the storage account
          groupIds: ['blob']                      // Critical: targets Blob subresource, creating the private endpoint connection
        }
      }
    ]
  }
}

Other

1In Azure Portal, go to Storage accounts > select your account
2Under Security + networking, choose Networking > Private endpoint connections
3Click + Private endpoint > Create
4Resource type: Microsoft.Storage/storageAccounts; Resource: your account; Target subresource: blob
5Select the Virtual network and Subnet, then Review + create > Create

Check Code

from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.storage.storage_client import storage_client


class storage_ensure_private_endpoints_in_storage_accounts(Check):
    def execute(self) -> Check_Report_Azure:
        findings = []
        for subscription, storage_accounts in storage_client.storage_accounts.items():
            subscription_name = storage_client.subscriptions.get(
                subscription, subscription
            )
            for storage_account in storage_accounts:
                report = Check_Report_Azure(
                    metadata=self.metadata(), resource=storage_account
                )
                report.subscription = subscription
                if storage_account.private_endpoint_connections:
                    report.status = "PASS"
                    report.status_extended = f"Storage account {storage_account.name} from subscription {subscription_name} ({subscription}) has private endpoint connections."
                else:
                    report.status = "FAIL"
                    report.status_extended = f"Storage account {storage_account.name} from subscription {subscription_name} ({subscription}) does not have private endpoint connections."
                findings.append(report)

        return findings