Skip to main content
Neoinsights

Databricks Unity Catalog ABAC: Attribute-Based Access Control

How ABAC in Databricks Unity Catalog governs row filters and column masks by tag instead of by table, how to wire it up, and when it's overkill.

Mory KabaMory Kaba13 min read
Diagram of a single Unity Catalog ABAC policy masking the pii:email column across three tables, including one created next quarter

In Unity Catalog you usually grant access to objects by giving a principal a privilege on an object. For example, the data analyst group gets SELECT on every table in your gold layer, and the moment you add a new user to that group, they inherit every grant the group already has. That’s Role-Based Access Control, and for most organizations using Databricks and Unity Catalog - it’s enough.

When SELECT is too permissive, you reach for the next layer down. Row filters hide rows a user shouldn’t see. Column masks redact values in a field without hiding the column. Both are applied table by table, by the table owner, and they work well right up until you have forty tables that all need the same mask on the same kind of column.

That last problem is the one Attribute-Based Access Control (ABAC) was built for. It’s the new kid on the block in Unity Catalog, generally available since May 2026 for row filters and column masks, and it changes the unit of governance from “this table” to “any table that looks like this.” This post covers how it works, how you actually wire it up, and when you should use it.

Key Takeaways

  • ABAC moves the access decision off the individual table and onto the object’s attributes, expressed as governed tags in Unity Catalog
  • One policy written at the catalog or schema level covers every matching object inside that scope, including tables that don’t exist yet
  • The three moving parts are governed tags (the attributes), policies (the rule), and SQL UDFs (the masking or filtering logic)
  • Row filter and column mask policies reached GA in May 2026; GRANT policies are still Beta and only cover EXECUTE on models
  • Because policies are owned above the table, a table owner can’t drop a mask that governance applied - which is the separation-of-duties story regulated teams actually need
  • For most mid-market teams ABAC is not where you start, but you should centralize your masking UDFs now so the eventual migration is a lift, not a rewrite

The layers you already have

Before ABAC, Unity Catalog gives you three control mechanisms, and they stack rather than compete.

Privileges (RBAC). This is the base layer. GRANT SELECT ON SCHEMA gold TO data_analysts decides who can touch the object at all. Inheritance flows down the hierarchy - a grant on a catalog reaches its schemas and tables so you manage access at the level that makes sense and let it cascade.

Table-level row filters and column masks. When “can see the table” isn’t the same as “can see every row and value,” you attach a row filter or a column mask. Each is a SQL UDF: a row filter returns a boolean and drops the rows where it’s false, a mask takes a value and returns it either intact or redacted. You bind them with ALTER TABLE ... SET MASK or the equivalent for filters, and the table owner manages them.

Dynamic views. When you want to hand someone a reshaped, joined, or redacted slice of data without giving them the base tables, you wrap the tables in a view gated by functions like is_account_group_member(). It’s for exposing a curated surface, not for governing the tables underneath.

The friction shows up at scale. A mask attached to a table lives and dies with that table. Copy the pattern across forty tables and you get forty near-identical UDF bindings, each one a place where the logic can drift. The owner of table number forty-one has to remember to apply it. New tables land unprotected by default.

What ABAC actually is

ABAC moves the decision off the individual object and onto the object’s attributes. In Unity Catalog those attributes are governed tags - account-level key-value pairs with a controlled vocabulary, where the governance team decides both the allowed values and who is allowed to assign them. A pii tag with values like email, phone, and ssn. A sensitivity tag with public, internal, confidential, restricted.

You tag your data once. Then you write a policy that says, in effect, “mask any column tagged pii:email, for everyone except the compliance group.” The policy attaches at a catalog, schema, or table, and it evaluates dynamically: every object inside that scope carrying the matching tag is governed by it, automatically. Define it at the catalog level and it inherits down to every schema and table inside - including tables that don’t exist yet. This is how you can use ABAC to scale your governance policies. One policy, written once, covering data you haven’t created.

Policy (catalog level)COLUMN MASK on anycolumn tagged pii:emailfct_orderscustomer_email· · · maskeddim_customercontact_email· · · maskedfct_returns(created next quarter)buyer_emailFigure: One policy at the catalog level masks every column carrying the matching tag - across existing tables and any future table that gets tagged.

