Skip to content

Custom Constraints & CEL Expressions

Tại sao cần custom constraints?

Managed constraints của Google bao gồm những ràng buộc phổ biến nhất — nhưng mỗi tổ chức có chính sách riêng không phải Google có thể dự đoán trước. Một số ví dụ về chính sách tổ chức không có managed constraint tương ứng:

  • "Tất cả GKE node pools phải tắt auto-upgrade trong folder Production" (để team control upgrade window)
  • "Cloud SQL instances trong project tài chính phải bật point-in-time recovery"
  • "Compute Engine instances phải có label cost-centerenvironment"
  • "BigQuery datasets không được có defaultTableExpirationMs quá 90 ngày"

Custom constraints cho phép tổ chức định nghĩa bất kỳ ràng buộc nào có thể được expressed thông qua CEL (Common Expression Language) trên resource configuration.

Internal Model: Custom Constraint Lifecycle

Custom constraint hoạt động theo ba bước riêng biệt:

Bước 1: Định nghĩa constraint (YAML file + register với org)

Bước 2: Tạo Organization Policy enforce constraint đó

Bước 3: Apply org policy vào resource hierarchy

Ba bước này tách biệt có chủ ý: bạn có thể định nghĩa hàng chục constraints nhưng chỉ enforce một số tại các levels khác nhau.

Enforcement point: Khi nào constraint được check?

Custom constraints được enforce tại GCP resource management layer khi:

  • Một API call CREATE resource mới
  • Một API call UPDATE resource hiện tại (nếu constraint support UPDATE)

Khi request đến GCP, sequence evaluation là:

API Request (CREATE/UPDATE)

    ├── [IAM Authorization check]

    └── [Organization Policy check] ← Đây là điểm enforcement
            ├── Evaluate managed constraints
            ├── Evaluate custom constraints (CEL expression)
            └── Nếu vi phạm → HTTP 403 PERMISSION_DENIED
                Nếu pass → Resource được tạo/cập nhật

Cú Pháp YAML của Custom Constraint

yaml
# custom-constraint.yaml
name: organizations/ORGANIZATION_ID/customConstraints/CONSTRAINT_NAME
#     └── Tên phải bắt đầu bằng "custom."
#         Ví dụ: custom.disableGkeAutoUpgrade

# Resource types mà constraint áp dụng (fully qualified GCP resource type)
resourceTypes:
  - container.googleapis.com/NodePool
  # Có thể list nhiều resource types nếu constraint áp dụng cho nhiều loại

# Method types: CREATE và/hoặc UPDATE
methodTypes:
  - CREATE
  - UPDATE   # Optional — không phải tất cả resources hỗ trợ UPDATE enforcement

# CEL expression — điều kiện phải đúng để ALLOW, sai để DENY (tùy actionType)
condition: "resource.management.autoUpgrade == false"

# Action type: ALLOW hoặc DENY
# ALLOW: resource được tạo/update CHỈ KHI condition là true
# DENY: resource bị block KHI condition là true
actionType: ALLOW

# Metadata cho UI
displayName: "Disable GKE Auto-Upgrade on Node Pools"
description: "Requires that GKE node pools have auto-upgrade disabled."

actionType: ALLOW vs DENY

Sự khác biệt giữa ALLOW và DENY:

ALLOW: Condition phải evaluate thành true để operation được phép.

yaml
condition: "resource.management.autoUpgrade == false"
actionType: ALLOW
# Nghĩa là: Chỉ được tạo NodePool khi autoUpgrade == false

DENY: Condition evaluate thành true sẽ block operation.

yaml
condition: "resource.management.autoUpgrade == true"
actionType: DENY
# Nghĩa là: Block tạo NodePool khi autoUpgrade == true

Cả hai về mặt logic đều tương đương, nhưng cú pháp khác nhau có thể ảnh hưởng đến readability. Prefer DENY khi điều kiện cần block là rõ ràng hơn; prefer ALLOW khi điều kiện "đúng" dễ express hơn.

CEL Expressions Cho Custom Constraints

Namespace của resource trong CEL

Trong CEL expression của custom constraint, resource đại diện cho resource được tạo/update. Cấu trúc của resource phụ thuộc vào resource type — bạn cần tham chiếu GCP REST API documentation cho resource type đó để biết cấu trúc fields.

