🎯 Mục Tiêu Lab
Tạo VNet + subnet cho VM và private endpoint
Tạo Private Endpoint cho Key Vault với private IP trong VNet
Cấu hình Private DNS Zone privatelink.vaultcore.azure.net
Tắt public network access cho Key Vault
Bật system-assigned managed identity cho VM, gán Key Vault Secrets User
Từ VM: xác nhận DNS resolve về private IP, đọc secret qua managed identity token
📋 Chuẩn Bị
- Key Vault từ Lab 40 (hoặc tạo mới trong lab này)
- Quyền tạo VNet, VM, Private Endpoint
- Azure CLI 2.50+
- VM Linux sẽ phát sinh chi phí — xóa sau lab
- RG: rg-az500-lab41
- Key Vault: kv-az500-lab41-[suffix]
- VNet: vnet-az500-lab41
- Subnet VM: snet-vm-lab41
- Subnet PE: snet-pe-lab41
- VM: vm-az500-lab41
- Private DNS: privatelink.vaultcore.azure.net
🏗️ Kịch Bản
VM ứng dụng cần đọc secret từ Key Vault mà không cần credential và không để Key Vault expose ra Internet. Chuẩn 2026: Key Vault chỉ accessible qua Private Endpoint (private IP trong VNet), DNS resolve tự động về private IP, VM dùng managed identity (không cần username/password) để lấy token và đọc secret.
🧪 Các Bước Thực Hiện
Tạo VNet, Subnet và Key Vault với RBAC
- 1.1Tạo VNet
vnet-az500-lab41với address space10.41.0.0/16, 2 subnet:snet-vm-lab41 (10.41.1.0/24)vàsnet-pe-lab41 (10.41.2.0/24) - 1.2Tắt Private Endpoint Network Policy trên
snet-pe-lab41(bắt buộc để tạo PE) - 1.3Tạo Key Vault
kv-az500-lab41-xxxvới RBAC + soft delete + purge protection (xem Lab 40) - 1.4Tạo secret
app-db-password=SuperSecret2026!
# Tạo resource group
az group create --name rg-az500-lab41 --location southeastasia
# Tạo VNet với 2 subnet
az network vnet create \
--resource-group rg-az500-lab41 \
--name vnet-az500-lab41 \
--address-prefix 10.41.0.0/16 \
--subnet-name snet-vm-lab41 \
--subnet-prefix 10.41.1.0/24
az network vnet subnet create \
--resource-group rg-az500-lab41 \
--vnet-name vnet-az500-lab41 \
--name snet-pe-lab41 \
--address-prefix 10.41.2.0/24
# Tắt Private Endpoint Network Policies trên subnet PE (bắt buộc)
az network vnet subnet update \
--resource-group rg-az500-lab41 \
--vnet-name vnet-az500-lab41 \
--name snet-pe-lab41 \
--disable-private-endpoint-network-policies true
# Tạo Key Vault (RBAC + soft delete + purge protection)
KV_NAME="kv-az500-lab41-$(shuf -i 100-999 -n 1)"
echo "Key Vault: $KV_NAME"
az keyvault create \
--resource-group rg-az500-lab41 \
--name $KV_NAME \
--location southeastasia \
--sku standard \
--enable-rbac-authorization true \
--enable-soft-delete true \
--soft-delete-retention-days 90 \
--enable-purge-protection true
# Gán Key Vault Administrator cho user hiện tại
KV_ID=$(az keyvault show --name $KV_NAME --query id --output tsv)
USER_OID=$(az ad signed-in-user show --query id --output tsv)
az role assignment create \
--assignee $USER_OID \
--role "Key Vault Administrator" \
--scope $KV_ID
# Tạo secret test
az keyvault secret set \
--vault-name $KV_NAME \
--name app-db-password \
--value "SuperSecret2026!"
echo "Setup hoàn tất. KV_NAME=$KV_NAME"
Tạo Private Endpoint và Private DNS Zone
- 2.1Key vault → Networking → tab Private endpoint connections → + Create
- 2.2Name:
pe-kv-lab41→ Resource: Key Vault → Target sub-resource: vault - 2.3Virtual network:
vnet-az500-lab41→ Subnet:snet-pe-lab41 - 2.4Private DNS integration: Yes → DNS zone: privatelink.vaultcore.azure.net → Create
- 2.5Key vault → Networking → Firewalls and virtual networks → Public network access: Disable → Apply
# Lấy subnet ID cho private endpoint
PE_SUBNET_ID=$(az network vnet subnet show \
--resource-group rg-az500-lab41 \
--vnet-name vnet-az500-lab41 \
--name snet-pe-lab41 \
--query id --output tsv)
# Tạo Private Endpoint cho Key Vault
# --group-id vault = sub-resource của Key Vault
az network private-endpoint create \
--resource-group rg-az500-lab41 \
--name pe-kv-lab41 \
--vnet-name vnet-az500-lab41 \
--subnet snet-pe-lab41 \
--private-connection-resource-id $KV_ID \
--group-id vault \
--connection-name conn-kv-lab41 \
--location southeastasia
# Xem private IP được gán cho endpoint
az network private-endpoint show \
--resource-group rg-az500-lab41 \
--name pe-kv-lab41 \
--query "customDnsConfigs[].{FQDN:fqdn, IP:ipAddresses}" \
--output table
# Tạo Private DNS Zone cho Key Vault
az network private-dns zone create \
--resource-group rg-az500-lab41 \
--name "privatelink.vaultcore.azure.net"
# Link DNS Zone với VNet
az network private-dns link vnet create \
--resource-group rg-az500-lab41 \
--zone-name "privatelink.vaultcore.azure.net" \
--name dns-link-vnet-lab41 \
--virtual-network vnet-az500-lab41 \
--registration-enabled false
# Tạo DNS record set từ private endpoint
PE_NIC_ID=$(az network private-endpoint show \
--resource-group rg-az500-lab41 \
--name pe-kv-lab41 \
--query "networkInterfaces[0].id" --output tsv)
PE_IP=$(az network nic show \
--ids $PE_NIC_ID \
--query "ipConfigurations[0].privateIPAddress" --output tsv)
echo "Private IP của KV endpoint: $PE_IP"
# Tạo A record trong Private DNS Zone
az network private-dns record-set a create \
--resource-group rg-az500-lab41 \
--zone-name "privatelink.vaultcore.azure.net" \
--name $KV_NAME
az network private-dns record-set a add-record \
--resource-group rg-az500-lab41 \
--zone-name "privatelink.vaultcore.azure.net" \
--record-set-name $KV_NAME \
--ipv4-address $PE_IP
# TẮT public network access của Key Vault
# SAU KHI Private Endpoint đã được tạo và DNS đã cấu hình
az keyvault update \
--resource-group rg-az500-lab41 \
--name $KV_NAME \
--public-network-access Disabled
# Xác nhận public access đã tắt
az keyvault show --name $KV_NAME \
--query "properties.publicNetworkAccess" --output tsv
# Output: Disabled
Tạo VM với Managed Identity và Gán Key Vault Secrets User
- 3.1Tạo VM Ubuntu 22.04 trong
snet-vm-lab41→ không cần public IP - 3.2Tab Management → System assigned managed identity: On
- 3.3Sau khi VM tạo: VM → Identity → lấy Object ID của managed identity
- 3.4Key vault → Access control (IAM) → Add → Key Vault Secrets User → Managed identity → chọn VM → Assign
# Tạo VM Linux (không public IP, dùng Bastion hoặc serial console để test)
# Dùng VM size nhỏ nhất để tiết kiệm chi phí
az vm create \
--resource-group rg-az500-lab41 \
--name vm-az500-lab41 \
--image Ubuntu2204 \
--size Standard_B1s \
--vnet-name vnet-az500-lab41 \
--subnet snet-vm-lab41 \
--public-ip-address "" \
--admin-username azureuser \
--generate-ssh-keys \
--assign-identity \
--location southeastasia
# Lấy managed identity principal ID của VM
VM_MI_OID=$(az vm identity show \
--resource-group rg-az500-lab41 \
--name vm-az500-lab41 \
--query principalId --output tsv)
echo "VM Managed Identity OID: $VM_MI_OID"
# Gán Key Vault Secrets User (read secrets only) cho managed identity
az role assignment create \
--assignee-object-id $VM_MI_OID \
--assignee-principal-type ServicePrincipal \
--role "Key Vault Secrets User" \
--scope $KV_ID
# Xác nhận role assignment
az role assignment list \
--scope $KV_ID \
--query "[?principalId=='$VM_MI_OID'].{Role:roleDefinitionName, Principal:principalId}" \
--output table
Kiểm Tra Từ VM — DNS Private Resolution và Đọc Secret
- 4.1VM → Run command → RunShellScript → chạy lệnh test DNS và secret
- 4.2Kiểm tra output: DNS resolve về private IP (10.41.2.x), secret value được trả về
# Script chạy bên trong VM để test toàn bộ flow
# Thay KV_NAME bằng tên thực tế
cat > /tmp/test-keyvault.sh << SCRIPT
#!/bin/bash
KV_NAME="REPLACE_WITH_KV_NAME"
echo "=== 1. Kiểm tra DNS resolution ==="
nslookup \${KV_NAME}.vault.azure.net
# Phải resolve về 10.41.2.x (private IP), KHÔNG phải public IP
echo ""
echo "=== 2. Lấy Access Token từ IMDS (managed identity) ==="
TOKEN=$(curl -s \
"http://169.254.169.254/metadata/identity/oauth2/token?\
api-version=2018-02-01&resource=https%3A%2F%2Fvault.azure.net" \
-H "Metadata: true" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
echo "Token lấy thành công (prefix): \${TOKEN:0:50}..."
echo ""
echo "=== 3. Đọc secret từ Key Vault qua private endpoint ==="
SECRET=$(curl -s \
"https://\${KV_NAME}.vault.azure.net/secrets/app-db-password?api-version=7.4" \
-H "Authorization: Bearer \$TOKEN" | \
python3 -c "import sys,json; print(json.load(sys.stdin)['value'])")
echo "Secret value: \$SECRET"
echo ""
echo "=== 4. Xác nhận public access bị chặn (từ local machine) ==="
echo "Từ máy bên ngoài VNet, thử: curl https://\${KV_NAME}.vault.azure.net/secrets"
echo "Sẽ nhận: 403 Forbidden (public network access disabled)"
SCRIPT
# Thay KV_NAME trong script
sed -i "s/REPLACE_WITH_KV_NAME/$KV_NAME/g" /tmp/test-keyvault.sh
# Chạy script trên VM qua az vm run-command
az vm run-command invoke \
--resource-group rg-az500-lab41 \
--name vm-az500-lab41 \
--command-id RunShellScript \
--scripts "$(cat /tmp/test-keyvault.sh)"
=== 1. Kiểm tra DNS resolution ===
Server: 168.63.129.16
Address: 168.63.129.16#53
Non-authoritative answer:
kv-az500-lab41-xxx.vault.azure.net canonical name = kv-az500-lab41-xxx.privatelink.vaultcore.azure.net
Name: kv-az500-lab41-xxx.privatelink.vaultcore.azure.net
Address: 10.41.2.4 # ← PRIVATE IP (không phải public IP)
=== 2. Lấy Access Token từ IMDS (managed identity) ===
Token lấy thành công (prefix): eyJ0eXAiOiJKV1QiLCJhbGciOiJSUz...
=== 3. Đọc secret từ Key Vault qua private endpoint ===
Secret value: SuperSecret2026! # ← secret đọc thành công!
=== 4. Public access bị chặn ===
# Từ máy ngoài VNet:
# curl → 403 Forbidden: Public network access is disabled for this resource
📊 Kết Quả Mong Đợi
PE state: Approved, private IP trong range 10.41.2.x gán cho NIC
nslookup từ VM → CNAME privatelink.vaultcore.azure.net → 10.41.2.x
IMDS token → KV API → secret value: SuperSecret2026!
Từ máy ngoài VNet → 403 Forbidden, publicNetworkAccess: Disabled
🧹 Dọn Dẹp Tài Nguyên
# Bật lại public access để có thể xóa key vault qua CLI
az keyvault update \
--resource-group rg-az500-lab41 \
--name $KV_NAME \
--public-network-access Enabled
# Xóa resource group (VM, VNet, PE, DNS Zone, KV soft-deleted)
az group delete --name rg-az500-lab41 --yes --no-wait
# Key Vault vào soft-deleted state — purge sau 90 ngày hoặc:
# az keyvault purge --name $KV_NAME --location southeastasia
# (chỉ khi purge protection TẮT)
❓ Câu Hỏi Ôn Tập
1. Tại sao cần tạo Private DNS Zone privatelink.vaultcore.azure.net thay vì dùng DNS mặc định?
Gợi ý: DNS mặc định của Azure resolve kv.vault.azure.net về public IP. Private DNS Zone override này để resolve về private IP của endpoint trong VNet — không cần thay đổi code ứng dụng
2. Managed identity của VM lấy access token từ đâu và tại sao không cần lưu credential trong code?
Gợi ý: Azure Instance Metadata Service (IMDS) tại 169.254.169.254 — endpoint nội bộ của VM cấp token tự động từ Entra ID. Token ngắn hạn (~1 giờ), tự refresh — không có credential nào để lộ
3. Tại sao phải tắt Private Endpoint Network Policies trên subnet trước khi tạo PE?
Gợi ý: Network policies (NSG, UDR) mặc định không áp dụng cho private endpoints. Cần disable PrivateEndpointNetworkPolicies trên subnet để Azure có thể gán NIC và IP tĩnh cho PE
4. Sau khi tắt public access, administrator từ ngoài VNet có thể quản lý Key Vault bằng CLI không?
Gợi ý: Không qua data plane (secrets/keys/certs) nhưng vẫn quản lý được management plane (properties, RBAC) qua Azure Resource Manager. Để truy cập data plane từ ngoài cần tạm bật public access hoặc dùng VPN/Bastion vào VNet
5. Nếu cần nhiều VM ở các VNet khác nhau truy cập cùng một Key Vault, cần làm gì?
Gợi ý: Tạo thêm Private Endpoint trong mỗi VNet (hoặc VNet peering + DNS forwarding), link Private DNS Zone với mỗi VNet. Mỗi VNet có NIC riêng của PE nhưng cùng trỏ về một Key Vault