Stirring in Services: Zero Trust for PaaS with Private Links

Applying Zero Trust to Azure PaaS — securing SQL, Storage, and Key Vault with Private Endpoints, DNS, and service isolation.

You’ve segmented your VNets, layered in NSGs, and rolled out Security Admin Rules. Your traffic path is tightly controlled. But what about your Azure SQL Database? Or that Storage Account full of secrets and blobs?

By default, many PaaS services in Azure are reachable over the public internet — even if you’ve got everything else segmented. That’s like leaving the sugar jar on the café counter; any passer‑by could dip into it.

Zero Trust means your services need segmentation too. And in Azure, that’s where Private Endpoints and Private Links come in.

  • Azure Private Endpoint: A network interface inside your VNet that maps to a PaaS resource (SQL, Storage, Key Vault, etc).
  • Azure Private Link: The overall service that enables PaaS resources to be accessible privately over Azure backbone instead of public endpoints.

Effectively, you’re giving your service a private address in the VNet, reachable only by authenticated traffic from trusted sources.

Why It Matters in Zero Trust

  • Removes public internet exposure.
  • Enforces least privilege by only allowing access from the segments/VNets you define.
  • Works seamlessly with DNS integration using Private DNS Zones.
  • Reduces the blast radius — compromised workloads can’t hit services unless explicitly permitted.

Real‑World Impact

Take a Storage Account that holds application data. By default:

  • Developers might reach it from home IPs.
  • The app VNet sends requests to the public endpoint.
  • Anyone with SAS tokens could hit it over the internet.

With Private Link in place:

  • Storage has a Private Endpoint in the Data VNet.
  • Only VNets integrated with that endpoint can reach it.
  • Access controlled further with NSGs and identity (Azure AD + RBAC).
  • DNS automatically resolves storageaccount.blob.core.windows.net → the private IP in your VNet.

Now the service sits safely inside your segmented network boundary.

Architecture Overview

flowchart TD subgraph WebVNet[Web VNet] WebVM[Web VMs] end subgraph AppVNet[App VNet] AppVM[App VMs] end subgraph DataVNet[Data VNet] SQLpe[SQL Private Endpoint] StPE[Storage Private Endpoint] KVPE[Key Vault Private Endpoint] end subgraph HubVNet[Hub VNet] AFW[Azure Firewall] Bastion[Azure Bastion] DNS[Private DNS Zone Resolver] end Internet((Internet)) Internet --> AFW AFW --> WebVM WebVM --> AppVM AppVM --> SQLpe AppVM --> StPE AppVM --> KVPE Bastion --> WebVM Bastion --> AppVM

PaaS is now pulled into the same segmentation model. All access is denied by default unless private paths are configured.

Implementation Examples

Azure Portal Steps

  1. Navigate to your PaaS resource (e.g. Storage Account).
  2. Under Networking, select Private Endpoint connections.
  3. Add a new Private Endpoint in the Data VNet (or wherever your consumers live).
  4. Associate with a Private DNS Zone (privatelink.blob.core.windows.net).
  5. Test resolution from App VMs → traffic stays private.
  6. Disable public network access on the resource.

Bicep Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
param location string = resourceGroup().location
param vnetrg string = 'myVnetRg'
param subnetName string = 'PrivateEndpointSubnet'
param storageAccountName string = 'mystorageacct${uniqueString(resourceGroup().id)}'

// Virtual Network
resource vnet 'Microsoft.Network/virtualNetworks@2023-05-01' existing = {
  scope: resourceGroup(vnetrg)
  name: vnetrg
}

// Storage Account
resource stg 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  sku: { name: 'Standard_LRS' }
  kind: 'StorageV2'
  properties: {
    minimumTlsVersion: 'TLS1_2'
    publicNetworkAccess: 'Disabled' // critical step
  }
}

// Private Endpoint
resource stgPe 'Microsoft.Network/privateEndpoints@2023-05-01' = {
  name: '${storageAccountName}-pe'
  location: location
  properties: {
    subnet: {
      id: '${vnet.id}/subnets/${subnetName}'
    }
    privateLinkServiceConnections: [
      {
        name: 'stgConnection'
        properties: {
          privateLinkServiceId: stg.id
          groupIds: [ 'blob' ]
        }
      }
    ]
  }
}

// Private DNS Zone
resource stgDns 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: 'privatelink.blob.core.windows.net'
  location: 'global'
}

// Virtual Network Link
resource vnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
  name: 'stg-dnslink'
  parent: stgDns
  properties: {
    virtualNetwork: { id: vnet.id }
    registrationEnabled: false
  }
}

Gotchas & Edge Cases

  • DNS configuration: Most errors are DNS resolution issues. Make sure Private DNS Zones are linked to every relevant VNet.
  • Overlapping endpoints: One service → multiple VNets → you’ll need multiple Private Endpoints or central service VNet design.
  • Costs: Each Private Endpoint + DNS zone has a cost. Plan accordingly at scale.
  • Disable public access: Forgetting this leaves your service accessible two ways — which defeats the point.

Best Practices

  • Deploy Private Endpoints in the data tier VNet for sensitive workloads.
  • Use Private DNS Zones consistently across spokes.
  • Always disable public access once Private Endpoints are validated.
  • Combine with identity (Azure AD auth for SQL, Storage, Key Vault) for layered Zero Trust.
  • Consider centralising Private Endpoints in a “Shared Services VNet” with controlled access if scaling across subscriptions.
🍺
Brewed Insight: Private Endpoints are like keeping your beans behind the counter — only staff should have access. Leaving PaaS exposed to the internet is like putting them out on the footpath. Zero Trust means service traffic should be private, deliberate, and identity‑verified.

Learn More