Three pieces make it run:

  • Governed tags carry the attributes. By default a securable inherits tags from its parent catalog or schema, so you can tag broadly and override where needed. Column tags are the exception - they don’t inherit from the table and have to be applied directly to the column.
  • Policies carry the rule. Three types exist: row filters, column masks, and GRANT policies. The first two are GA; GRANT policies are still Beta and currently only cover EXECUTE on models, so treat dynamic privilege grants as something to watch rather than build on.
  • UDFs carry the logic. The same SQL functions you’d write for a table-level mask, reused by the policy. Prefer SQL over Python here - the optimizer can inline SQL UDFs and can’t do the same for Python, and at query time that difference is real.

Policies match on tags through two functions, has_tag() and has_tag_value(). A WHEN clause matches at the table level (direct or inherited tags); a MATCH COLUMNS clause matches columns (direct tags only). That’s the entire condition language. It is deliberately small and evaluated by the control plane against metadata, not something you can turn into arbitrary logic.

There’s a separation-of-duties story baked in, and it’s the part that matters once more than one person is involved. The governance team defines the tag taxonomy. Data stewards (or automated data classification) apply tags. Governance admins write policies. Data producers create tables inside the governed scope. Nobody is a bottleneck, and because policies are owned above the table, a table owner can’t quietly drop a mask the compliance team put in place.

Wiring it up

The flow is: define the tag, get it onto the data, write the policy. Here’s a column mask end to end (the full grammar lives in the Databricks policy reference).

First a UDF for the masking logic, nothing ABAC-specific, just a SQL function that returns the last four characters:

CREATE FUNCTION gold.security.mask_email (email STRING)
RETURNS STRING
RETURN concat('***@', split_part(email, '@', 2));

Tag the columns you want governed. You can do this by hand on each column, or let Data Classification detect and tag them for you - it learns the pattern from columns you’ve already tagged and labels new ones as they arrive, which is the part that keeps coverage from rotting over time.

Then the policy itself:

CREATE POLICY mask_email_pii
ON CATALOG gold
COLUMN MASK gold.security.mask_email
TO `account users`
EXCEPT compliance_team
FOR TABLES
MATCH COLUMNS has_tag_value('pii', 'email') AS email
ON COLUMN email;

The column named in ON COLUMN is passed to the mask function automatically as its first argument, so a single-argument mask like mask_email needs no USING COLUMNS clause; you’d add one only to pass extra arguments beyond the masked value. Read it left to right: on everything in the gold catalog, apply the mask_email function to any column tagged pii:email, for all users except the compliance team. No table named anywhere. Add a new gold table tomorrow with an email column tagged the same way, and it’s masked the moment it lands.

Row filters follow the same shape, with a WHEN clause selecting which tables the filter applies to:

CREATE FUNCTION gold.security.region_filter (region STRING)
RETURNS BOOLEAN
RETURN region = current_user_region();
 
CREATE POLICY region_row_filter
ON SCHEMA gold.sales
ROW FILTER gold.security.region_filter
TO sales_reps
FOR TABLES
WHEN has_tag_value('sensitivity', 'confidential')
MATCH COLUMNS has_tag('region') AS region
USING COLUMNS (region);

A few practical constraints worth knowing before you commit:

  • You need Databricks Runtime 16.4 or above, or serverless compute. Older clusters won’t enforce these policies.
  • Creating or editing a policy requires MANAGE on the securable (or ownership), plus EXECUTE on the UDF.
  • It’s all scriptable. CREATE POLICY is SQL, and the same operations are exposed through the REST API, the SDKs, and Terraform, which is where this belongs if you’re treating governance as code rather than clicking through Catalog Explorer.

That last point is the one I’d push hardest with any team standing this up. Tags, policies, and the UDF library are configuration. Put them in Terraform, review them in pull requests, and your access model becomes a thing you can diff and audit instead of tribal knowledge living in a UI.

When to reach for it, and when not to

This is where the honest answer diverges from the vendor answer. Databricks recommends ABAC as the default and tells you to fall back to table-level filters only for per-table logic or if you haven’t adopted ABAC yet. That’s the right advice for a large enterprise. It is not automatically the right advice for a thirty-person company with one analyst team and forty gold tables.

