ABAC policies in Unity Catalog
kept in this browser
Edit this pageABAC policies apply row filters and column masks from a metastore, catalog or schema, selecting objects by governed tag. A rule written once with CREATE POLICY covers every tagged table, present and future.
Checked against the Databricks documentation on · history
On the preview radar: Metastore-level ABAC policies
On this page14
What it is
ABAC (attribute-based access control) is how Unity Catalog applies access rules based on the attributes of objects rather than their names. The attribute is a governed tag (pii = email, sensitivity = high); the rule is a policy attached to a metastore (Beta), a catalog, a schema or a table that says: “for members of these groups, on columns with this tag, apply this mask” or “on every table with this tag, apply this row filter.”
Why it exists
Per-table row filters and column masks scale poorly: a hundred tables with an email column means a hundred ALTER TABLE statements, and the hundred-and-first table created tomorrow is born unprotected. With ABAC the rule is written once by the catalog owner, and every table that receives the tag inherits it automatically, with no way for the table owner to remove it.
How it works
Governed tags
Regular tags are free-form labels. Governed tags are defined at the account level (by an account admin or metastore admin) with a list of allowed values and permissions on who may assign them. In the UI they show a padlock. Only governed tags can be used in policy conditions. There are also predefined system tags (for example system.certification_status and the class.* family) that cannot be modified.
There are two condition functions:
has_tag('name'): the object or column has the tag, with any value;has_tag_value('name', 'value'): the tag has exactly that value.
Anatomy of a policy
CREATE [ OR REPLACE ] POLICY policy_name
ON { METASTORE | CATALOG c | SCHEMA c.s | TABLE c.s.t }
[ COMMENT '...' ]
{ ROW FILTER function | COLUMN MASK function }
TO principal [, ...]
[ EXCEPT principal [, ...] ]
FOR TABLES
[ WHEN condition ] -- on the object
[ MATCH COLUMNS condition AS alias [, ...] ] -- on the columns
[ ON COLUMN alias ] -- column mask only
[ USING COLUMNS ( alias | constant [, ...] ) ]
| Clause | Meaning |
|---|---|
ON | where the policy is attached: it applies to everything below. ON METASTORE (Beta) takes no name — it means the metastore you are connected to, and naming one is a syntax error |
TO / EXCEPT | who it applies to and who is exempt (often account users with EXCEPT for admins) |
FOR TABLES | the target object type (tables, materialized views, streaming tables) |
WHEN | condition on the object, for example has_tag_value('sensitivity', 'high') |
MATCH COLUMNS ... AS alias | selects columns by tag and gives them a name usable later |
ON COLUMN | which column to mask |
USING COLUMNS | additional arguments to the function |
The function is a SQL UDF, the same kind used by per-table filters; whoever queries the table needs EXECUTE on it. Creating or modifying a policy requires MANAGE on the ON object, or ownership — except ON METASTORE, which is the metastore admin’s alone.
Conditions are evaluated in the control plane, and three families of function can appear in them: the tag functions above, identity attribute functions (has_identity_attribute_value, has_identity_attribute_tag_match), which work only in the WHEN clause of a column mask and only from SQL, and context attribute functions (has_context_attribute, has_context_attribute_value), which read the request’s own context — what application is asking — and are the way to treat an agent acting on somebody’s behalf differently from that person at a keyboard.
Column mask
CREATE FUNCTION prod.sec.last_digits(value STRING, n INT)
RETURN IF(is_account_group_member('hr'), value, CONCAT('***', RIGHT(value, n)));
CREATE POLICY mask_tax_id
ON CATALOG prod
COMMENT 'Tax ID visible in full only to HR'
COLUMN MASK prod.sec.last_digits
TO `account users` EXCEPT `hr`
FOR TABLES
MATCH COLUMNS has_tag_value('pii', 'tax_id') AS tax
ON COLUMN tax
USING COLUMNS (4);
From this point on, every column in prod tagged pii = tax_id shows only its last four characters to anyone outside hr.
Row filter
CREATE FUNCTION prod.sec.domestic_only(region STRING)
RETURN region = 'IT';
CREATE POLICY filter_non_domestic
ON SCHEMA prod.sales
ROW FILTER prod.sec.domestic_only
TO `analysts`
FOR TABLES
WHEN has_tag_value('sensitivity', 'high')
MATCH COLUMNS has_tag('geo_region') AS region
USING COLUMNS (region);
From Python you run it with spark.sql(...), like any DDL.
Evaluation and conflicts
Policies are evaluated on every query: if the object carries the required tags, the policy applies. At runtime only one row filter and one mask per column can resolve for a given user: if two policies (or a policy and a per-table filter) apply the same function, the query proceeds; if they apply different functions, the query fails with an error. The comparison is on the functions, not on their results.
ABAC versus per-table filters
| Per-table row filter / mask | ABAC policy | |
|---|---|---|
| Scope | one table | catalog, schema, or table and everything below it |
| Selection | by name | by governed tag |
| Who manages it | table owner | catalog/schema owner; the table owner cannot remove it |
| New tables | must be configured by hand | covered as soon as they are tagged |
| OpenSharing (Delta Sharing) | no | yes, if the share owner is exempt |
| When to use | table-specific logic, a few stable tables | cross-cutting rules, a growing data estate |
One policy for a whole metastore
A policy can now be attached to the metastore itself, which is the only scope that reaches catalogs that do not exist yet — including ones created after the policy was written. All four policy types support it, it takes the metastore admin role rather than MANAGE, and it is Beta.
Two things to know before reaching for it. Writing the SQL needs newer compute than using the result does (below), and Unity Catalog’s managed disaster recovery does not replicate metastore-level policies — objects arrive in the secondary metastore unprotected, which is the worst moment to discover it.
Requirements and limits
Three different runtime thresholds, which is a common source of confusion:
| To do this | You need |
|---|---|
| Query a table an ABAC policy protects | serverless, or DBR 16.4+ on standard compute, or DBR 16.4+ on dedicated compute with fine-grained access control filtering |
| Create or change a GRANT or DENY policy in SQL | classic compute on DBR 18 LTS or above |
| Create or change a metastore-level policy in SQL | compute on DBR 19 or above |
Older runtimes cannot read protected tables at all: use EXCEPT to exempt the principals still on them.
The quotas, which the documentation now splits more finely than it used to:
| Limit | Number |
|---|---|
| Policies per metastore, counting every object below it | 10,000 |
| Policies attached directly to a metastore (Beta) | 100 |
| Policies per catalog | 100 |
| Policies per schema | 100 |
| Policies per table | 50 |
Principals per policy, TO and EXCEPT together | 20 |
Column conditions in one MATCH COLUMNS, row filter policy | 6 |
Column conditions in one MATCH COLUMNS, column mask policy | 3 |
Row filters and column masks count separately from GRANT and DENY policies, which share a quota with each other.
Other limits worth carrying:
- Materialized views and streaming tables: the refresh runs as the pipeline owner, so if the owner is subject to the policy the data is materialized already masked. Exempt them.
- Time travel and clones — deep or shallow — fail on protected tables, and AI Search indexes do not enforce policies at all.
- A metastore cannot carry a tag itself, so a metastore-level condition has to match something at catalog level or below.
- GRANT and DENY cannot target external locations, storage credentials, shares, recipients, providers or connections.
GRANT and DENY policies
Two policy types beyond filters and masks, neither needed for the Associate exam.
A GRANT policy hands out privileges by tag instead of by name, and its targets are mostly the AI objects: models, model services, model provider services, MCP services and agent services, with EXECUTE, APPLY_TAG and READ_METADATA. What a user can actually do is the union of their direct grants and any policy that matches — and SHOW GRANTS shows only the first half, so SHOW EFFECTIVE POLICIES is the one that answers “why can they do that”.
A DENY policy (Beta) is the reason DENY is not a statement in Unity Catalog (see Privileges: GRANT, REVOKE, and DENY). It denies exactly one privilege, MANAGE_ACCESS_CONTROL, and it is absolute: it beats every grant including one held through a group or through owning the object, it inherits downward, and where two of them overlap the most restrictive wins. Metastore admins are always exempt, which is what stops somebody locking everyone out of their own metastore.
Example
A data steward tags prod.crm.customers.email with pii = email and prod.marketing.lead.email with the same tag. A single policy on prod covers both, plus every future table:
CREATE FUNCTION prod.sec.mask_email(email STRING)
RETURN CONCAT('***@', SPLIT_PART(email, '@', 2));
CREATE POLICY mask_email
ON CATALOG prod
COLUMN MASK prod.sec.mask_email
TO `account users` EXCEPT `privacy-office`
FOR TABLES
MATCH COLUMNS has_tag_value('pii', 'email') AS e
ON COLUMN e;
Common mistakes
- Using a regular tag in the condition: only governed tags work.
- Forgetting
EXCEPTfor the pipeline owner: the materialized views end up masked for everyone. - Attaching a policy and a
SET MASKwith different functions to the same column: the querying user gets an error, not double masking. - Assuming the table owner can remove the policy: only whoever created it at the higher level can.
Where this sits
- Data Engineer AssociateGovernance and Security15% of the exam“Understand Unity Catalog ABAC policies to centrally control row-level filtering and column masking for sensitive data.”
- Data Engineer ProfessionalEnsuring Data Security and Compliance
- Generative AI Engineer AssociateGovernance
- Learning pathGovernance & SecurityUnity Catalog end to end: the three-level namespace, managed and external tables, privileg…
Nothing of that kind here yet. Try the full list.