Private Endpoints — Storage & Key Vault
Overview
Each environment’s storage account (blob) and Key Vault are reachable only
over private endpoints — public network access is disabled on both. The
endpoints live in the spoke VNet’s PE subnet and register A records in the
relevant privatelink.* private DNS zones, so apps, VMs, and VPN clients
resolve them to private IPs.
Both are created by the workload-env root, once per environment (dev, qa,
regression, training, uat, prod). The blob and vaultcore zones are global,
region-less zones owned by the control-plane-dns-zones root.
Blob (Storage Account)
Defined in infrastructure/workload-env/main.tf
(azurerm_private_endpoint.storage_blob):
| Attribute | Value |
|---|---|
| PE name | pe-blob-saesharsapp{env}cus (pe-blob-${local.storage_name}) |
| Connection name | pe-blob-saesharsapp{env}cus-psc |
| Resource group | rg-eshars-storage-{tier}-cus (the env’s storage RG) |
| Subnet | snet-pe-eshars-{tier}-cus (PE subnet in the spoke VNet) |
| Target | module.storage.id |
subresource_names | ["blob"] |
| DNS zone group | default → privatelink.blob.core.windows.net |
The storage account is CMK-encrypted (per-env key in the purge-protected scaffold KV) and has public access disabled — the blob PE is the only ingress.
Key Vault
The Key Vault private endpoint is created inside the key-vault module
(infrastructure/modules/key-vault/main.tf, azurerm_private_endpoint.this),
wired up by the workload-env module call:
| Attribute | Value |
|---|---|
| PE name | pe-kv-eshars-{env}-cus (pe-${var.name}) |
| Connection name | pe-kv-eshars-{env}-cus-psc |
| Subnet | snet-pe-eshars-{tier}-cus (passed in as pe_subnet_id) |
| Target | the env Key Vault |
subresource_names | ["vault"] |
| DNS zone group | default → privatelink.vaultcore.azure.net |
The module sets public_network_access_enabled = false. The CI/CD service
principal is granted Key Vault Secrets Officer so tofu apply can still write
secrets through the private endpoint.
DNS Configuration
| Zone | Owned by | Resource group | Scope |
|---|---|---|---|
privatelink.blob.core.windows.net | control-plane-dns-zones | rg-eshars-global-{tier} | Global, region-less |
privatelink.vaultcore.azure.net | control-plane-dns-zones | rg-eshars-global-{tier} | Global, region-less |
workload-env reads both via data "azurerm_private_dns_zone" lookups in
var.global_rg (rg-eshars-global-{tier}). This differs from the SQL zone,
which lives with the spoke network — see
Private Endpoints — Database.
The global zones are linked to the hub VNet (and the spoke via peering), so
hub-linked VNets and VPN clients resolve the blob and vault FQDNs to the
private endpoint IPs. VPN resolution goes through the DNS forwarder
(vm-eshars-dns-{tier}-cus) → see
INFRA-0009.
Testing Connectivity
From a host inside the spoke or over the VPN:
# Each should resolve to a 10.x private IP, not a public onenslookup saesharsappdevcus.blob.core.windows.netnslookup kv-eshars-dev-cus.vault.azure.netIf a FQDN resolves to a public IP, the PE’s private_dns_zone_group link or the
VNet-to-zone link is missing.
Related
- Private Endpoints — Database — SQL PE (spoke-owned zone)
- Application Infrastructure —
workload-envroot, storage, and Key Vault - Workload Network — spoke VNet, PE subnet, zone links
- INFRA-0009: DNS Forwarder & P2S VPN Routing