🎯 Mục Tiêu Lab
Tạo VM Ubuntu và bật system-assigned managed identity
Tạo Key Vault, thêm secret, cấu hình RBAC permission model
Gán role Key Vault Secrets User cho managed identity của VM
Dùng IMDS endpoint 169.254.169.254 để lấy access token từ bên trong VM
Đọc secret từ Key Vault dùng token lấy qua IMDS — zero stored credential
Phân biệt system-assigned MI và user-assigned MI về lifecycle và tái sử dụng
📋 Chuẩn Bị
- Azure subscription với quyền Owner hoặc Contributor + User Access Administrator
- Azure CLI 2.50+ đăng nhập
- SSH client (OpenSSH trên Windows 10+, Terminal trên macOS/Linux)
- Region: southeastasia (Singapore)
- VM Standard_B1s ~$0.012/giờ — xóa ngay sau lab
- Key Vault Standard tier ~$0.03/10,000 operations
- Managed identity hoàn toàn miễn phí
- Tổng lab ~$0.05 nếu hoàn thành trong 1 giờ
🏗️ Kịch Bản
Một VM đang chạy script backup cần đọc connection string được lưu trong Key Vault. Cách cũ: hardcode credential trong script hoặc env variable — cực kỳ nguy hiểm. Giải pháp đúng: bật system-assigned managed identity cho VM, gán quyền đọc secret từ Key Vault, và script dùng IMDS endpoint http://169.254.169.254 để lấy token tự động — không lưu bất kỳ secret nào trên VM.
🧪 Các Bước Thực Hiện
Tạo Resource Group, Key Vault và VM
Tạo hạ tầng cần thiết: resource group, Key Vault với RBAC model, một secret, và VM Ubuntu nhỏ.
- 1.1Portal → Resource groups → + Create → Name:
rg-az500-mi-lab17, Region: Southeast Asia - 1.2Tạo Key Vault: name
kv-az500-lab17-[suffix], Region: Southeast Asia, Permission model: Azure role-based access control - 1.3Trong Key Vault → Secrets → + Generate/Import → Name:
db-connection-string, Value:Server=sql-lab17;Database=appdb;... - 1.4Tạo VM: Virtual machines → Image: Ubuntu 22.04 LTS, Size: B1s, Authentication: SSH public key, Public inbound ports: SSH (22)
# Biến môi trường
RG="rg-az500-mi-lab17"
LOCATION="southeastasia"
KV_NAME="kv-az500-lab17-$(date +%s | tail -c 5)"
VM_NAME="vm-az500-lab17"
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
# Tạo Resource Group
az group create --name $RG --location $LOCATION
# Tạo Key Vault với RBAC permission model (chuẩn 2026)
az keyvault create \
--name $KV_NAME \
--resource-group $RG \
--location $LOCATION \
--enable-rbac-authorization true \
--sku standard \
--enable-soft-delete true \
--retention-days 7
echo "Key Vault: $KV_NAME"
# Gán quyền Key Vault Secrets Officer cho user hiện tại (để tạo secret)
CURRENT_USER=$(az ad signed-in-user show --query id -o tsv)
KV_ID=$(az keyvault show --name $KV_NAME --query id -o tsv)
az role assignment create \
--assignee $CURRENT_USER \
--role "Key Vault Secrets Officer" \
--scope $KV_ID
# Tạo secret trong Key Vault
sleep 15 # Đợi role assignment propagate
az keyvault secret set \
--vault-name $KV_NAME \
--name "db-connection-string" \
--value "Server=sql-lab17.database.windows.net;Database=appdb;Encrypt=True"
# Tạo VM Ubuntu 22.04 LTS (Standard_B1s để tiết kiệm chi phí)
az vm create \
--resource-group $RG \
--name $VM_NAME \
--image Ubuntu2204 \
--size Standard_B1s \
--location $LOCATION \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard \
--tags "owner=hoatranlab" "env=lab" "module=az500"
echo "VM created: $VM_NAME"
VM_PUBLIC_IP=$(az vm show --resource-group $RG --name $VM_NAME \
--show-details --query publicIps -o tsv)
echo "Public IP: $VM_PUBLIC_IP"
Bật System-assigned Managed Identity
Khi bật system-assigned MI, Azure tự tạo service principal trong Entra ID gắn với lifecycle của VM. Khi VM bị xóa, service principal cũng tự xóa.
- 2.1Portal → tìm VM
vm-az500-lab17→ menu trái → "Identity" - 2.2Tab "System assigned" → Status: toggle sang On → click Save
- 2.3Xác nhận dialog → sau vài giây: Object ID xuất hiện — đây là Object ID của service principal
- 2.4Click "Azure role assignments" → quan sát hiện chưa có role nào được gán
# Bật system-assigned managed identity cho VM
az vm identity assign \
--resource-group $RG \
--name $VM_NAME \
--identities "[system]"
# Lấy Object ID của managed identity (service principal)
MI_OBJECT_ID=$(az vm identity show \
--resource-group $RG \
--name $VM_NAME \
--query principalId -o tsv)
echo "Managed Identity Object ID: $MI_OBJECT_ID"
# Xác nhận trong Entra ID — service principal đã được tạo
az ad sp show --id $MI_OBJECT_ID \
--query "{Name:displayName, Type:servicePrincipalType, AppId:appId}" \
--output json
{
"appId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"displayName": "vm-az500-lab17",
"servicePrincipalType": "ManagedIdentity"
}
Gán Role Key Vault Secrets User (RBAC)
Gán built-in role Key Vault Secrets User cho managed identity của VM trên scope của Key Vault — chỉ cho phép đọc secret, không tạo/xóa được.
- 3.1Portal → Key Vault
kv-az500-lab17-*→ menu trái → "Access control (IAM)" - 3.2Click "+ Add" → "Add role assignment"
- 3.3Role: tìm "Key Vault Secrets User" → Next
- 3.4Members: "Managed identity" → "+ Select members" → Managed identity: "Virtual machine" → chọn
vm-az500-lab17 - 3.5Review + assign → xác nhận role assignment thành công trong tab "Role assignments"
# Gán role "Key Vault Secrets User" cho managed identity trên scope Key Vault
az role assignment create \
--assignee-object-id $MI_OBJECT_ID \
--assignee-principal-type ServicePrincipal \
--role "Key Vault Secrets User" \
--scope $KV_ID
# Xác nhận role assignment
az role assignment list \
--assignee $MI_OBJECT_ID \
--scope $KV_ID \
--output table
# Kết quả mong đợi:
# RoleDefinitionName PrincipalName Scope
# Key Vault Secrets User vm-az500-lab17 /subscriptions/.../kv-az500-lab17-*
Lấy Token qua IMDS Endpoint
Azure Instance Metadata Service (IMDS) là HTTP endpoint nội bộ http://169.254.169.254 — chỉ truy cập được từ bên trong VM. Dùng để lấy access token cho managed identity mà không cần bất kỳ credential nào.
Portal → VM → Connect → Bastion (nếu có) → SSH vào VM → chạy lệnh trong bước 4 CLI bên dưới
# === Chạy trên máy local — SSH vào VM ===
ssh azureuser@$VM_PUBLIC_IP
# === Từ đây tất cả lệnh chạy BÊN TRONG VM ===
# Cài curl nếu chưa có
sudo apt-get update && sudo apt-get install -y curl jq
# Lấy access token cho Key Vault qua IMDS
# resource = https://vault.azure.net (Key Vault endpoint)
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" | jq -r '.access_token')
echo "Token type: Bearer"
echo "Token (first 50 chars): ${TOKEN:0:50}..."
# Decode JWT header để xem thông tin token (không decode signature)
echo $TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | jq \
'{oid: .oid, appid: .appid, iss: .iss, exp: .exp}' 2>/dev/null || \
echo "Token obtained successfully"
Đọc Secret từ Key Vault bằng Managed Identity
Dùng token vừa lấy qua IMDS để gọi Key Vault REST API — không có password, không có service principal credential nào trên VM.
# Thay KV_NAME bằng tên Key Vault thực (hỏi admin hoặc xem từ Portal)
KV_NAME="kv-az500-lab17-XXXXX" # Thay XXXXX bằng suffix thực
# Đọc secret "db-connection-string" từ Key Vault REST API
SECRET_RESPONSE=$(curl -s \
"https://$KV_NAME.vault.azure.net/secrets/db-connection-string?api-version=7.4" \
-H "Authorization: Bearer $TOKEN")
# Parse giá trị secret
SECRET_VALUE=$(echo $SECRET_RESPONSE | jq -r '.value')
echo "Secret value: $SECRET_VALUE"
# Kiểm tra metadata của secret
echo $SECRET_RESPONSE | jq '{id: .id, created: .attributes.created, enabled: .attributes.enabled}'
# Thử list tất cả secrets — sẽ FAIL vì Key Vault Secrets User không có quyền list
curl -s \
"https://$KV_NAME.vault.azure.net/secrets?api-version=7.4" \
-H "Authorization: Bearer $TOKEN" | jq '.error'
# Kết quả: Forbidden — đúng với principle of least privilege
Secret value: Server=sql-lab17.database.windows.net;Database=appdb;Encrypt=True
{
"id": "https://kv-az500-lab17-xxxxx.vault.azure.net/secrets/db-connection-string/...",
"created": 1748000000,
"enabled": true
}
📊 Kết Quả Đầu Ra Lab 17
VM Identity tab: Status = On, Object ID hiển thị, Entra ID có service principal ManagedIdentity type
Key Vault IAM → Role assignments: vm-az500-lab17 có role "Key Vault Secrets User"
curl IMDS trả về access_token JWT hợp lệ, token type = Bearer
REST API /secrets/db-connection-string trả về value đúng
List secrets API trả về 403 Forbidden — Key Vault Secrets User không có quyền list
Không có file .env, không có client_secret, không có connection string cứng trên VM
🧹 Dọn Dẹp
# Xóa toàn bộ resource group (VM, Key Vault, NIC, NSG, Public IP, Disk...)
az group delete \
--name rg-az500-mi-lab17 \
--yes \
--no-wait
# Xóa soft-deleted Key Vault (nếu cần purge hoàn toàn để tái tạo với tên tương tự)
# az keyvault purge --name $KV_NAME --location $LOCATION
echo "Cleanup initiated. Resources will be deleted in background."
❓ Câu Hỏi Ôn Tập
1. IMDS endpoint 169.254.169.254 có thể truy cập từ bên ngoài VM không? Tại sao đây là cơ chế bảo mật quan trọng?
Gợi ý: Không — đây là link-local address, chỉ routable trong VM. Kẻ tấn công bên ngoài không thể lấy token, kể cả khi biết URL. Phải có code execution trên VM mới gọi được.
2. System-assigned MI bị xóa khi nào? So sánh với user-assigned MI về lifecycle và tái sử dụng.
Gợi ý: System-assigned MI xóa theo VM — không tái sử dụng được. User-assigned MI là independent resource — gán cho nhiều VM/App, tồn tại sau khi resource bị xóa.
3. Tại sao Key Vault nên dùng RBAC permission model thay vì Vault Access Policy (legacy)?
Gợi ý: RBAC có audit log tốt hơn, hỗ trợ scope (subscription/RG/vault/secret), tích hợp Azure Policy, dễ quản lý tập trung. Vault Access Policy legacy chỉ scope ở vault level.
4. Key Vault Secrets User khác Key Vault Secrets Officer ở điểm gì? Khi nào dùng mỗi role?
Gợi ý: Secrets User = chỉ GET và LIST secret versions (read-only). Secrets Officer = full CRUD. App đọc secret dùng Secrets User. Admin tạo/rotate secret dùng Secrets Officer.
5. Nếu VM bị compromise và kẻ tấn công chạy code bên trong, họ có thể dùng IMDS để đọc secret không? Làm thế nào để giảm thiểu rủi ro này?
Gợi ý: Có — đây là rủi ro real. Giảm thiểu: giới hạn secret scope (chỉ đúng secret cần thiết), bật Private Endpoint cho Key Vault, giám sát KV access log, dùng Defender for Key Vault để alert truy cập bất thường.