Skip to content

IAM Deny Policies — Deny Before Allow, Cơ Chế và Deniable Permissions

Tại sao IAM Deny Policies tồn tại

Trong model IAM truyền thống (chỉ có allow policies), không có cách nào block một permission ở level thấp hơn nếu permission đó đã được grant ở level cao hơn trong hierarchy. Inheritance của allow policies chỉ đi một chiều: grant trickle down, không thể undone ở level con.

Điều này tạo ra một số vấn đề thực tế:

  1. Không thể exception hóa: Cả team có role X, nhưng một người trong team không được phép làm action Y. Với allow-only, phải redesign toàn bộ role structure.

  2. Không thể enforce guardrails: Org-level policy "không ai được xóa production database" cần cơ chế khác ngoài "đừng grant delete permission" — vì có người cần delete permission cho staging, hoặc có inherited Editor role.

  3. Separation of duties: Với IAM audit requirement, cần chứng minh một số actions không thể thực hiện bởi certain principals, kể cả khi họ có broad roles.

IAM Deny Policies (GA từ 2023) giải quyết những vấn đề này bằng cách thêm một lớp explicit, unconditional block trước khi allow policies được evaluated.


Internal model: Deny-before-allow evaluation

Nguyên tắc cơ bản được document chính thức:

"IAM always checks relevant deny policies before checking relevant allow policies."Google Cloud documentation

Điều này có nghĩa:

  1. Deny là absolute: Nếu một deny rule match, permission bị block — bất kể allow policy có grant permission đó hay không.
  2. Không có "allow override deny": Không có concept "allow wins over deny" trong cùng level. Muốn un-deny một principal cụ thể, phải dùng exceptionPrincipals field.
  3. Evaluation order rõ ràng: PAB → Deny → Allow (như đã phân tích ở Chapter 31 - File 01)

Cấu trúc của một Deny Policy

Deny Policy là object riêng biệt (không phải binding trong allow policy), được manage qua IAM v2 API:

json
{
  "name": "policies/cloudresourcemanager.googleapis.com%2Fprojects%2Fmy-project/denypolicies/restrict-delete",
  "displayName": "Restrict deletion permissions",
  "rules": [
    {
      "denyRule": {
        "deniedPrincipals": [
          "principalSet://iam.googleapis.com/projects/123456789/roles/roles%2Feditor"
        ],
        "exceptionPrincipals": [
          "user:dba-admin@example.com"
        ],
        "deniedPermissions": [
          "storage.googleapis.com/buckets.delete",
          "storage.googleapis.com/objects.delete"
        ],
        "condition": {
          "title": "Chỉ block môi trường production",
          "expression": "resource.matchTag('123456789012/env', 'production')"
        }
      }
    }
  ]
}

Mỗi deny rule bao gồm:

  • deniedPrincipals: Principals bị block
  • exceptionPrincipals (optional): Principals được miễn trừ khỏi deny rule này
  • deniedPermissions: Permissions bị block (có thể dùng wildcard)
  • condition (optional): CEL expression điều kiện

Attachment points

Deny policies có thể gắn vào:

  • Organizations
  • Folders
  • Projects

Không thể gắn trực tiếp vào individual resources (buckets, instances...). Tuy nhiên, deny policies kế thừa xuống — deny policy ở org level áp dụng cho mọi resource trong org.

Giới hạn quan trọng: Tối đa 500 deny policies per organization, folder, hoặc project.


Deniable Permissions: Không phải permission nào cũng có thể deny

Đây là một constraint quan trọng thường bị bỏ qua: không phải tất cả IAM permissions đều deniable. GCP maintain một danh sách explicit về những permissions có thể bị deny.

Permissions deniable theo pattern:

  • Hầu hết permissions của major services (Storage, Compute, BigQuery, Pub/Sub, IAM...)
  • Format FQDN: service.googleapis.com/resource.action

