Unity Catalog Governance
01 / 17

Unity Catalog
Governance

Four layers of access control. One enforcement model.
Agenda

What this deck covers

The Challenge

Once identity is resolved, what can each user actually see?

Your AI platform answers questions from live data, but not every user should see every answer. Sales reps should only see their own region's pipeline. Partners should see aggregated metrics but never individual end-user records. Auditors need read access to compliance tables but nothing else.

These rules must apply consistently whether the question comes from a Genie Agent, a Vector Search query, an agent workflow, or a direct SQL call. Unity Catalog is the single enforcement layer that makes this possible: row filters, column masks, attribute-based access control, all resolved from the caller's identity.

This deck shows you the four layers of data access control and how they compose.

Foundation

The UC Hierarchy

Every securable object lives in a three-level namespace. Permissions propagate downward.

Three-Level Namespace

catalog.schema.table

-- Example
demo_governance.sales.deals

Tables, views, functions, volumes, models, and connections all live inside this hierarchy.

Grant Propagation

GRANT on catalogApplies to all schemas and tables within
GRANT on schemaApplies to all tables within
GRANT on tableApplies to that table only

Grants are additive. A GRANT at the catalog level gives access to everything below. There is no implicit access: every identity starts with zero privileges.

Explicit-only model: No access without a GRANT. Owners have full control. Metastore admins can manage all objects.

Access Control

Four Complementary Layers

UC evaluates all four layers in sequence before returning any data. Each answers a different question.

1. Workspace Restrictions

WHERE can they access?

Workspace bindings limit which workspaces see specific catalogs. Isolates dev/staging/prod.

2. Privileges & Ownership

WHO can access WHAT?

GRANTs on securable objects. Hierarchical inheritance. Owners have full control.

3. ABAC Policies

WHAT data based on tags?

Governed tags + policy functions enforce access dynamically at scale. Define once, apply to thousands.

4. Table-Level Filtering

WHAT rows/columns?

Row filters, column masks, and dynamic views control visibility at query time per identity.

All four layers evaluate in sequence. The user receives fully governed data: workspace verified, privileges checked, ABAC policies enforced, rows filtered, columns masked, all actions audited.

Layers 1 + 2

Workspace Restrictions & Privileges

Workspace Bindings

Control which workspaces can see specific catalogs, external locations, and storage credentials.

Use caseRestrict production data to production workspaces only
ScopeCatalogs, external locations, storage credentials
OverrideTakes precedence over user-level permissions

A user with SELECT on a table still cannot access it from a workspace that is not bound to the catalog.

Privileges & GRANTs

Explicit permission model. No implicit access.

-- Grant read access
GRANT SELECT ON TABLE
  catalog.schema.deals
  TO `sales_east`;

-- Grant at schema level
GRANT USE SCHEMA ON SCHEMA
  catalog.sales
  TO `all_sales`;

Hierarchical: catalog → schema → table. Owners have full control over their objects.

Layer 4a

Row Filters: How They Work

Row filters automatically restrict which rows a user sees. Applied at query time by the SQL engine.

The Mechanism

A SQL function is bound to a table. UC injects the function as a WHERE clause on every query. The user never sees filtered rows and does not know they exist.

-- Create the filter function
CREATE FUNCTION sales_filter(region STRING)
RETURN
  is_member('admin')
  OR region = 'WEST'
    AND is_member('sales_west');

-- Apply to table
ALTER TABLE deals
  SET ROW FILTER sales_filter
  ON (region);

Key Properties

SilentFiltered rows are invisible. No error, no indication they exist.
AutomaticEnforced by the SQL engine on every query. No app code changes.
Identity-awarecurrent_user() and is_member() resolve at query time.
Same queryDifferent users run the same SQL, see different rows.

One filter per table. If you need multiple filter conditions, combine them in a single function using OR/AND logic.

Layer 4b

Column Masks: How They Work

Column masks transform sensitive values at query time. Users see the row but with masked column values.

The Mechanism

A SQL function is bound to a specific column. UC replaces the raw value with the function's output on every query.

-- Create the mask function
CREATE FUNCTION mask_ssn(ssn STRING)
RETURN
  CASE
    WHEN is_member('admins')
      THEN ssn
    ELSE '***-**-' || RIGHT(ssn, 4)
  END;

