sync a58faaa refactor(authz)

This commit is contained in:
2026-10-13 18:28:09 +07:00
parent 5519ae2de7
commit 678885f681
6 changed files with 290 additions and 40 deletions
+86 -20
View File
@@ -38,9 +38,7 @@ identity_user := input.context.identity.user
# SPESIFIK (data.authz.*) — JANGAN meneruskan `data` utuh ke builtin, sebab itu
# membuat compiler menganggapnya bergantung pada `allow` (rego_recursion_error).
default data_catalogs := []
default write_schemas := []
data_catalogs := data.authz.catalogs
write_schemas := data.authz.write_schemas
# Catalog yang boleh di-listing = catalog terdaftar + 'system' (bawaan Trino).
allowed_catalogs contains c if { some c in data_catalogs }
@@ -157,6 +155,15 @@ base_table_name(name) := name if {
table_id(schema, table) := sprintf("%s_%s", [schema, base_table_name(table)])
# ── Grant tingkat-SCHEMA: berlaku untuk SEMUA tabel di schema tersebut ───────
# Bentuk data (dari authz.yml `roles.<role>.schemas`):
# data.roles.<role>.schema_grants.<schema> = { select/write/ddl, columns }
# Digabung (union) dengan grant per-tabel. Tabel baru (termasuk sementara dbt/Sling
# milik engineer) otomatis ikut ter-cover bila grant-nya di level schema.
schema_grant(role, schema) := sg if {
sg := data.roles[role].schema_grants[schema]
}
allow if {
cap := op_caps[op]
res := input.action.resource
@@ -257,30 +264,89 @@ allow if {
any_cap(grant)
}
# ── Grant tingkat-SCHEMA (union dgn grant per-tabel di atas) ─────────────────
# Operasi per-tabel (non-SELECT) yang dicakup cap.
allow if {
cap := op_caps[op]
res := input.action.resource
some r in effective_roles
sg := schema_grant(r, res.table.schemaName)
sg[cap] == true
}
# SELECT per-kolom pada tabel di schema yang di-grant.
allow if {
op == "SELECTFROMCOLUMNS"
res := input.action.resource
res.table.catalogName in data_catalogs
cols := res.table.columns
count(cols) > 0
some r in effective_roles
sg := schema_grant(r, res.table.schemaName)
sg.select == true
cols_all_ok(sg.columns, cols)
}
# SELECT tanpa kolom eksplisit (count(*)) pada schema yang di-grant.
allow if {
op == "SELECTFROMCOLUMNS"
res := input.action.resource
res.table.catalogName in data_catalogs
count(res.table.columns) == 0
some r in effective_roles
sg := schema_grant(r, res.table.schemaName)
sg.select == true
}
# Listing tabel: terlihat bila schema punya grant apapun.
allow if {
op == "FILTERTABLES"
res := input.action.resource
res.table.catalogName in data_catalogs
some r in effective_roles
sg := schema_grant(r, res.table.schemaName)
any_cap(sg)
}
# FilterColumns (visibilitas kolom) utk schema yang di-grant.
allow if {
op == "FILTERCOLUMNS"
res := input.action.resource
some r in effective_roles
sg := schema_grant(r, res.table.schemaName)
sg.select == true
sg.columns == "*"
}
allow if {
op == "FILTERCOLUMNS"
res := input.action.resource
cols := res.table.columns
count(cols) > 0
some r in effective_roles
sg := schema_grant(r, res.table.schemaName)
sg.select == true
cols_all_ok(sg.columns, cols)
}
allow if {
op == "SetSystemSessionProperty"
}
# ── DEV/EVAL: data-engineer bebas DDL + tulis di schema bronze ───────────────
# Diminta eksplisit (2026-09-22) untuk mempermudah ingestion (Sling) dan
# transform (dbt) di layer bronze.
# ── Blok luas: role dengan schema-grant 'write' = OPERASI APA PUN di schema ──
# Dulu HARDCODE (role data-engineer + schema bronze/silver/gold via write_schemas).
# Kini DATA-DRIVEN dari authz.yml `roles.<role>.schemas` — eksplisit & terport.
#
# ⚠️ Cakupan blok ini SENGAJA LEBIH LUAS dari least-privilege per-tabel:
# 1. Berlaku untuk OPERASI APA PUN yang menargetkan tabel di schema bronze —
# termasuk operasi level-kolom yang dikirim Trino untuk UPDATE/MERGE, yang
# tidak punya entri di op_caps sehingga sebelumnya jatuh ke deny-by-default.
# Tanpa ini, `MERGE` (upsert) selalu gagal: "Access Denied: Cannot update
# columns [...]".
# 2. Berlaku untuk tabel apa pun, termasuk tabel sementara `<nama>_tmp` milik
# Sling. Tabel itu tidak akan cocok dengan grant biasa karena policy hanya
# men-strip sufiks milik dbt (`__dbt_tmp`, `__dbt_backup`).
#
# Efek: tabel baru di bronze tidak lagi perlu baris grant di data.json.
# Mempersempit kembali: hapus blok ini, atau tambahkan syarat daftar nama tabel
# eksplisit. Layer lain (silver/gold) TIDAK terpengaruh — tetap least-privilege.
# Cakupan sengaja LEBIH LUAS dari least-privilege per-tabel, karena:
# 1. Menangkap OPERASI APA PUN di schema itu — termasuk operasi level-kolom yang
# dikirim Trino untuk UPDATE/MERGE dan TIDAK punya entri di op_caps. Tanpa ini
# `MERGE` (upsert) gagal: "Access Denied: Cannot update columns [...]".
# 2. Berlaku untuk tabel apa pun di schema itu, termasuk tabel sementara
# `<nama>_tmp` (Sling) dan tabel baru → tak perlu grant per-tabel.
# Hanya bila schema-grant punya 'write' — role 'select'-only (mis. data-analyst)
# TIDAK terpengaruh (tetap least-privilege).
allow if {
res := input.action.resource
res.table.schemaName in write_schemas
some r in effective_roles
r == "data-engineer"
sg := schema_grant(r, res.table.schemaName)
sg.write == true
}