Ví dụ cho container.googleapis.com/NodePool, resource có structure như NodePool REST resource:

resource.name                         → Tên node pool
resource.management.autoUpgrade       → Auto-upgrade setting
resource.config.machineType           → Machine type của node
resource.config.shieldedInstanceConfig.enableSecureBoot → Shielded boot
resource.config.preemptible           → Có dùng preemptible nodes?
resource.autoscaling.enabled          → Autoscaling enabled?
resource.autoscaling.minNodeCount     → Min node count

CEL data types và operations

Integer:

cel
resource.autoscaling.minNodeCount > 0
resource.autoscaling.maxNodeCount <= 100
resource.autoscaling.minNodeCount != resource.autoscaling.maxNodeCount

String:

cel
# Exact match
resource.config.machineType == "e2-standard-4"

# Starts with
resource.config.machineType.startsWith("n2-")

# Contains
resource.name.contains("production")

# Regex match
resource.config.machineType.matches("n[12]-standard-.*")

# Multiple conditions
resource.config.machineType.startsWith("n2-") || resource.config.machineType.startsWith("c2-")

Boolean:

cel
resource.management.autoUpgrade == false
resource.config.shieldedInstanceConfig.enableSecureBoot == true

List operations:

cel
# Kiểm tra list size
resource.config.taints.size() > 0

# Kiểm tra element tồn tại trong list với exists()
resource.config.labels.exists(label, label.key == "cost-center")

# Kiểm tra tất cả elements thỏa điều kiện với all()
resource.config.nodePools.all(pool, pool.management.autoUpgrade == false)

Map operations:

cel
# Kiểm tra key tồn tại trong map
has(resource.config.labels) && has(resource.config.labels.cost_center)

# Truy cập map value
resource.config.labels.environment == "production"

Defensive CEL — Tránh Runtime Errors

Một constraint CEL compile thành công nhưng có thể fail runtime nếu truy cập field không tồn tại:

cel
# NGUY HIỂM: Nếu config.labels không tồn tại, expression sẽ fail
resource.config.labels.cost_center == "engineering"

# AN TOÀN: Kiểm tra sự tồn tại trước
has(resource.config.labels) && has(resource.config.labels.cost_center) &&
resource.config.labels.cost_center == "engineering"

# AN TOÀN: Dùng kết hợp với OR để không block khi field thiếu
!has(resource.config.labels) || !has(resource.config.labels.cost_center) ||
resource.config.labels.cost_center == "engineering"

Khi một constraint fail runtime (thay vì evaluate false), GCP sẽ block operation với error BAD_CONDITION — về behavior, điều này giống như constraint bị violated. Đây là nguyên nhân phổ biến của unexpected blocks sau khi deploy custom constraint.

Quy tắc vàng: Trước khi access list index hay map key, luôn kiểm tra sự tồn tại bằng has() hoặc .size() > N.

Ví Dụ Custom Constraints Thực Tế

Constraint 1: Bắt buộc labels trên Compute Engine VMs

yaml
name: organizations/123456789/customConstraints/custom.requireVmLabels
resourceTypes:
  - compute.googleapis.com/Instance
methodTypes:
  - CREATE
  - UPDATE
condition: >
  has(resource.labels) &&
  has(resource.labels.cost_center) &&
  has(resource.labels.environment) &&
  ["production", "staging", "development"].exists(e, e == resource.labels.environment)
actionType: ALLOW
displayName: "Require cost_center and environment labels on VMs"
description: >
  All Compute Engine instances must have 'cost_center' and 'environment' labels.
  Valid environment values: production, staging, development.

Constraint 2: GKE node pools phải dùng machine types được approve

yaml
name: organizations/123456789/customConstraints/custom.allowedGkeMachineTypes
resourceTypes:
  - container.googleapis.com/NodePool
methodTypes:
  - CREATE
condition: >
  resource.config.machineType.startsWith("e2-") ||
  resource.config.machineType.startsWith("n2-") ||
  resource.config.machineType.startsWith("c2-")
actionType: ALLOW
displayName: "Restrict GKE node pool machine types to approved families"
description: >
  GKE node pools must use machine types from the e2, n2, or c2 families.
  Prevents use of n1 or other legacy machine types.

Constraint 3: Cloud SQL phải bật deletion protection