-- Apply to column
ALTER TABLE customers
  ALTER COLUMN ssn
  SET MASK mask_ssn;

Performance Guidance

Use SQLSQL masks faster than Python UDFs. Prefer deterministic expressions.
Minimize argsFewer arguments = better optimization. Max args: referenced columns still unconstrained.
Simple logicMinimize distinct mask expressions. CASE WHEN over nested function calls.
Lookup tablesOne per query (optimized out). Keep them small and cached.

Limitation: Cannot mask generated columns or reference time-travel/clone snapshots. Iceberg REST reads do not apply masks.

Layer 3

ABAC with Governed Tags

Attribute-Based Access Control uses tags to enforce dynamic, scalable policies without per-object permissions.

Governed Tags: Classification

Account-level tags with enforced naming rules. Applied to catalogs, schemas, tables, columns, volumes, and service objects (model services, MCP services). Tags inherit downward. Reserved tag families include databricks:, class.*, sap.*, system.*, and ai.* (for foundation models like ai.model_creator).

-- Tag a table
ALTER TABLE customer_data
  SET TAGS ('sensitivity' = 'high',
            'region' = 'EMEA');

-- Tag a foundation model service
ALTER SERVICE my_model
  SET TAGS ('ai.model_creator' = 'data-science',
            'sensitivity' = 'restricted');

ABAC Policies: Enforcement

Policies reference tags to dynamically control access. One policy can govern thousands of objects without manual per-object configuration. GRANT policies (Beta) dynamically grant privileges like EXECUTE on models based on tag attributes, while DENY policies (Beta) explicitly block access and take precedence over grants.

-- Query tags at runtime
SELECT * FROM system.information_schema
  .column_tags
WHERE tag_name = 'sensitivity'
  AND tag_value = 'pii';

Tags classify. Masks/Filters enforce. Apply a mask to any column tagged PII; it fires automatically on all queries, all tools.

ABAC

Why ABAC at Scale

Without ABAC

1,000 tables1,000 GRANT statements per role. Manual, error-prone.
New tableMust remember to add GRANTs. Miss one and data leaks or access breaks.
Policy changeUpdate hundreds of GRANTs. Hope you got them all.
AuditPiecing together who has access requires scanning every object.

With ABAC

1,000 tables1 policy + tags. Applied automatically.
New tableTag it. Policy applies instantly.
Policy changeUpdate one policy definition. Instant propagation.
AuditQuery tags and policies. Full visibility.

Scalable

One policy covers thousands of tables.

Dynamic

Re-tag a table to change access instantly.

Auditable

Tags + policies = full governance lineage.

Enforcement

All Enforced by the SQL Engine

Not application code. Not middleware. The SQL engine itself enforces every layer.

No Code Changes

Applications run the same SQL. Governance is invisible to the app layer. Add a row filter and every query automatically respects it.

Cannot Bypass

Row filters and column masks are injected by the engine. No SQL trick, no API shortcut, no SDK shortcut can skip them. The only override is ownership or admin privilege.

Full Audit Trail

Every query is logged with the identity that executed it, the governance policies that fired, and the data that was returned. System tables provide the full lineage.

Same table. Same query. Different identity. Different data.

This is the core principle of UC governance. It works identically for Genie Agents, Agent Bricks, MCP servers, notebooks, dashboards, and any tool that executes SQL through a warehouse.

Row Filters

current_user() vs is_member()

Two identity functions, different propagation behavior. Choose based on your token pattern.

current_user()

Returns the calling user's email from the active SQL token.

-- Filter: only see your own rows
WHERE owner = current_user()
OBOReturns the human's email. Propagates through Genie Agents, Agent Bricks, MCP chains.
M2MReturns the SP's application ID.
Best forPer-user row ownership, individual audit trails.

is_member()

Returns TRUE if the executing identity belongs to a workspace group.

-- Filter: role-based access
WHERE is_member('sales_west')
  AND region = 'WEST'
M2MEvaluates the SP's group membership. Works well when you control the SP.
OBOEvaluates the human's groups. Works as expected.
Best forRole-based filtering, federation patterns.

OBO pattern: prefer current_user(), which propagates the human identity end-to-end. Federation pattern: use is_member(), where role-based SPs map to workspace groups.

