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
+14 -3
View File
@@ -6,9 +6,10 @@
# ./scripts/sicabe-authz render # authz.yml -> data.json
# ./scripts/sicabe-authz apply # render + reload OPA
# ./scripts/sicabe-authz fmt # rapikan format berkas ini
# Policy Rego membaca data.authz.catalogs / data.authz.write_schemas — nama
# catalog/schema TIDAK di-hardcode di Rego. Ganti nama catalog = ubah `catalogs:`
# di sini -> render -> reload. Dokumentasi: docs/authorization-cli.md
# Policy Rego membaca data.authz.catalogs + grant schema (data.roles.<r>.schema_grants)
# — nama catalog/schema & cakupan GRANT TIDAK di-hardcode di Rego. Ganti nama
# catalog = ubah `catalogs:` di sini -> render -> reload. Grant tingkat-schema:
# `roles.<r>.schemas: [{schema, caps, columns}]`. Dokumentasi: docs/authorization-cli.md
# =============================================================================
version: 1
catalogs: [iceberg]
@@ -29,6 +30,16 @@ roles:
- table: gold.orders_gold
caps: [select, write, ddl]
columns: '*'
schemas:
- schema: bronze
caps: [select, write, ddl]
columns: '*'
- schema: silver
caps: [select, write, ddl]
columns: '*'
- schema: gold
caps: [select, write, ddl]
columns: '*'
data-catalog:
grants:
- table: bronze.orders
+21 -1
View File
@@ -39,6 +39,26 @@
"ddl": true,
"columns": "*"
}
},
"schema_grants": {
"bronze": {
"select": true,
"write": true,
"ddl": true,
"columns": "*"
},
"silver": {
"select": true,
"write": true,
"ddl": true,
"columns": "*"
},
"gold": {
"select": true,
"write": true,
"ddl": true,
"columns": "*"
}
}
},
"data-catalog": {
@@ -90,6 +110,6 @@
},
"_generated": {
"from": "authz.yml",
"sha256": "8c7de053a5683e00c27f8d92157463c65ff6d45d5802e1e03b48bb9a2a933cb5"
"sha256": "36620fa9962357759a498f40d22a3a729d8fa7a8fa69d9941dbd270806968aad"
}
}
+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
}