IAM Conditions & CEL Expressions — Attribute-Based Access Control trong GCP
Tại sao IAM Conditions là bước tiến quan trọng
IAM Conditions giải quyết một giới hạn cơ bản của role bindings thông thường: bindings không có condition là all-or-nothing — principal hoặc có role, hoặc không có. Không có cách nào nói "user này có quyền đọc chỉ trong giờ hành chính" hay "service account này chỉ được access resources thuộc environment production" bằng standard bindings.
Conditions thêm vào một lớp attribute-based access control (ABAC) trên nền tảng role-based access control (RBAC) của IAM. Thay vì chỉ hỏi "principal này có role này không", Conditions cho phép hỏi thêm "và context của request này có đáp ứng điều kiện không?"
Common Expression Language (CEL): Cơ chế evaluation
IAM Conditions dùng Common Expression Language (CEL) — một ngôn ngữ expression đơn giản, strongly-typed, được Google thiết kế cho policy evaluation. CEL không phải Turing-complete (không có loops, không có side effects), điều này là thiết kế có chủ đích: expressions phải terminate và không có access đến external state — chỉ evaluate dựa trên các attributes được cung cấp tại thời điểm request.
Cấu trúc condition trong binding
{
"role": "roles/storage.objectViewer",
"members": ["serviceAccount:my-sa@project.iam.gserviceaccount.com"],
"condition": {
"title": "Chi doc trong gio hanh chinh",
"description": "Cho phep doc chi trong 8h-17h theo gio Viet Nam",
"expression": "request.time.getHours('Asia/Ho_Chi_Minh') >= 8 && request.time.getHours('Asia/Ho_Chi_Minh') < 17"
}
}Condition bao gồm ba fields:
title: Identifier ngắn gọn (required)description: Mô tả (optional nhưng recommended)expression: CEL expression trả về boolean (required)
Evaluation semantics
Khi một binding có condition:
- Nếu condition evaluates to true → binding có hiệu lực (principal có role)
- Nếu condition evaluates to false → binding không có hiệu lực (như không có binding)
- Nếu condition không thể evaluate (attribute không available) → binding không có hiệu lực
Điểm cuối cùng quan trọng: nếu bạn viết condition dựa trên resource.name nhưng resource type không support resource.name attribute, condition không thể evaluate → binding bị bỏ qua, không phải "auto-allow".
Với Deny Policy, semantics ngược lại: nếu condition không thể evaluate → deny rule có hiệu lực (deny-on-unclear là secure default cho deny policies).
Ba nhóm condition attributes
1. Time-based attributes
Dựa trên timestamp của request. Đây là loại condition đơn giản nhất và supported rộng rãi nhất.
request.time là timestamp (loại Timestamp trong CEL) của khi request được thực hiện.
# Tạm thời grant access — hết hạn vào một thời điểm cụ thể
request.time < timestamp('2026-12-31T23:59:59Z')
# Giờ hành chính (8h-17h theo múi giờ Việt Nam)
request.time.getHours('Asia/Ho_Chi_Minh') >= 8
&& request.time.getHours('Asia/Ho_Chi_Minh') < 17
# Ngày trong tuần (0=Chủ nhật, 6=Thứ Bảy) — chỉ weekdays
request.time.getDayOfWeek('Asia/Ho_Chi_Minh') >= 1
&& request.time.getDayOfWeek('Asia/Ho_Chi_Minh') <= 5
# Chỉ trong tháng đầu năm (January-June)
request.time.getMonth('Asia/Ho_Chi_Minh') < 6Use case thực tế cho time-based conditions:
Temporary elevated access (break-glass): Thay vì grant permanent admin access khi có incident, tạo time-limited binding:
{
"role": "roles/compute.admin",
"members": ["user:oncall-engineer@example.com"],
"condition": {
"title": "Temp access incident 2026-06",
"expression": "request.time < timestamp('2026-06-24T08:00:00Z')"
}
}Binding tự động "hết hạn" sau mốc thời gian — không cần nhớ revoke thủ công.
2. Resource-based attributes
Dựa trên thuộc tính của resource mà request đang nhắm đến.
resource.type: Loại resource dưới dạng service.googleapis.com/ResourceType
# Chỉ áp dụng cho GCS buckets
resource.type == 'storage.googleapis.com/Bucket'
# Chỉ áp dụng cho Compute Engine instances
resource.type == 'compute.googleapis.com/Instance'resource.name: Full resource name
# Chỉ access bucket trong project cụ thể
resource.name.startsWith('projects/_/buckets/prod-')
# Chỉ access instances trong zone cụ thể
resource.name.startsWith('projects/my-project/zones/us-central1-a/')resource.service: GCP service name
# Chỉ áp dụng cho Cloud Storage service
resource.service == 'storage.googleapis.com'resource.matchTag(): Dựa trên resource tags (không phải labels — labels không được dùng trong conditions)
# Chỉ access resources có tag env=production
resource.matchTag('123456789012/env', 'production')
# Tổ hợp nhiều tags
resource.matchTag('123456789012/env', 'production')
&& resource.matchTag('123456789012/data-class', 'confidential')Resource tags khác với labels: tags là IAM-aware objects trong Resource Manager, có namespace riêng (key-value pairs với định danh dưới dạng ORG_ID/TAG_KEY). Tags phải được tạo trước thông qua gcloud resource-manager tags.
3. Request-based attributes (IAP-specific)
Một số attributes chỉ available khi request đi qua Identity-Aware Proxy (IAP):
# Access levels (dành cho BeyondCorp Enterprise / IAP)
'accessPolicies/POLICY_ID/accessLevels/CorpNetwork'
in request.auth.access_levels
# Destination IP (IAP TCP tunneling)
destination.ip == '10.0.0.5'
# URL path (IAP HTTP proxy)
request.path.startsWith('/admin/')Những attributes này không available cho direct API calls không qua IAP, vì vậy rất ít dùng trong general IAM policy design.
Tại sao CEL được chọn thay vì ngôn ngữ khác
CEL được chọn vì các tính chất quan trọng cho policy evaluation:
Deterministic và finite: Không có loops vô hạn, mọi expression luôn terminate. Critical cho authorization systems.
No side effects: CEL expression không thể gọi external APIs, không modify state. Authorization check là pure function.
Type-safe: CEL có type system nghiêm ngặt, bắt lỗi tại compile time thay vì runtime.
Fast evaluation: CEL expressions được compile thành bytecode, evaluation rất nhanh — phù hợp cho latency-sensitive authorization.
Portable: CEL được dùng ở nhiều nơi khác trong GCP (Organization Policies custom constraints, Kubernetes ValidatingAdmissionPolicy, Cloud Armor rule expressions, Firestore rules), tạo ra consistent mental model.
Limitations quan trọng cần biết
1. Không hỗ trợ basic roles
IAM Conditions không thể được dùng với basic roles (Owner, Editor, Viewer):
// INVALID — sẽ bị reject
{
"role": "roles/editor",
"members": ["user:alice@example.com"],
"condition": {
"title": "Chỉ giờ hành chính",
"expression": "request.time.getHours('Asia/Ho_Chi_Minh') >= 8"
}
}Lý do thiết kế: Basic roles không được dùng trong production environments, và không hỗ trợ conditions là một cách khuyến khích chuyển sang predefined/custom roles.
2. Không hỗ trợ allUsers / allAuthenticatedUsers
Không thể thêm conditions cho bindings với allUsers hay allAuthenticatedUsers.
3. Giới hạn 100 conditional bindings per policy
Mỗi allow policy có tối đa 100 conditional role bindings. Bindings không có condition không tính vào giới hạn này. Nếu vượt quá, phải consolidate conditions hoặc dùng approach khác.
4. Resource type không phải lúc nào cũng có đủ attributes
Không phải mọi resource type đều expose đủ attributes cho condition evaluation. Trước khi design condition phức tạp, verify resource type đó support các attributes bạn cần.
Ví dụ: resource.name có thể không available cho một số resource types, đặc biệt là các resource types legacy. Cloud Storage, Compute Engine, BigQuery, Pub/Sub là những services support tốt nhất.
5. Conditions không hỗ trợ label-based matching
Một misconception phổ biến: muốn grant access dựa trên label (ví dụ environment=prod). IAM Conditions không support resource labels — chỉ support resource tags (một loại khác, xem Resource Tags trong resource-manager).
Labels là metadata đơn thuần, không có IAM integration. Tags là IAM-aware objects với namespace có thể dùng trong conditions.
Anti-patterns phổ biến với IAM Conditions
Anti-pattern 1: Condition quá phức tạp, khó maintain
// Khó maintain, khó debug khi có vấn đề
(request.time.getHours('Asia/Ho_Chi_Minh') >= 8
&& request.time.getHours('Asia/Ho_Chi_Minh') < 17
&& request.time.getDayOfWeek('Asia/Ho_Chi_Minh') >= 1
&& request.time.getDayOfWeek('Asia/Ho_Chi_Minh') <= 5)
|| (request.time.getHours('Asia/Ho_Chi_Minh') >= 9
&& request.time.getHours('Asia/Ho_Chi_Minh') < 12
&& request.time.getDayOfWeek('Asia/Ho_Chi_Minh') == 6)Đúng hơn: Chia thành nhiều bindings với conditions đơn giản hơn, hoặc xem xét VPC Service Controls access levels cho phức tạp access control.
Anti-pattern 2: Dùng conditions như security boundary duy nhất
Conditions là enforcement mechanism, không phải security boundary. Nếu principal bị compromise (credentials bị lấy), attacker có thể craft request với attributes hợp lệ (ví dụ request trong giờ hành chính, với resource name hợp lệ) để bypass time/resource conditions.
Conditions tốt nhất khi kết hợp với principle of least privilege thực sự — không grant roles/bigquery.admin rồi thêm conditions, mà grant role hẹp nhất phù hợp và thêm conditions để thu hẹp thêm.
Anti-pattern 3: Nhầm lẫn condition không evaluate với deny
Khi condition không thể evaluate (attribute not available), binding bị bỏ qua — không phải "deny". Điều này có nghĩa là trong một số edge cases, principal có thể mất access không mong muốn khi condition không evaluate, chứ không phải bị deny explicitly.
Nếu có:
Binding A: Principal P có Role R1, không có condition
Binding B: Principal P có Role R2, có condition → không evaluate được
Kết quả:
Principal P chỉ có Role R1 (Binding A có hiệu lực)
Role R2 từ Binding B không áp dụngConditions trong Deny Policies: Semantics ngược lại
Khi dùng conditions trong deny policies, semantics khác với allow policies:
| Trường hợp | Allow Policy | Deny Policy |
|---|---|---|
| Condition = true | Binding có hiệu lực (grant) | Deny rule có hiệu lực (block) |
| Condition = false | Binding không có hiệu lực | Deny rule không áp dụng |
| Condition không evaluate | Binding không có hiệu lực | Deny rule có hiệu lực (block) |
Deny policies chọn "fail secure": khi không chắc chắn, deny. Allow policies chọn "graceful degradation": khi không chắc chắn, không grant.
GCP-native implementation: Testing conditions
Một điểm quan trọng trong operations: làm thế nào verify condition hoạt động đúng trước khi áp dụng production?
# Dry-run: test condition với giả lập request attributes
gcloud iam simulate-policy-binding \
--request-resource="//storage.googleapis.com/projects/_/buckets/my-bucket" \
--principal="user:alice@example.com" \
--permission="storage.objects.get" \
--format=json
# Nếu có time-based condition, request time mặc định là current time
# Để test với time khác, dùng Policy Troubleshooter trong Console
# với custom timestampVới resource tag conditions, cần tạo tag trước:
# Tạo tag key
gcloud resource-manager tags keys create env \
--parent=organizations/ORG_ID
# Tạo tag value
gcloud resource-manager tags values create production \
--parent=tagKeys/TAG_KEY_ID
# Gán tag cho resource
gcloud resource-manager tags bindings create \
--tag-value=tagValues/TAG_VALUE_ID \
--parent="//storage.googleapis.com/projects/_/buckets/my-bucket" \
--location=global