yaml
name: organizations/123456789/customConstraints/custom.requireCloudSqlDeletionProtection
resourceTypes:
  - sqladmin.googleapis.com/Instance
methodTypes:
  - CREATE
  - UPDATE
condition: "resource.settings.deletionProtectionEnabled == true"
actionType: ALLOW
displayName: "Require deletion protection on Cloud SQL instances"
description: >
  All Cloud SQL instances must have deletion protection enabled
  to prevent accidental deletion of production databases.

Constraint 4: BigQuery datasets phải có table expiration ≤ 90 ngày

yaml
name: organizations/123456789/customConstraints/custom.bigqueryTableExpiration
resourceTypes:
  - bigquery.googleapis.com/Dataset
methodTypes:
  - CREATE
  - UPDATE
# Điều kiện: Phải có defaultTableExpirationMs, và không quá 90 ngày (7776000000 ms)
# Nếu không set defaultTableExpirationMs (0 = unlimited), cũng block
condition: >
  has(resource.defaultTableExpirationMs) &&
  resource.defaultTableExpirationMs > 0 &&
  resource.defaultTableExpirationMs <= 7776000000
actionType: ALLOW
displayName: "Require BigQuery table expiration ≤ 90 days"
description: >
  BigQuery datasets must have a default table expiration of 90 days or less
  to prevent unbounded storage growth. 7776000000 ms = 90 days.

Workflow Deploy Custom Constraint

bash
# Bước 1: Register custom constraint với organization
gcloud org-policies set-custom-constraint custom-constraint.yaml

# Bước 2: Verify constraint được register
gcloud org-policies list-custom-constraints \
  --organization=ORGANIZATION_ID

# Bước 3: Tạo org policy sử dụng custom constraint
cat > org-policy.yaml << 'EOF'
name: projects/my-project/policies/custom.disableGkeAutoUpgrade
spec:
  rules:
    - enforce: true
EOF

gcloud org-policies set-policy org-policy.yaml

# Bước 4: Verify policy được apply
gcloud org-policies describe custom.disableGkeAutoUpgrade \
  --project=my-project

# Bước 5: Test với dry-run (tạo resource vi phạm policy)
gcloud container node-pools create test-pool \
  --cluster=my-cluster \
  --location=us-central1 \
  --machine-type=e2-medium \
  --enable-autoupgrade  # ← Sẽ bị block bởi constraint

Giới Hạn của Custom Constraints

Limit 20 constraints per resource type: Nếu cần hơn 20 constraints, cần refactor hoặc combine nhiều conditions vào một constraint.

Chỉ CREATE và UPDATE: Không thể tạo constraint cho DELETE operations — không thể ngăn xóa resource thông qua custom constraint.

Không có access đến existing state: CEL expression chỉ có thể access properties của resource được tạo/update trong request hiện tại. Không thể query "giá trị hiện tại của field này là gì" hay cross-reference với các resources khác.

Không có side effects: Custom constraint không thể trigger notifications, emit logs riêng (chỉ standard org policy violation logs), hay gọi external services.

Eventual consistency trong List/Describe: Sau khi register custom constraint hoặc apply org policy, có thể mất vài phút trước khi nó được enforce trên toàn bộ infrastructure.

Không áp dụng retroactively: Giống managed constraints, custom constraints chỉ apply cho new CREATE/UPDATE operations — resources vi phạm đang tồn tại không bị ảnh hưởng.

Custom Constraint vs. ValidatingAdmissionPolicy trong GKE

Có một sự trùng lặp về use case giữa Org Policy custom constraints và Kubernetes ValidatingAdmissionPolicy (VAP):

Khía cạnhOrg Policy Custom ConstraintGKE ValidatingAdmissionPolicy
Enforcement pointGCP API layer (cluster creation/config)Kubernetes API server (pod/workload config)
CEL expression typeGCP resource REST modelKubernetes resource spec
ScopeRàng buộc GKE cluster và node pool configurationRàng buộc Kubernetes workloads (Pods, Deployments, ...)
Ví dụNode pool phải dùng Shielded VMsPod không được chạy với root user

Không phải either/or — cả hai cần thiết cho defense in depth: Org Policy bảo vệ infrastructure configuration, VAP bảo vệ workload configuration.

References