Do you need finer control than”who can query this table”?noPrivileges(RBAC)yesSame rule across many tables? New tablesappearing? Separation of duties or GDPRclassification at stake?noTable-levelfilter/maskyesABAC policiestags + central policyNeed a joined / reshaped surface for userswithout base-table access? → Dynamic viewLayers stack - ABAC sits on top of privileges, not instead of them.Figure: A decision path for which control to reach for. The layers combine; ABAC governs how data is masked or filtered, privileges still decide who reaches the object at all.

Reach for ABAC when the economics flip in its favor:

  • The same masking or filtering logic repeats across many tables. This is the core case. Three column masks are cheaper to maintain by hand than a tag taxonomy plus a UDF library. Thirty are not.
  • New tables land continuously and must be protected on arrival. If unclassified data showing up unmasked is a compliance incident, you want coverage that doesn’t depend on a person remembering a step.
  • Separation of duties is a real requirement, not a diagram. Compliance writes the rule, stewards classify, producers build, and no producer can override the rule. For a regulated DACH business (Germany, Austria, Switzerland) under GDPR, that division is often the actual deliverable, and ABAC enforces it structurally.
  • You’re building the platform now. Greenfield is the cheapest time to set up a tag taxonomy. Retrofitting one across hundreds of existing tables is the expensive path.

It’s overkill when none of that pressure exists yet. A handful of gold tables, one analyst group, masks you can count on one hand - building governed tags, a classification process, and a policy library for that is more scaffolding than the building needs. You’ll spend more effort standing up the taxonomy than you’d ever spend maintaining the masks directly, and a half-populated tag taxonomy is worse than none, because policies silently miss the columns nobody tagged.

The mid-market answer I’d actually give a client: usually not yet, but design as if you will. Keep your masking UDFs centralized and reusable from day one even if you’re binding them table by table. Name your sensitive columns consistently. When you cross the threshold - more tables than you want to govern by hand, a GDPR audit that wants demonstrable separation of duties, or a new platform build - the migration to ABAC is then a matter of tagging and lifting your existing UDFs into policies, not a rewrite. ABAC isn’t where most mid-market teams should start. It’s where they should be ready to go the moment per-table governance stops scaling, and on a Snowflake-or-Databricks platform built for growth, that moment tends to arrive sooner than the org plans for.

If you’re still mapping out where governance sits in your broader plan, Defining Your Data Strategy for 2026 puts this decision in context, and the Unity Catalog system tables are where you’d audit who actually accessed what once your policies are live.

FAQ

Is ABAC generally available, or still in preview?

Row filter policies, column mask policies, governed tags, and Data Classification reached GA in May 2026. GRANT policies, meaning dynamic privilege grants based on tags, are still Beta and currently limited to EXECUTE on models.

Does ABAC replace RBAC?

No. They sit at different levels and are meant to run together. Privileges decide whether a principal can reach an object at all; ABAC decides what rows and values they see once they can. You still grant SELECT; ABAC layers the masking and filtering on top.

Can a table owner override or remove an ABAC policy?

No, and that’s the point. Policies are owned at the catalog or schema level by whoever holds MANAGE. A table owner can’t strip a mask that governance applied, which is exactly the separation of duties you can’t get from table-level masks the owner controls.

SQL UDFs or Python UDFs for the masking logic?

SQL, unless you have a hard reason not to. The optimizer can inline SQL UDFs and push predicates through them; it can’t do the same for Python, so Python masks cost you at query time. If you must use Python, mark it DETERMINISTIC where it applies.

What compute do I need?

Databricks Runtime 16.4 or above, or serverless. Policy enforcement won’t work on older clusters, so check your runtime before you start tagging.

Can I manage all of this as code?

Yes. Tags, policies, and UDFs are all reachable through the REST API, the SDKs, and Terraform. If you’re serious about governance, that’s where it should live, version-controlled and reviewable, rather than configured by hand in Catalog Explorer.

Mory Kaba

Mory Kaba

Senior Data Platform Engineer and consultant specializing in data engineering, AI, and cloud architecture across the DACH region.

Newsletter

Get practical data engineering notes in your inbox

Occasional emails on lakehouses, dbt, Spark, RAG, and what actually works in production. No fluff, unsubscribe anytime.

By subscribing you agree to receive emails from Neoinsights. You can unsubscribe at any time.