🎯 Mục Tiêu Lab
Tạo App Service Plan và Web App cơ bản
Cấu hình Access Restrictions — chỉ cho phép subnet hoặc IP cụ thể truy cập inbound
Bật VNet Integration — cho phép app gọi outbound đến resources trong VNet (SQL, Storage private endpoint)
Tạo Private Endpoint cho App Service (inbound private — không expose public hostname)
Phân biệt Access Restriction vs Private Endpoint vs VNet Integration
Kiểm tra App Service gọi được Storage private endpoint qua VNet Integration
📋 Chuẩn Bị
- Azure subscription Active
- VNet
vnet-az500-privatetừ lab trước - App Service Plan Standard S1 hoặc cao hơn (cần để dùng VNet Integration)
- Azure CLI ≥ 2.50
- App Service Plan S1: ~$0.10/giờ — xóa sau lab
- VNet Integration cần subnet riêng (không share với PE hay app subnet)
- Private DNS Zone App Service:
privatelink.azurewebsites.net - Free/Shared tier KHÔNG hỗ trợ VNet Integration
🏗️ Kịch Bản & Kiến Trúc
App Service cần: (1) chỉ nhận inbound từ subnet frontend hoặc Application Gateway — không public Internet, (2) gọi outbound đến SQL và Storage private endpoints trong VNet. Giải pháp: Access Restriction + VNet Integration, hoặc tạo Private Endpoint cho App Service nếu cần private hoàn toàn.
🧪 Các Bước Thực Hiện
Tạo Subnet Bổ Sung và App Service
Tạo snet-frontend (nguồn inbound được phép) và snet-vnetint (subnet cho VNet Integration — phải delegate cho App Service).
# Subnet frontend (nguồn được phép truy cập app) az network vnet subnet create \ --name snet-frontend \ --vnet-name vnet-az500-private \ --resource-group rg-az500-ch07-private \ --address-prefix 10.10.4.0/24 # Subnet VNet Integration (phải delegate cho App Service) az network vnet subnet create \ --name snet-vnetint \ --vnet-name vnet-az500-private \ --resource-group rg-az500-ch07-private \ --address-prefix 10.10.5.0/24 \ --delegations Microsoft.Web/serverFarms # Tạo App Service Plan (Standard S1 — bắt buộc cho VNet Integration) az appservice plan create \ --name asp-az500-lab31 \ --resource-group rg-az500-ch07-private \ --location southeastasia \ --sku S1 # Tạo Web App az webapp create \ --name appservice-az500-lab31 \ --resource-group rg-az500-ch07-private \ --plan asp-az500-lab31 \ --runtime "NODE:20-lts" # Xem URL mặc định az webapp show \ --name appservice-az500-lab31 \ --resource-group rg-az500-ch07-private \ --query defaultHostName -o tsv # → appservice-az500-lab31.azurewebsites.net
1. Portal → App Services → + Create → Web App Basics: - Resource group: rg-az500-ch07-private - Name: appservice-az500-lab31 (phải unique) - Publish: Code - Runtime stack: Node 20 LTS - Region: Southeast Asia - Plan: (New) asp-az500-lab31 → Standard S1 Networking: - Enable public access: On (để test, sẽ restrict sau) 2. Review + Create → Create
Cấu Hình Access Restrictions (Inbound)
Thêm rule cho phép snet-frontend, sau đó deny tất cả Internet. Access Restrictions hoạt động như layer-7 firewall cho App Service.
1. App Service → Networking → Access restriction → + Add rule Rule 1 — Cho phép snet-frontend: - Name: Allow-snet-frontend - Priority: 100 - Action: Allow - Type: Virtual Network - Virtual Network: vnet-az500-private - Subnet: snet-frontend Rule 2 — Deny tất cả (thêm sau Allow) - Name: Deny-All-Internet - Priority: 200 - Action: Deny - Type: Any 2. Save → Xác nhận rules hiển thị đúng thứ tự priority Lưu ý: App Service tự động có Deny All mặc định ở cuối. Rule Allow ở priority thấp hơn sẽ được ưu tiên trước.
# Lấy subnet ID của snet-frontend FRONTEND_SUBNET=$(az network vnet subnet show \ --name snet-frontend \ --vnet-name vnet-az500-private \ --resource-group rg-az500-ch07-private \ --query id -o tsv) # Thêm rule Allow cho snet-frontend (priority 100) az webapp config access-restriction add \ --name appservice-az500-lab31 \ --resource-group rg-az500-ch07-private \ --rule-name Allow-snet-frontend \ --action Allow \ --subnet $FRONTEND_SUBNET \ --priority 100 # Thêm rule Deny All (priority 200) az webapp config access-restriction add \ --name appservice-az500-lab31 \ --resource-group rg-az500-ch07-private \ --rule-name Deny-All-Internet \ --action Deny \ --ip-address Any \ --priority 200 # Xem danh sách rules az webapp config access-restriction show \ --name appservice-az500-lab31 \ --resource-group rg-az500-ch07-private \ --output table
Name Priority Action Type IpAddress/Subnet -------------------- -------- ------ ------------- -------------------------- Allow-snet-frontend 100 Allow VirtualNetwork snet-frontend (10.10.4.0/24) Deny-All-Internet 200 Deny Any Any
Bật VNet Integration (Outbound)
VNet Integration cho phép App Service gọi outbound đến resources trong VNet (Storage PE, SQL PE). Subnet snet-vnetint được delegate cho Microsoft.Web/serverFarms.
1. App Service → Networking → VNet integration → + Add VNet integration - Virtual Network: vnet-az500-private - Subnet: snet-vnetint (phải đã delegate) → OK → đợi ~1 phút 2. App settings → (quan trọng) thêm app setting: WEBSITE_DNS_SERVER = 168.63.129.16 → Để app dùng Azure DNS, phân giải được Private DNS Zone 3. Configuration → General settings → bật: "Route all traffic" = On → Để toàn bộ outbound đi qua VNet (bao gồm DNS queries)
# Bật VNet Integration với snet-vnetint
az webapp vnet-integration add \
--name appservice-az500-lab31 \
--resource-group rg-az500-ch07-private \
--vnet vnet-az500-private \
--subnet snet-vnetint
# Thêm app setting để dùng Azure DNS (bắt buộc cho Private DNS Zone)
az webapp config appsettings set \
--name appservice-az500-lab31 \
--resource-group rg-az500-ch07-private \
--settings WEBSITE_DNS_SERVER=168.63.129.16
# Bật route all traffic qua VNet (để DNS query cũng dùng VNet DNS)
az webapp config set \
--name appservice-az500-lab31 \
--resource-group rg-az500-ch07-private \
--generic-configurations '{"vnetRouteAllEnabled": true}'
# Xác nhận VNet Integration
az webapp vnet-integration list \
--name appservice-az500-lab31 \
--resource-group rg-az500-ch07-private \
--output table
Name VnetResourceId SubnetName SwiftSupported --------------------- ------------------------------------------------ ------------ ---------------- vnet-az500-private /subscriptions/.../vnet-az500-private snet-vnetint True
Tạo Private Endpoint cho App Service (Tùy chọn — Inbound Private)
Nếu muốn App Service không có public hostname, tạo Private Endpoint. Sau khi tạo PE, public URL vẫn tồn tại nhưng bị từ chối — chỉ truy cập qua private IP từ VNet.
# Lấy App Service ID APP_ID=$(az webapp show \ --name appservice-az500-lab31 \ --resource-group rg-az500-ch07-private \ --query id -o tsv) # Tạo Private Endpoint cho App Service az network private-endpoint create \ --name pe-appservice-lab31 \ --resource-group rg-az500-ch07-private \ --vnet-name vnet-az500-private \ --subnet snet-pe \ --private-connection-resource-id $APP_ID \ --group-id sites \ --connection-name conn-appservice-lab31 \ --location southeastasia # Tạo Private DNS Zone cho App Service az network private-dns zone create \ --resource-group rg-az500-ch07-private \ --name "privatelink.azurewebsites.net" # Link DNS Zone với VNet az network private-dns link vnet create \ --resource-group rg-az500-ch07-private \ --zone-name "privatelink.azurewebsites.net" \ --name link-vnet-appservice \ --virtual-network vnet-az500-private \ --registration-enabled false # Lấy private IP và tạo A record NIC_ID=$(az network private-endpoint show \ --name pe-appservice-lab31 \ --resource-group rg-az500-ch07-private \ --query "networkInterfaces[0].id" -o tsv) APP_PRIVATE_IP=$(az network nic show --ids $NIC_ID \ --query "ipConfigurations[0].privateIPAddress" -o tsv) az network private-dns record-set a add-record \ --resource-group rg-az500-ch07-private \ --zone-name "privatelink.azurewebsites.net" \ --record-set-name "appservice-az500-lab31" \ --ipv4-address $APP_PRIVATE_IP echo "App Service Private IP: $APP_PRIVATE_IP"
Kiểm Tra Access Restriction và VNet Integration
# Từ máy local (ngoài snet-frontend) — phải bị 403 curl -I https://appservice-az500-lab31.azurewebsites.net # HTTP/1.1 403 Forbidden # X-MS-IIS-Reason: AccessRestriction # Từ VM trong snet-frontend — phải được phép (200) # SSH vào VM trong snet-frontend, rồi: curl -s https://appservice-az500-lab31.azurewebsites.net # → HTML của default page App Service
# Dùng Kudu console của App Service để test từ bên trong app # Portal → App Service → Advanced Tools (Kudu) → SSH hoặc Debug Console # Trong Kudu console: # Kiểm tra DNS phân giải Storage PE qua VNet curl https://staz500lab29xk9m.blob.core.windows.net \ --resolve staz500lab29xk9m.blob.core.windows.net:443:10.10.3.4 \ -I # Hoặc dùng nameserver 168.63.129.16: nslookup staz500lab29xk9m.blob.core.windows.net 168.63.129.16 # → 10.10.3.4 ← App Service phân giải được private IP qua VNet Integration! # Test từ Azure CLI (App Service có thể dùng managed identity) # Portal → App Service → Identity → System assigned: On az webapp identity assign \ --name appservice-az500-lab31 \ --resource-group rg-az500-ch07-private
1. App Service → Networking: - Inbound traffic: Private endpoint + Access restrictions ✓ - Outbound traffic: VNet Integration → snet-vnetint ✓ 2. App Service → Networking → VNet integration: - Status: Connected to vnet-az500-private - Route All: Enabled ✓ 3. App Service → Networking → Access restriction: - Allow-snet-frontend (Priority 100) ✓ - Deny-All-Internet (Priority 200) ✓
📊 Kết Quả Đầu Ra Lab 31
curl từ ngoài → 403 Forbidden (AccessRestriction). Từ snet-frontend → 200 OK
App Service connected snet-vnetint, Route All: Enabled
nslookup trong Kudu → Storage/SQL PE trả về 10.x.x.x
pe-appservice-lab31 với private IP, DNS Zone privatelink.azurewebsites.net linked
App có system-assigned managed identity để gọi Storage/SQL không cần credential
App setting 168.63.129.16 để dùng Azure DNS qua VNet Integration
🧹 Dọn Dẹp Tài Nguyên
# Xóa Private Endpoint App Service az network private-endpoint delete \ --name pe-appservice-lab31 \ --resource-group rg-az500-ch07-private --yes # Xóa App Service Plan và Web App az webapp delete \ --name appservice-az500-lab31 \ --resource-group rg-az500-ch07-private az appservice plan delete \ --name asp-az500-lab31 \ --resource-group rg-az500-ch07-private --yes # Xóa Private DNS Zones còn lại az network private-dns zone delete \ --resource-group rg-az500-ch07-private \ --name "privatelink.azurewebsites.net" --yes # Xóa toàn bộ resource group (bao gồm mọi thứ từ Lab 28-31) az group delete \ --name rg-az500-ch07-private \ --yes --no-wait # Xác nhận xóa az group show --name rg-az500-ch07-private 2>/dev/null || echo "Resource group deleted."
❓ Câu Hỏi Ôn Tập
1. Access Restriction và Private Endpoint khác nhau như thế nào về inbound bảo mật App Service?
Gợi ý: Access Restriction là layer-7 firewall — public hostname vẫn tồn tại, traffic bị filter. Private Endpoint thay đổi DNS, app chỉ có private IP — public URL vẫn resolve nhưng không có service lắng nghe. PE bảo mật hơn nhưng tốn phí.
2. Tại sao VNet Integration cần subnet riêng được delegate cho Microsoft.Web/serverFarms?
Gợi ý: Delegation cho phép App Service Platform inject NIC vào subnet đó để route outbound traffic. Không thể share với subnet khác đã có delegation hoặc Private Endpoint.
3. Tại sao phải set cả WEBSITE_DNS_SERVER=168.63.129.16 VÀ vnetRouteAllEnabled=true?
Gợi ý: WEBSITE_DNS_SERVER chỉ Azure DNS. vnetRouteAllEnabled bắt tất cả traffic (bao gồm DNS UDP port 53) đi qua VNet. Nếu thiếu một trong hai, DNS query vẫn đi public và không phân giải được Private DNS Zone.
4. App Service Free/Shared tier có hỗ trợ VNet Integration không? Tại sao cần Standard S1 trở lên?
Gợi ý: Free và Shared không hỗ trợ VNet Integration vì chạy trên shared infrastructure. Basic B1 hỗ trợ VNet Integration nhưng không hỗ trợ Private Endpoint (cần Standard+). Mỗi tier mở thêm tính năng networking.
5. Kịch bản nào nên dùng Access Restriction thay vì Private Endpoint cho App Service?
Gợi ý: Access Restriction phù hợp khi: cần restrict theo IP cụ thể/subnet/Service Tag, muốn giữ public hostname cho CDN/Front Door, không có budget cho PE, tier Basic. Private Endpoint phù hợp khi cần zero public exposure, hybrid scenario, enterprise compliance.