Lan truyền IAM Policy: Eventual Consistency & kiểm thử
Vì sao IAM propagation khó xử lý
Một trong những khía cạnh ít được hiểu đúng nhất của bảo mật GCP là sự thật rằng IAM policies không lan truyền ngay lập tức. Khi bạn cấp một role cho một user:
T+0: gcloud projects add-iam-policy-binding PROJECT_ID \
--member=user:alice@company.com \
--role=roles/editor
Response: Updated policy
T+0.5s: Alice thử truy cập project
Kết quả: Có thể nhận "permission denied" (policy chưa hiển thị)
T+5s: Alice thử lại
Kết quả: Nhiều khả năng thành công (policy đã lan truyền)
T+60s+: Tất cả cache đã được cập nhật (đã đảm bảo)Thực tế trong production:
- User được cấp role rồi thử dùng ngay — bị từ chối truy cập
- Script tạo service account rồi dùng ngay — thất bại
- IAM policies thay đổi nhưng service phụ thuộc vẫn ép buộc quyền cũ
- Monitoring hiển thị audit log không nhất quán
Kiến trúc lan truyền IAM
IAM policies có hệ thống lan truyền ba lớp:
Lớp 1: Control Plane (ngay lập tức)
T+0: gọi API setIamPolicy()
↓
Policy được lưu ở master control plane
↓
Trả response về (đồng bộ)Ở lớp này, IAM policy được cập nhật ngay. API call hoàn tất thành công.
Lớp 2: Cache của API server (5-30 giây)
T+0 đến T+30s:
kube-apiserver (hoặc service GCP tương đương) cập nhật cache policy cục bộ
Điều này lan sang:
- Load balancers
- Triển khai theo region
- Các bản sao serviceVí dụ: GKE control plane sao chép IAM policies tới tất cả replica trong cluster. Cần thời gian để đạt đồng thuận.
Lớp 3: Data Plane Services (5-60 giây)
T+5 đến T+60s:
Compute Engine, Cloud Storage, BigQuery, v.v.
đồng bộ thay đổi policy từ master
Một số service có cache cục bộ:
- Compute Engine cache ở mức node
- Cloud Storage cache ở mức regionTrường hợp xấu nhất:
- User nhận 403 Forbidden (permission denied) khi đáng lẽ phải có quyền
- Không có log lỗi rõ ràng — chỉ là permission denied
- Rất khó debug
Độ trễ lan truyền thực tế
Kịch bản 1: Kiểm soát truy cập user
bash
# T+0: Cấp role Editor
gcloud projects add-iam-policy-binding my-project \
--member=user:alice@company.com \
--role=roles/editor
# T+0 đến T+10s: Alice thử truy cập Cloud Console
# Kết quả: "You don't have permissions to access this project"
# T+15s: Alice refresh trình duyệt
# Kết quả: Truy cập được (policy đã lan truyền)Tại sao? Cloud Console cache IAM policies ở cả trình duyệt và backend. Cả hai cache đều cần cập nhật.
Kịch bản 2: Service account impersonation
python
# T+0: Tạo service account + cấp role
sa = create_service_account("app-sa")
grant_role(sa_email, "roles/compute.admin")
# T+0 đến T+5s: Thử tạo VM bằng service account
gcloud compute instances create test-vm \
--service-account=$SA_EMAIL
# Có thể thất bại: Service account chưa có compute.instances.create
# T+10s: Thử lại
# Thành công: Role đã lan truyềnKịch bản 3: Deny policies
Deny policies có thời gian lan truyền dài hơn (tối đa 60 giây):
bash
# T+0: Tạo deny policy (explicit deny)
gcloud iam deny-policies create deny-sa-iam-binding \
--location=organizations/ORG_ID \
--rules='deny {permissions: ["iam.serviceAccounts.actAs"]; principals: ["principalSet://goog/public:all"]}'
# T+0 đến T+60s: Deny policy lan truyền
# Enforcement có thể không nhất quán trong khoảng nàyHành vi cache
Client-side caching
Google Cloud SDKs cache thông tin policy:
python
from google.cloud import iam_admin_v1
from functools import lru_cache
# Mặc định: SDK cache 5 phút
policy = iam_admin_client.get_iam_policy(resource)
# ❌ Vấn đề: Cache cũ
time.sleep(2) # User vừa được cấp role mới
policy = iam_admin_client.get_iam_policy(resource) # Vẫn thấy policy cũ
# ✅ Giải pháp: Tắt cache
client = iam_admin_v1.IAMClient()
client.api = iam_admin_v1.services.iam.transports.IAMTransport(
# Tắt cache
cache_policy=None
)Cache ở cấp service
Các service GCP khác nhau cache policy khác nhau:
| Service | TTL cache | Thời gian lan truyền |
|---|---|---|
| Cloud IAM (API) | Ngay lập tức | 1-5 giây |
| Cloud Console UI | 5 phút | 10-30 giây |
| Compute Engine | 10 phút mỗi node | 5-30 giây mỗi region |
| Cloud Storage | 5 phút | 10-60 giây |
| BigQuery | 15 phút | 5-30 giây |
| GKE | Tùy biến | 5-30 giây |
Kiểm thử IAM propagation
Test 1: Kiểm tra quyền
python
def test_iam_propagation(resource_name, member, role):
"""Kiểm tra thay đổi IAM policy đã lan truyền hay chưa"""
import time
# Cấp role
policy = get_iam_policy(resource_name)
policy.bindings.append({
"role": role,
"members": [member]
})
set_iam_policy(resource_name, policy)
# Poll cho tới khi quyền hiển thị
max_retries = 30
for attempt in range(max_retries):
try:
# Thử thao tác cần role đó
result = test_permission(resource_name, member, role)
if result:
print(f"✓ Role đã lan truyền sau {attempt} giây")
return True
except Exception as e:
if attempt == max_retries - 1:
print(f"✗ Role chưa lan truyền sau {attempt} giây")
raise
time.sleep(1)
return False
def test_permission(resource_name, member, role):
"""Xác minh member thật sự có role thông qua testIamPermissions"""
# testIamPermissions là kiểm tra theo permission cụ thể
# Lấy permissions được cấp bởi role
role_permissions = get_permissions_for_role(role)
# Kiểm tra member có thể thực hiện các permission đó không
can_perform = client.test_iam_permissions(
resource=resource_name,
permissions=role_permissions,
identity=member # Trong API thật, kiểm tra thông qua service account
)
return len(can_perform) > 0Test 2: Kiểm thử service account
bash
#!/bin/bash
# test-iam-propagation.sh
PROJECT_ID=$1
SA_EMAIL=$2
TIMEOUT=60
# Cấp role cho service account
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member=serviceAccount:$SA_EMAIL \
--role=roles/compute.admin
# Poll cho đến khi service account có thể dùng role
end_time=$(($(date +%s) + TIMEOUT))
while [ $(date +%s) -lt $end_time ]; do
# Tạo credentials của service account (local cho testing)
gcloud iam service-accounts keys create /tmp/key.json \
--iam-account=$SA_EMAIL
# Thử thao tác cần role
if gcloud compute instances list \
--project=$PROJECT_ID \
--account=$SA_EMAIL \
--key-file=/tmp/key.json 2>/dev/null; then
echo "✓ IAM propagated successfully"
rm /tmp/key.json
exit 0
fi
sleep 2
done
echo "✗ IAM not propagated within $TIMEOUT seconds"
exit 1Test 3: Lan truyền giữa các service
python
def test_cross_service_propagation():
"""Kiểm tra Compute Engine có thấy thay đổi IAM hay không"""
import time
sa_email = create_service_account("test-sa")
# Cấp role Compute Instance Admin
grant_role(sa_email, "roles/compute.instanceAdmin.v1")
# Chờ propagation
time.sleep(5)
# Kiểm tra: service account có thể tạo VM không?
try:
credentials = impersonate_service_account(sa_email)
compute_client = compute_v1.InstancesClient(credentials=credentials)
# Thử tạo VM
operation = compute_client.insert(
project=PROJECT_ID,
zone="us-central1-a",
body={"name": "test-vm", "machineType": "..."}
)
print("✓ Service account có thể tạo VM (IAM đã lan truyền)")
return True
except Exception as e:
if "permission denied" in str(e):
print(f"✗ IAM chưa lan truyền: {e}")
return False
raiseXử lý propagation trong production
Mẫu 1: Retry loop với exponential backoff
python
import time
from google.api_core import retry
# Decorator xử lý retry tự động
@retry.Retry(
initial=1, # Bắt đầu 1 giây
maximum=10, # Tối đa 10 giây
multiplier=2, # Tăng gấp đôi mỗi lần
deadline=60 # Timeout tổng: 60 giây
)
def use_service_account(sa_email):
"""Dùng service account (có thể thất bại lúc đầu nếu IAM chưa lan truyền)"""
try:
# Thử thao tác
create_resource_with_sa(sa_email)
return True
except google.api_core.exceptions.PermissionDenied:
# Retry nếu bị permission denied (nhiều khả năng là propagation)
raise
# Cách dùng:
use_service_account("app-sa@project.iam.gserviceaccount.com")Mẫu 2: Idempotent operations
python
def create_vm_idempotent(instance_name, sa_email):
"""Tạo VM, xử lý IAM propagation một cách an toàn"""
import time
for attempt in range(5):
try:
# Kiểm tra VM đã tồn tại chưa
try:
instance = get_instance(instance_name)
print(f"✓ VM đã tồn tại")
return instance
except NotFound:
pass
# Tạo VM (có thể thất bại nếu IAM chưa lan truyền)
instance = create_vm(instance_name, service_account=sa_email)
print(f"✓ Đã tạo VM sau {attempt} lần thử")
return instance
except PermissionDenied as e:
if attempt < 4:
wait_time = 2 ** attempt # exponential backoff
print(f"! Bị từ chối quyền, thử lại sau {wait_time}s...")
time.sleep(wait_time)
else:
raise
create_vm_idempotent("app-vm", sa_email)Mẫu 3: Pre-warming services
python
def setup_project_with_service_account(project_id, sa_email):
"""Thiết lập project, pre-warm services để đảm bảo IAM đã lan truyền"""
# Tạo service account
sa = create_service_account(project_id, sa_email)
# Cấp các role cần thiết
grant_role(project_id, sa_email, "roles/compute.admin")
grant_role(project_id, sa_email, "roles/storage.admin")
# Pre-warm: gọi thử API bằng service account
# Điều này buộc các service tải và cache IAM policies
print("Pre-warming Compute Engine...")
try:
compute_client = compute_v1.InstancesClient(
credentials=impersonate_service_account(sa_email)
)
compute_client.list(project=project_id, zone="us-central1-a")
except Exception:
pass # Có thể thất bại nếu chưa có resource, nhưng vẫn làm nóng cache
print("Pre-warming Cloud Storage...")
try:
storage_client = storage.Client(
project=project_id,
credentials=impersonate_service_account(sa_email)
)
list(storage_client.list_buckets())
except Exception:
pass
# Chờ cache ổn định
time.sleep(5)
print("✓ Project đã được pre-warm, sẵn sàng vận hành")Giám sát vấn đề propagation IAM
Phát hiện qua audit logs
bash
# Truy vấn audit logs cho thay đổi IAM
gcloud logging read \
'severity=WARNING AND
resource.type="service_account" AND
protoPayload.methodName=~"SetIamPolicy"' \
--limit=10 \
--format=json
# Theo dõi lỗi permission denied
gcloud logging read \
'severity=ERROR AND
httpRequest.status="403"' \
--limit=20Triển khai monitoring tùy chỉnh
python
from prometheus_client import Gauge
import time
iam_propagation_delay = Gauge(
'iam_propagation_delay_seconds',
'Thời gian để IAM policy lan truyền hoàn toàn'
)
def measure_iam_propagation(resource, member, role):
"""Đo thời gian lan truyền thực tế"""
start_time = time.time()
grant_role(resource, member, role)
# Poll cho đến khi thấy được
while True:
elapsed = time.time() - start_time
if can_member_perform_action(resource, member, role):
iam_propagation_delay.observe(elapsed)
print(f"IAM propagation mất {elapsed:.1f}s")
break
if elapsed > 60:
print("⚠️ IAM propagation > 60s (có thể có vấn đề)")
break
time.sleep(1)Các kiểu lỗi thường gặp
| Mẫu lỗi | Triệu chứng | Cách khắc phục |
|---|---|---|
| Không có retry logic | API call đầu tiên thất bại ngay | Thêm retry exponential backoff |
| Giả định tức thì | Race condition trong test | Thêm delay 5-10s hoặc retry loop |
| Liên service | Service A thấy role, B không thấy | Chờ lâu hơn, pre-warm services |
| Client caching | Policy cũ vẫn hiển thị | Xóa cache client hoặc tạo client mới |
| Deny policies | Mất 60s để lan truyền | Tăng delay khi đổi deny policy |