Column Masks

is_member() in OBO Chains

A critical distinction when column masks use is_member() in different token patterns.

M2M: is_member() works as expected

When a service principal executes the SQL directly, is_member() evaluates the SP's workspace group membership. You control group assignment explicitly.

-- SP is in 'admins' group
is_member('admins') => TRUE
-- Full SSN returned

OBO: is_member() evaluates service identity

In Genie Agents or Agent Bricks OBO flows, is_member() checks the service layer's groups, not the human's. Use a lookup table instead:

-- Pattern: current_user() + lookup
CASE
  WHEN EXISTS (
    SELECT 1 FROM access_control.user_roles
    WHERE email = current_user()
      AND role IN ('admin', 'compliance')
  ) THEN ssn
  ELSE '***-**-' || RIGHT(ssn, 4)
END

Rule: In OBO chains, use current_user() + UC-governed lookup table, never is_member() alone for role-based masking.

Combined

Row Filters + Column Masks Together

Both can apply to the same table. The row filter runs first, then column masks apply to surviving rows.

Example: Sales Deals Table

-- Row filter: region-based access
CREATE FUNCTION deals_row_filter(region STRING)
RETURN
  is_member('admin') OR
  (region = 'WEST' AND is_member('sales_west')) OR
  (region = 'EAST' AND is_member('sales_east'));

ALTER TABLE deals SET ROW FILTER deals_row_filter ON (region);

-- Column mask: hide margin from non-finance
CREATE FUNCTION mask_margin(val DOUBLE)
RETURN CASE WHEN is_member('finance') THEN val ELSE NULL END;

ALTER TABLE deals ALTER COLUMN margin_pct SET MASK mask_margin;

West Sales Rep Sees

DealRegionAmountMargin
Acme CorpWEST$50KNULL
Beta IncWEST$30KNULL

EAST rows filtered out. Margin masked.

Finance Admin Sees

DealRegionAmountMargin
Acme CorpWEST$50K42%
Beta IncWEST$30K38%
East CoEAST$45K51%

All rows visible. Real margins shown.

Integration

Data Classification & Governance

Automated classification tags sensitive data. Your masks and filters decide what to do with those tags.

Auto-Classification

Agentic AI scans columns and detects PII (email, SSN, credit card, etc). Results written as databricks:classifier:* tags. The results view shows User Access exposure metrics for the last 7 days, revealing how many distinct users accessed masked versus unmasked data by classification type.

-- See classified columns
SELECT catalog_name, table_name,
       column_name, tag_value
FROM system.information_schema
  .column_tags
WHERE tag_name =
  'databricks:classifier:pii_type';

Scans new tables within 24h. Can be automatic or manual review only. Detection exclusions (Beta) let you dismiss false positives, which removes the tag and feeds back to improve future scans.

Apply Masks to Tagged Columns

Classification does not auto-apply masks. After reviewing, you apply masks to sensitive columns.

-- For each classified column
ALTER TABLE my_catalog.hr.employees
  ALTER COLUMN ssn
  SET MASK my_catalog.masks.pii_mask;

Use ABAC: one mask covers all PII-tagged columns.

Workflow: Classification finds PII → review results → apply masks/row filters → all queries automatically governed.

Decision Guide

When to Use Which Pattern

Restrict catalogs to specific workspaces
→
WORKSPACE BINDINGS
Control who can SELECT/MODIFY objects
→
GRANTS
100s of tables, same access pattern
→
ABAC + GOVERNED TAGS
Users see only their own rows
→
ROW FILTER current_user()
Role-based row visibility
→
ROW FILTER is_member()
Mask PII (SSN, credit card, PHI)
→
COLUMN MASK
Dynamic tag-based masks at scale
→
ABAC + MASK UDF
External users, no Databricks accounts
→
FEDERATION role SPs + is_member()

Most production deployments combine multiple layers. Workspace bindings for environment isolation. GRANTs for baseline access. ABAC for scale. Row filters + column masks for fine-grained control.

FOUR LAYERS. ONE ENGINE. EVERY QUERY GOVERNED.
Workspace bindings + GRANTs + ABAC + Row filters + Column masks — enforced by the SQL engine, not application code.
Databricks Applied AI Governance Series · UC Governance