Lakehouse Access Control

Access Control on Lakehouse Tables and Catalogs

A warehouse enforces GRANT itself because it owns the storage. A lakehouse table is a set of files that any engine holding the bucket's key can read, so access control must hold at three layers: object storage (which keys reach which prefixes, Object Storage IAM), the catalog (who may load, create or drop a table: Polaris roles, Lakekeeper with OpenFGA, Unity Catalog grants) and the query engine, the only layer that understands rows and columns. Trino 483 403,499 offers allow-all, file-based, Open Policy Agent and Apache Ranger access control; BookNest's file-based rules give analyst the sales tables and a masked customers, and the Malaysian support team support_my only Malaysian customers:

privacy/trino/rules.json: table, column and row rules for Trino's file-based access controlJSON
{
  "tables": [
    {"user": "admin", "privileges": ["SELECT", "INSERT", "DELETE", "UPDATE", "OWNERSHIP"]},
    {"user": "support_my", "schema": "booknest", "table": "customers",
     "privileges": ["SELECT"], "filter": "country = 'MY'"},
    {"user": "analyst", "schema": "booknest", "table": "customers", "privileges": ["SELECT"],
     "columns": [
       {"name": "email", "mask": "regexp_replace(email, '^(.)[^@]*', '$1***')"},
       {"name": "national_id", "mask": "CAST(NULL AS varchar)"},
       {"name": "phone", "allow": false}]},
    {"user": "analyst", "schema": "booknest", "table": "(orders|order_items|daily_sales)",
     "privileges": ["SELECT"]}
  ]
}

The first matching rule wins, and a table no rule matches is denied. access-control.name=file and security.config-file=/etc/trino/rules.json in access-control.properties switch it on (privacy/trino_acl_up.sh mounts both into l1-trino). As admin, customer 12 shows gus.evans12@example.com and IC 011223-12-1792; privacy/acl.sh runs queries as the other users:

Output of 56
-- analyst: SELECT customer_id, email, national_id FROM customers WHERE customer_id = 12
 customer_id |      email       | national_id
-------------+------------------+-------------
          12 | g***@example.com | NULL
-- analyst: SELECT phone FROM customers WHERE customer_id = 12
Access Denied: Cannot select from table iceberg.booknest.customers
-- support_my: SELECT country, count(*) AS customers FROM customers GROUP BY country
 country | customers
---------+-----------
 MY      |       291
-- analyst: DELETE FROM orders WHERE order_id = 1
Access Denied: Cannot delete from table iceberg.booknest.orders

Masks and filters are SQL expressions Trino adds to the plan, so they hold inside joins, views and CREATE TABLE AS. Two caveats: without authentication --user analyst is a claim anyone can make (Securing Catalog and Engines adds passwords), and the rules bind only Trino, while a Spark 129 job holding the MinIO 30,943 key reads raw files. Close that gap with credential vending: Polaris, Lakekeeper and Unity Catalog check the caller's grants, then hand the engine short-lived storage credentials scoped to one table's prefix, so no engine holds the bucket key.