Permissions không deniable (ví dụ):

  • Một số IAM bootstrapping permissions
  • Resource Manager internal operations
  • Billing management operations

Để kiểm tra một permission có deniable không:

bash
# List deniable permissions cho một service
gcloud iam list-deniable-permissions \
  --filter="name:storage.googleapis.com/*"

# Hoặc check documentation tại:
# https://cloud.google.com/iam/docs/supported-deny-permissions

Wildcard permission groups trong Deny Policies

Deny policies hỗ trợ wildcard pattern để deny groups of permissions:

storage.googleapis.com/*.*       → Tất cả permissions của Cloud Storage
compute.googleapis.com/instances.*  → Tất cả permissions liên quan đến instances
iam.googleapis.com/serviceAccounts.* → Tất cả SA permissions

Wildcard rất powerful nhưng cần cẩn thận: compute.googleapis.com/*.* deny tất cả Compute Engine permissions, bao gồm cả compute.instances.get (đọc) và các permission ít nguy hiểm.

Khi thêm permission mới vào GCP matching một wildcard pattern đã có trong deny rule, permission mới tự động bị deny — không cần update deny policy.


Exception principals: Điều chỉnh ai bị deny

exceptionPrincipals field cho phép exclude một số principals khỏi deny rule, ngay cả khi họ nằm trong deniedPrincipals set.

json
{
  "denyRule": {
    "deniedPrincipals": [
      "principalSet://iam.googleapis.com/projects/MY_PROJECT/serviceAccounts/*"
    ],
    "exceptionPrincipals": [
      "serviceAccount:privileged-deploy-sa@my-project.iam.gserviceaccount.com"
    ],
    "deniedPermissions": [
      "compute.googleapis.com/instances.delete"
    ]
  }
}

Trong ví dụ này: tất cả service accounts trong project bị deny quyền delete instances, ngoại trừ privileged-deploy-sa (được phép delete để cleanup trong CI/CD).

Interaction giữa deniedPrincipals và exceptionPrincipals

Một principal bị block bởi deny rule nếu và chỉ nếu:

  1. Principal đó nằm trong deniedPrincipals
  2. Principal đó không nằm trong exceptionPrincipals

Nếu principal là member của một principal set trong deniedPrincipals nhưng cũng được list explicitly trong exceptionPrincipals, principal đó không bị deny.


Principal sets trong Deny Policies

Deny policies hỗ trợ nhiều loại principal identifiers, bao gồm cả principal sets (nhóm principals):

IdentifierÝ nghĩa
user:EMAILIndividual user account
serviceAccount:EMAILService account cụ thể
group:EMAILGoogle Group
principalSet://iam.googleapis.com/projects/PROJECT_ID/serviceAccounts/*Tất cả SA trong project
principalSet://iam.googleapis.com/workloadIdentityPools/POOL_ID/*Tất cả WIF identities trong pool
principalSet://goog-com:allTất cả Google Workspace identities
principalSet://iam.googleapis.com/allPrincipalsMọi principal (kể cả anonymous)

Principal sets với allPrincipals là cực kỳ powerful — dùng để enforce guardrails không có exceptions.


Conditions trong Deny Policies: Semantics khác với Allow

Như đã đề cập ở Chapter 31 - File 04, conditions trong deny policies có semantics khác:

  • Condition = true → Deny rule có hiệu lực (block access)
  • Condition = false → Deny rule không áp dụng (không block)
  • Condition không thể evaluate → Deny rule có hiệu lực (fail-secure)

Deny policies chỉ hỗ trợ subset của CEL attributes — cụ thể là chỉ resource.matchTag() (không có resource.name, resource.type, hay resource.service như trong allow conditions).

cel
// Hợp lệ trong deny policy condition:
resource.matchTag('123456789012/env', 'production')

// KHÔNG hợp lệ trong deny policy condition:
resource.type == 'storage.googleapis.com/Bucket'  // Không được support
resource.name.startsWith('projects/_/buckets/prod-')  // Không được support

Đây là giới hạn quan trọng: deny policy conditions chỉ có thể based trên resource tags, không phải resource type hay name.


Deny Policies vs Remove Role: Khi nào dùng cái nào

Khi nào dùng Deny Policies

  1. Override inheritance: Muốn block một permission dù principal có inherited role từ level cao hơn
  2. Cross-team enforcement: Team A cần một role có broad permissions, nhưng một số permissions trong đó phải bị restrict dù role structure
  3. Audit compliance: Cần chứng minh rằng một class of principals (ví dụ tất cả service accounts) không thể thực hiện một số actions sensitive
  4. Defense-in-depth: Thêm explicit deny ngay cả khi permission không được grant, để protect against accidental future grants

Khi nào revoke role đủ rồi

  1. Principal không cần permission: Revoke role/permission là đủ, không cần deny
  2. No inheritance concern: Resource policy của bạn không bị ảnh hưởng bởi inherited policies từ levels cao hơn mà bạn không control
  3. Simplicity: Deny policies thêm operational complexity, chỉ dùng khi có lý do rõ ràng

Rule của thumb: Nếu câu hỏi là "làm thế nào để đảm bảo principal X không thể làm Y kể cả khi có inherited permissions", đó là use case cho deny policy. Nếu câu hỏi chỉ là "revoke access", revoke role là đủ.


Production pattern: Org-level guardrails

Use case phổ biến nhất của deny policies là tạo organization-level guardrails — những restrictions áp dụng toàn bộ org, không có exceptions từ individual project policies.

json
// Deny policy ở organization level
{
  "displayName": "Prod environment protection guardrails",
  "rules": [
    {
      "denyRule": {
        "deniedPrincipals": ["principalSet://iam.googleapis.com/allPrincipals"],
        "exceptionPrincipals": [
          "serviceAccount:infra-destroyer@infra-project.iam.gserviceaccount.com"
        ],
        "deniedPermissions": [
          "storage.googleapis.com/buckets.delete",
          "bigquery.googleapis.com/datasets.delete"
        ],
        "condition": {
          "title": "Chỉ block production",
          "expression": "resource.matchTag('ORG_ID/environment', 'production')"
        }
      }
    }
  ]
}

Deny policy này:

  • Áp dụng cho mọi người trong org
  • Block xóa GCS buckets và BigQuery datasets trong môi trường production (tagged)
  • Chỉ infra-destroyer@... được phép (deployment automation)
  • Không thể bị override bởi project-level allow policies

Operational considerations

Deny policies tạo ra debugging complexity

Khi một request bị deny và không rõ lý do, cần kiểm tra cả allow policies deny policies. Policy Troubleshooter tự động trace qua cả hai, nhưng khi dùng raw API/logs, cần biết check cả hai.

bash
# List deny policies cho một project
gcloud iam policies list \
  --attachment-point="cloudresourcemanager.googleapis.com/projects/PROJECT_ID" \
  --kind="denypolicies"

# Describe một deny policy cụ thể
gcloud iam policies get-iam-policy POLICY_ID \
  --attachment-point="cloudresourcemanager.googleapis.com/projects/PROJECT_ID" \
  --kind="denypolicies"

Deny policy propagation delay

Deny policies cũng có eventual consistency tương tự allow policies. Khi tạo deny policy mới để emergency block, có thể mất 30–60 giây cho deny policy propagate. Trong trường hợp khẩn cấp, cần kết hợp deny policy với disable service account ngay lập tức.

Không có "deny all then allow exceptions" pattern

IAM không hỗ trợ "default deny everything" và sau đó explicitly allow. Deny policies chỉ có thể block specific permissions, không thể create blanket default-deny của tất cả permissions. Đây là fundamental khác biệt với hệ thống firewall (default deny outbound).

IAM vẫn là "allow-based" system: principals không có gì cho đến khi được grant. Deny policies chỉ là cách block những gì đã được grant.


References