Project 7: Private Endpoint Implementation β
Overview β
Secure Azure PaaS services using Private Endpoints and Private Link. This project covers connecting to Storage, SQL, and Key Vault privately, eliminating public internet exposure.
Difficulty: Intermediate
Duration: 3-4 hours
Cost: ~$30-50/month (Private endpoints, DNS zones)
Architecture Diagram β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β INTERNET β
β β β
β (Public Access Blocked) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VNet: vnet-privatelink (10.0.0.0/16) β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Subnet: snet-workloads (10.0.1.0/24) β β
β β β β
β β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββββββββββ β β
β β β vm-client-01 β β vm-client-02 β β vm-client-03 β β β
β β β Windows Server β β Ubuntu Linux β β App Server β β β
β β β β β β β β β β
β β β Access PaaS β β Access PaaS β β Access PaaS β β β
β β β via Private IPs β β via Private IPs β β via Private IPs β β β
β β ββββββββββ¬ββββββββββ ββββββββββ¬ββββββββββ ββββββββββββββ¬ββββββββββββββ β β
β β β β β β β
β β βββββββββββββββββββββββΌβββββββββββββββββββββββββββ β β
β β β β β
β βββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββ β
β β Subnet: snet-privateendpoints (10.0.2.0/24) β β
β β β β β
β β βββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββ β β
β β β β β β β
β β βΌ βΌ βΌ β β
β β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββββββββββ β β
β β β pe-storage β β pe-sql β β pe-keyvault β β β
β β β Private β β Private β β Private β β β
β β β Endpoint β β Endpoint β β Endpoint β β β
β β β β β β β β β β
β β β IP: 10.0.2.4 β β IP: 10.0.2.5 β β IP: 10.0.2.6 β β β
β β ββββββββββ¬ββββββββββ ββββββββββ¬ββββββββββ ββββββββββββββ¬ββββββββββββββ β β
β β β β β β β
β βββββββββββββΌββββββββββββββββββββββΌβββββββββββββββββββββββββββΌβββββββββββββββ β
β β β β β
β βββββββββββββΌββββββββββββββββββββββΌβββββββββββββββββββββββββββΌβββββββββββββββ β
β β β Private DNS Zonesβ β β β
β β β β β β β
β β ββββββββββ΄ββββββββββ ββββββββββ΄ββββββββββ βββββββββββββββ΄ββββββββββββ β β
β β βprivatelink. β βprivatelink. β βprivatelink. β β β
β β βblob.core. β βdatabase.windows β βvaultcore.azure.net β β β
β β βwindows.net β β.net β β β β β
β β β β β β β β β β
β β βA: st*.blob... β βA: sql-*.database β βA: kv-*.vault... β β β
β β β β 10.0.2.4 β β β 10.0.2.5 β β β 10.0.2.6 β β β
β β ββββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β Private Link Connection
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AZURE PAAS SERVICES β
β (Public Endpoints Disabled) β
β β
β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββββββββββββββ β
β β Azure Storage β β Azure SQL β β Azure Key Vault β β
β β Account β β Database β β β β
β β β β β β β β
β β ββββββββββββββ β β ββββββββββββββ β β ββββββββββββββββββββββββββ β β
β β β Blob β β β β SQL Server β β β β Secrets β β β
β β β Container β β β β Databases β β β β Keys β β β
β β ββββββββββββββ β β ββββββββββββββ β β β Certificates β β β
β β β β β β ββββββββββββββββββββββββββ β β
β β Public: β β β Public: β β β Public: β β β
β β Private: β
β β Private: β
β β Private: β
β β
β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
DNS Resolution Flow:
ββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
β Client VM βββββΆβ Azure DNS βββββΆβ Private DNS β
β Requests β β (168.63.129.16) β β Zone Lookup β
β blob.core.. β β β β Returns 10.0.2.4β
ββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββWhat You'll Learn β
- Create Private Endpoints for PaaS services
- Configure Private DNS Zones
- Link Private DNS to VNet
- Disable public access on PaaS services
- Test private connectivity
- Implement service endpoints vs private endpoints
Prerequisites β
- Azure subscription
- Azure CLI installed
- Basic networking knowledge
Phase 1: Create Network Infrastructure β
Step 1.1: Create Resource Group and VNet β
bash
# Set variables
LOCATION="eastus"
RG_NAME="rg-privatelink-lab-eastus"
# Create resource group
az group create \
--name $RG_NAME \
--location $LOCATION \
--tags Project=PrivateEndpoint Environment=Lab
# Create VNet
az network vnet create \
--resource-group $RG_NAME \
--name vnet-privatelink \
--address-prefix 10.0.0.0/16 \
--location $LOCATION
echo "VNet created"Step 1.2: Create Subnets β
bash
# Create workload subnet
az network vnet subnet create \
--resource-group $RG_NAME \
--vnet-name vnet-privatelink \
--name snet-workloads \
--address-prefix 10.0.1.0/24
# Create private endpoint subnet
az network vnet subnet create \
--resource-group $RG_NAME \
--vnet-name vnet-privatelink \
--name snet-privateendpoints \
--address-prefix 10.0.2.0/24
# Create Azure Bastion subnet
az network vnet subnet create \
--resource-group $RG_NAME \
--vnet-name vnet-privatelink \
--name AzureBastionSubnet \
--address-prefix 10.0.3.0/27
# Disable private endpoint network policies on PE subnet
az network vnet subnet update \
--resource-group $RG_NAME \
--vnet-name vnet-privatelink \
--name snet-privateendpoints \
--disable-private-endpoint-network-policies true
echo "Subnets created"Phase 2: Create Azure PaaS Services β
Step 2.1: Create Storage Account β
bash
# Generate unique name
STORAGE_NAME="stprivate$(date +%s | tail -c 8)"
# Create storage account
az storage account create \
--resource-group $RG_NAME \
--name $STORAGE_NAME \
--location $LOCATION \
--sku Standard_LRS \
--kind StorageV2 \
--min-tls-version TLS1_2
# Create a blob container
az storage container create \
--name "documents" \
--account-name $STORAGE_NAME
# Upload test file
echo "Hello from private endpoint!" > test.txt
az storage blob upload \
--account-name $STORAGE_NAME \
--container-name "documents" \
--name "test.txt" \
--file "test.txt"
echo "Storage account created: $STORAGE_NAME"Step 2.2: Create Azure SQL Database β
bash
# Generate unique name
SQL_SERVER_NAME="sql-private-$(date +%s | tail -c 8)"
SQL_ADMIN="sqladmin"
SQL_PASSWORD="P@ssw0rd123!Complex"
# Create SQL Server
az sql server create \
--resource-group $RG_NAME \
--name $SQL_SERVER_NAME \
--location $LOCATION \
--admin-user $SQL_ADMIN \
--admin-password $SQL_PASSWORD
# Create SQL Database
az sql db create \
--resource-group $RG_NAME \
--server $SQL_SERVER_NAME \
--name "testdb" \
--edition Basic \
--capacity 5
echo "SQL Server created: $SQL_SERVER_NAME"Step 2.3: Create Key Vault β
bash
# Generate unique name
KV_NAME="kv-private-$(date +%s | tail -c 8)"
# Create Key Vault
az keyvault create \
--resource-group $RG_NAME \
--name $KV_NAME \
--location $LOCATION \
--sku standard \
--enable-rbac-authorization true
# Add a secret
az keyvault secret set \
--vault-name $KV_NAME \
--name "DatabasePassword" \
--value "$SQL_PASSWORD"
echo "Key Vault created: $KV_NAME"Phase 3: Create Private Endpoints β
Step 3.1: Get Resource IDs β
bash
# Get Storage Account ID
STORAGE_ID=$(az storage account show \
--resource-group $RG_NAME \
--name $STORAGE_NAME \
--query id -o tsv)
# Get SQL Server ID
SQL_ID=$(az sql server show \
--resource-group $RG_NAME \
--name $SQL_SERVER_NAME \
--query id -o tsv)
# Get Key Vault ID
KV_ID=$(az keyvault show \
--resource-group $RG_NAME \
--name $KV_NAME \
--query id -o tsv)
echo "Resource IDs retrieved"Step 3.2: Create Private Endpoint for Storage β
bash
# Create private endpoint for blob storage
az network private-endpoint create \
--resource-group $RG_NAME \
--name pe-storage-blob \
--vnet-name vnet-privatelink \
--subnet snet-privateendpoints \
--private-connection-resource-id $STORAGE_ID \
--group-id blob \
--connection-name "StorageBlobConnection" \
--location $LOCATION
# Get private endpoint IP
STORAGE_PE_IP=$(az network private-endpoint show \
--resource-group $RG_NAME \
--name pe-storage-blob \
--query "customDnsConfigs[0].ipAddresses[0]" -o tsv)
echo "Storage Private Endpoint created with IP: $STORAGE_PE_IP"Step 3.3: Create Private Endpoint for SQL β
bash
# Create private endpoint for SQL
az network private-endpoint create \
--resource-group $RG_NAME \
--name pe-sql \
--vnet-name vnet-privatelink \
--subnet snet-privateendpoints \
--private-connection-resource-id $SQL_ID \
--group-id sqlServer \
--connection-name "SQLServerConnection" \
--location $LOCATION
# Get private endpoint IP
SQL_PE_IP=$(az network private-endpoint show \
--resource-group $RG_NAME \
--name pe-sql \
--query "customDnsConfigs[0].ipAddresses[0]" -o tsv)
echo "SQL Private Endpoint created with IP: $SQL_PE_IP"Step 3.4: Create Private Endpoint for Key Vault β
bash
# Create private endpoint for Key Vault
az network private-endpoint create \
--resource-group $RG_NAME \
--name pe-keyvault \
--vnet-name vnet-privatelink \
--subnet snet-privateendpoints \
--private-connection-resource-id $KV_ID \
--group-id vault \
--connection-name "KeyVaultConnection" \
--location $LOCATION
# Get private endpoint IP
KV_PE_IP=$(az network private-endpoint show \
--resource-group $RG_NAME \
--name pe-keyvault \
--query "customDnsConfigs[0].ipAddresses[0]" -o tsv)
echo "Key Vault Private Endpoint created with IP: $KV_PE_IP"Phase 4: Create Private DNS Zones β
Step 4.1: Create DNS Zones β
bash
# Create Private DNS Zone for Blob Storage
az network private-dns zone create \
--resource-group $RG_NAME \
--name "privatelink.blob.core.windows.net"
# Create Private DNS Zone for SQL
az network private-dns zone create \
--resource-group $RG_NAME \
--name "privatelink.database.windows.net"
# Create Private DNS Zone for Key Vault
az network private-dns zone create \
--resource-group $RG_NAME \
--name "privatelink.vaultcore.azure.net"
echo "Private DNS zones created"Step 4.2: Link DNS Zones to VNet β
bash
# Link Blob DNS Zone to VNet
az network private-dns link vnet create \
--resource-group $RG_NAME \
--zone-name "privatelink.blob.core.windows.net" \
--name "blob-vnet-link" \
--virtual-network vnet-privatelink \
--registration-enabled false
# Link SQL DNS Zone to VNet
az network private-dns link vnet create \
--resource-group $RG_NAME \
--zone-name "privatelink.database.windows.net" \
--name "sql-vnet-link" \
--virtual-network vnet-privatelink \
--registration-enabled false
# Link Key Vault DNS Zone to VNet
az network private-dns link vnet create \
--resource-group $RG_NAME \
--zone-name "privatelink.vaultcore.azure.net" \
--name "kv-vnet-link" \
--virtual-network vnet-privatelink \
--registration-enabled false
echo "DNS zones linked to VNet"Step 4.3: Create DNS Records β
bash
# Create A record for Storage
az network private-dns record-set a create \
--resource-group $RG_NAME \
--zone-name "privatelink.blob.core.windows.net" \
--name $STORAGE_NAME
az network private-dns record-set a add-record \
--resource-group $RG_NAME \
--zone-name "privatelink.blob.core.windows.net" \
--record-set-name $STORAGE_NAME \
--ipv4-address $STORAGE_PE_IP
# Create A record for SQL
az network private-dns record-set a create \
--resource-group $RG_NAME \
--zone-name "privatelink.database.windows.net" \
--name $SQL_SERVER_NAME
az network private-dns record-set a add-record \
--resource-group $RG_NAME \
--zone-name "privatelink.database.windows.net" \
--record-set-name $SQL_SERVER_NAME \
--ipv4-address $SQL_PE_IP
# Create A record for Key Vault
az network private-dns record-set a create \
--resource-group $RG_NAME \
--zone-name "privatelink.vaultcore.azure.net" \
--name $KV_NAME
az network private-dns record-set a add-record \
--resource-group $RG_NAME \
--zone-name "privatelink.vaultcore.azure.net" \
--record-set-name $KV_NAME \
--ipv4-address $KV_PE_IP
echo "DNS records created"Phase 5: Disable Public Access β
Step 5.1: Disable Public Access on Storage β
bash
# Disable public blob access
az storage account update \
--resource-group $RG_NAME \
--name $STORAGE_NAME \
--allow-blob-public-access false
# Configure network rules - deny all public access
az storage account update \
--resource-group $RG_NAME \
--name $STORAGE_NAME \
--default-action Deny
# Allow Azure services (for management)
az storage account update \
--resource-group $RG_NAME \
--name $STORAGE_NAME \
--bypass AzureServices
echo "Storage public access disabled"Step 5.2: Disable Public Access on SQL β
bash
# Deny public network access
az sql server update \
--resource-group $RG_NAME \
--name $SQL_SERVER_NAME \
--enable-public-network false
echo "SQL public access disabled"Step 5.3: Disable Public Access on Key Vault β
bash
# Disable public network access
az keyvault update \
--resource-group $RG_NAME \
--name $KV_NAME \
--public-network-access Disabled
echo "Key Vault public access disabled"Phase 6: Deploy Test VM and Azure Bastion β
Step 6.1: Create Azure Bastion β
bash
# Create public IP for Bastion
az network public-ip create \
--resource-group $RG_NAME \
--name pip-bastion \
--sku Standard \
--allocation-method Static
# Create Azure Bastion
az network bastion create \
--resource-group $RG_NAME \
--name bastion-privatelink \
--public-ip-address pip-bastion \
--vnet-name vnet-privatelink \
--sku Basic \
--location $LOCATION
echo "Azure Bastion deployed"Step 6.2: Create Test VM β
bash
ADMIN_USER="azureadmin"
ADMIN_PASSWORD="P@ssw0rd123!Complex"
# Create Windows VM for testing
az vm create \
--resource-group $RG_NAME \
--name vm-client-01 \
--vnet-name vnet-privatelink \
--subnet snet-workloads \
--image Win2022Datacenter \
--size Standard_D2s_v3 \
--admin-username $ADMIN_USER \
--admin-password $ADMIN_PASSWORD \
--public-ip-address "" \
--nsg ""
echo "Test VM created"Phase 7: Testing Private Connectivity β
Step 7.1: Connect via Bastion β
- Navigate to Azure Portal β vm-client-01
- Click "Connect" β "Bastion"
- Enter credentials and connect
Step 7.2: Test DNS Resolution β
From the VM, open PowerShell:
powershell
# Test DNS resolution for Storage
nslookup $env:STORAGE_NAME.blob.core.windows.net
# Should return private IP (10.0.2.x)
# Test DNS resolution for SQL
nslookup $env:SQL_SERVER_NAME.database.windows.net
# Should return private IP (10.0.2.x)
# Test DNS resolution for Key Vault
nslookup $env:KV_NAME.vault.azure.net
# Should return private IP (10.0.2.x)Step 7.3: Test Storage Access β
powershell
# Install Azure PowerShell module if needed
Install-Module -Name Az -Force -AllowClobber
# Connect to Azure
Connect-AzAccount
# Test storage access
$ctx = New-AzStorageContext -StorageAccountName "$env:STORAGE_NAME" -UseConnectedAccount
Get-AzStorageBlob -Container "documents" -Context $ctx
# Download test file
Get-AzStorageBlobContent -Container "documents" -Blob "test.txt" -Destination "C:\test.txt" -Context $ctx
Get-Content "C:\test.txt"Step 7.4: Test SQL Access β
powershell
# Install SQL Server module
Install-Module -Name SqlServer -Force
# Test SQL connection
$connString = "Server=$env:SQL_SERVER_NAME.database.windows.net;Database=testdb;User Id=sqladmin;Password=P@ssw0rd123!Complex;"
$conn = New-Object System.Data.SqlClient.SqlConnection($connString)
try {
$conn.Open()
Write-Host "SQL connection successful via private endpoint!" -ForegroundColor Green
# Run a test query
$cmd = $conn.CreateCommand()
$cmd.CommandText = "SELECT @@VERSION"
$result = $cmd.ExecuteScalar()
Write-Host $result
}
catch {
Write-Host "Connection failed: $_" -ForegroundColor Red
}
finally {
$conn.Close()
}Step 7.5: Test Key Vault Access β
powershell
# Test Key Vault access
$secret = Get-AzKeyVaultSecret -VaultName "$env:KV_NAME" -Name "DatabasePassword" -AsPlainText
Write-Host "Secret retrieved: $secret"Step 7.6: Verify Public Access is Blocked β
From your local machine (outside Azure VNet):
bash
# Try to access storage (should fail)
curl https://$STORAGE_NAME.blob.core.windows.net/documents/test.txt
# Error: Public access is not permitted
# Try to access SQL (should timeout/fail)
# Connection attempt will fail
# Try to access Key Vault (should fail)
az keyvault secret show --vault-name $KV_NAME --name "DatabasePassword"
# Error: Public network access is disabledPhase 8: Monitor Private Endpoints β
Step 8.1: View Private Endpoint Connections β
bash
# List all private endpoints
az network private-endpoint list \
--resource-group $RG_NAME \
--output table
# Show connection status
az network private-endpoint show \
--resource-group $RG_NAME \
--name pe-storage-blob \
--query "privateLinkServiceConnections[0].privateLinkServiceConnectionState.status" -o tsvStep 8.2: View DNS Records β
bash
# List DNS records in zone
az network private-dns record-set a list \
--resource-group $RG_NAME \
--zone-name "privatelink.blob.core.windows.net" \
--output tableSummary β
| Service | Private Endpoint | DNS Zone | Public Access |
|---|---|---|---|
| Storage | pe-storage-blob | privatelink.blob.core.windows.net | Disabled |
| SQL | pe-sql | privatelink.database.windows.net | Disabled |
| Key Vault | pe-keyvault | privatelink.vaultcore.azure.net | Disabled |
Cleanup β
bash
# Delete resource group (includes all resources)
az group delete --name $RG_NAME --yes --no-wait
echo "Cleanup initiated"Key Takeaways β
- Private Endpoints: Bring PaaS services into your VNet
- Private DNS Zones: Enable name resolution to private IPs
- Disable Public Access: Block internet access after PE is configured
- DNS Integration: Link private DNS zones to VNet for automatic resolution
- Security: No data traverses the public internet
Service Endpoints vs Private Endpoints β
| Feature | Service Endpoints | Private Endpoints |
|---|---|---|
| IP Address | Uses public IP | Uses private IP |
| DNS | No change needed | Requires private DNS |
| Cost | Free | ~$7.50/endpoint/month |
| Use Case | Basic network isolation | Full private connectivity |
| Cross-region | Same region only | Cross-region supported |
| On-premises | Not accessible | Accessible via VPN/ER |
Next Steps β
- Implement Private Endpoints for more services (Cosmos DB, Event Hub)
- Configure Private Link Service for custom services
- Set up Azure Private Resolver for hybrid DNS
- Implement Azure Firewall with Private Endpoints