<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>article Fine-Grained Access Control at Scale with ABAC in Technical Blog</title>
    <link>https://community.databricks.com/t5/technical-blog/fine-grained-access-control-at-scale-with-abac/ba-p/147786</link>
    <description>&lt;H2&gt;&lt;SPAN&gt;Stop Wiring Security Table by Table: How ABAC Changes Everything in Unity Catalog&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;If you have ever managed column masks or row filters across more than a handful of tables in Unity Catalog, every new table that lands with PII means another &lt;FONT color="#0000FF"&gt;&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;ALTER TABLE ... SET MASK&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt; command. Another function to create. Another thing to forget. ABAC (Attribute-Based Access Control) fixes this: &lt;EM&gt;&lt;STRONG&gt;you tag data once and let policies do the rest&lt;/STRONG&gt;&lt;/EM&gt;.&lt;/SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;This article walks through the three approaches to fine-grained access control (FGAC) in Unity Catalog, explains why ABAC is the one that actually scales.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;The Challenge: Scaling Fine-Grained Access Control (FGAC)&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Imagine you have a &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;&lt;FONT color="#0000FF"&gt;customers&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt; table with email addresses and phone numbers. You need to mask those columns so that only members of the &lt;FONT face="courier new,courier" color="#0000FF"&gt;&lt;STRONG&gt;data_admins&lt;/STRONG&gt;&lt;/FONT&gt; group can see them in clear text. Easy enough.&lt;/SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;Now imagine you have 50 tables with email columns. Or 200. Every time a data engineer adds a new table with an email field, someone has to remember to create and attach a mask. If they forget, that PII sits there unprotected until an audit catches it. The security model becomes a to-do list instead of a system.&lt;/SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;Unity Catalog gives you three ways to solve this. Let's look at each one, then talk about why only one of them holds up at scale.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;SPAN&gt;Approach 1: Manual Column Masks and Row Filters&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;This is the most common starting point. You write a SQL function that defines both &lt;FONT color="#FF0000"&gt;&lt;EM&gt;&lt;STRONG&gt;what&lt;/STRONG&gt;&lt;/EM&gt;&lt;/FONT&gt; to mask and &lt;FONT color="#FF0000"&gt;&lt;EM&gt;&lt;STRONG&gt;who&lt;/STRONG&gt;&lt;/EM&gt;&lt;/FONT&gt; gets to see the original value, then you attach this function to a specific column on a specific table.&lt;/SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;Here is a column mask for email addresses on &lt;STRONG&gt;&lt;FONT face="courier new,courier" color="#0000FF"&gt;samples.bakehouse.customers&lt;/FONT&gt;&lt;/STRONG&gt;:&lt;/SPAN&gt;&lt;/P&gt;
&lt;PRE&gt;&lt;SPAN&gt;-- The function defines WHAT to mask AND WHO sees what&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;CREATE OR REPLACE FUNCTION silver.mask_email(email_&lt;/SPAN&gt;&lt;WBR /&gt;&lt;SPAN&gt;address STRING)&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;RETURNS STRING&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;RETURN CASE&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; WHEN is_account_group_member('data_&lt;/SPAN&gt;&lt;WBR /&gt;&lt;SPAN&gt;admins') THEN email_address&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; ELSE CONCAT('****@', SPLIT(email_address, '@')[1])&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;END;&lt;/SPAN&gt;&lt;/PRE&gt;
&lt;P&gt;&lt;SPAN&gt;Then you attach it:&lt;/SPAN&gt;&lt;/P&gt;
&lt;PRE&gt;&lt;SPAN&gt;ALTER TABLE silver.customers&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;ALTER COLUMN email_address SET MASK silver.mask_email;&lt;/SPAN&gt;&lt;/PRE&gt;
&lt;P&gt;&lt;SPAN&gt;Row filters follow the same pattern. You write a function that returns &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;&lt;FONT color="#0000FF"&gt;TRUE&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt; for rows the user is allowed to see, then attach it to the table:&lt;/SPAN&gt;&lt;/P&gt;
&lt;PRE&gt;&lt;SPAN&gt;-- Again: WHAT to filter AND WHO sees what&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;CREATE OR REPLACE FUNCTION silver.country_filter(country STRING)&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;RETURNS BOOLEAN&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;RETURN CASE&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; WHEN is_account_group_member('data_&lt;/SPAN&gt;&lt;WBR /&gt;&lt;SPAN&gt;admins') THEN TRUE&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; WHEN is_account_group_member('&lt;/SPAN&gt;&lt;WBR /&gt;&lt;SPAN&gt;account users')&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; &amp;nbsp; THEN country IN ('Australia', 'Japan')&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; ELSE FALSE&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;END;&lt;/SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;ALTER TABLE silver.franchises&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;SET ROW FILTER silver.country_filter ON (country);&lt;/SPAN&gt;&lt;/PRE&gt;
&lt;P&gt;&lt;SPAN&gt;This works fine for a few tables. The problem is that both the function and the binding are per-table, per-column. The function itself encodes the &lt;STRONG&gt;&lt;FONT color="#FF0000"&gt;WHO&lt;/FONT&gt;&lt;/STRONG&gt; (which groups see what) alongside the &lt;STRONG&gt;&lt;FONT color="#FF0000"&gt;WHAT&lt;/FONT&gt;&lt;/STRONG&gt; (how to transform the data). There is no separation of concerns.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Every column needs its own &lt;STRONG&gt;&lt;FONT face="courier new,courier" color="#0000FF"&gt;ALTER TABLE&lt;/FONT&gt;&lt;/STRONG&gt; statement. Every new table needs manual attention.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;SPAN&gt;Approach 2: Dynamic Views&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;Dynamic views embed security logic directly in a view definition using &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;&lt;FONT color="#0000FF"&gt;CASE&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt; expressions and context functions. They combine row filtering and column masking in a single object.&lt;/SPAN&gt;&lt;/P&gt;
&lt;PRE&gt;&lt;SPAN&gt;CREATE OR REPLACE VIEW silver.v_customers_secure AS&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;SELECT&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; customerID,&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; first_name,&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; last_name,&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; CASE&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; &amp;nbsp; WHEN is_account_group_member('data_&lt;/SPAN&gt;&lt;WBR /&gt;&lt;SPAN&gt;admins') THEN email_address&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; &amp;nbsp; ELSE CONCAT('****@', SPLIT(email_address, '@')[1])&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; END AS email_address,&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; CASE&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; &amp;nbsp; WHEN is_account_group_member('data_&lt;/SPAN&gt;&lt;WBR /&gt;&lt;SPAN&gt;admins') THEN phone_number&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; &amp;nbsp; ELSE CONCAT('***-***-', RIGHT(phone_number, 4))&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; END AS phone_number,&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; city,&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;&amp;nbsp; country&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;FROM silver.customers&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;WHERE is_account_group_member('data_&lt;/SPAN&gt;&lt;WBR /&gt;&lt;SPAN&gt;admins') OR country = 'Australia';&lt;/SPAN&gt;&lt;/PRE&gt;
&lt;P&gt;&lt;SPAN&gt;Dynamic views are the recommended approach when sharing data via &lt;FONT color="#FF0000"&gt;&lt;STRONG&gt;Delta Sharing&lt;/STRONG&gt;&lt;/FONT&gt;, because row filters and column masks are not applied to Delta Sharing recipients. The security logic in a dynamic view is enforced because recipients query the view, not the underlying table.&lt;/SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;But dynamic views still suffer from the same scaling problem. Each view is hand-crafted for a specific table. Add a new table with PII and you need a new view. The security logic is spread across dozens of view definitions with no central point of control.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;SPAN&gt;Approach 3: ABAC &lt;EM&gt;(The One That Scales)&lt;/EM&gt;&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;ABAC separates the concerns. The masking function defines only the &lt;STRONG&gt;&lt;FONT color="#FF0000"&gt;WHAT&lt;/FONT&gt;&lt;/STRONG&gt;. A policy object defines the &lt;STRONG&gt;&lt;FONT color="#FF0000"&gt;WHO&lt;/FONT&gt;&lt;/STRONG&gt; and the &lt;FONT color="#FF0000"&gt;&lt;STRONG&gt;WHERE&lt;/STRONG&gt;&lt;/FONT&gt; (which scope it applies to). A governed tag connects them.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="abac_overview.png" style="width: 999px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/23881i2177A3EDB30A763E/image-size/large?v=v2&amp;amp;px=999" role="button" title="abac_overview.png" alt="abac_overview.png" /&gt;&lt;/span&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;Here is the key difference. Look at the mask function for ABAC:&lt;/SPAN&gt;&lt;/P&gt;
&lt;PRE&gt;&lt;SPAN&gt;-- The function defines ONLY the WHAT&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;-- No is_account_group_member() check, no WHO logic&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;CREATE OR REPLACE FUNCTION silver.mask_email(email_&lt;/SPAN&gt;&lt;WBR /&gt;&lt;SPAN&gt;address STRING)&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;RETURNS STRING&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;RETURN CONCAT('****@', SPLIT(email_address, '@')[1]);&lt;/SPAN&gt;&lt;/PRE&gt;
&lt;P&gt;&lt;SPAN&gt;There is no &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;&lt;FONT color="#0000FF"&gt;is_account_group_member()&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt; call. The function does not care who is querying. It only knows how to mask an email address. The &lt;FONT color="#FF0000"&gt;&lt;STRONG&gt;WHO&lt;/STRONG&gt;&lt;/FONT&gt; is handled entirely by the policy.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H4&gt;&lt;SPAN&gt;Step 1: Tag the Column&lt;/SPAN&gt;&lt;/H4&gt;
&lt;P&gt;&lt;SPAN&gt;Governed tags are the bridge between your data and your policies. You apply a tag to any column that contains a certain class of data:&lt;/SPAN&gt;&lt;/P&gt;
&lt;PRE&gt;&lt;SPAN&gt;ALTER TABLE silver.customers&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;ALTER COLUMN email_address SET TAGS ('class.email_address');&lt;/SPAN&gt;&lt;/PRE&gt;
&lt;P&gt;&lt;SPAN&gt;Unity Catalog ships with system-defined governed tags like &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;&lt;FONT color="#0000FF"&gt;class.email_address&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;, &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;&lt;FONT color="#0000FF"&gt;class.us_ssn&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;, and others. You can also create your own through the Catalog Explorer UI or programmatically via the Databricks API.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H4&gt;&lt;SPAN&gt;Step 2: Create the Policy&lt;/SPAN&gt;&lt;/H4&gt;
&lt;P&gt;&lt;SPAN&gt;The policy ties it all together. It says: for a given scope, find all columns with a matching tag, apply this mask function, and apply it to these groups:&lt;/SPAN&gt;&lt;/P&gt;
&lt;PRE&gt;&lt;SPAN&gt;CREATE OR REPLACE POLICY pii_email_policy&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;ON SCHEMA silver&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;COLUMN MASK silver.mask_email&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;TO `account users`&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;EXCEPT `adminuser@mycompany.com`&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;FOR TABLES&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;MATCH COLUMNS hasTag('class.email_address') AS email_address&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;ON COLUMN email_address;&lt;/SPAN&gt;&lt;/PRE&gt;
&lt;P&gt;&lt;SPAN&gt;That is it. Every column in the &lt;STRONG&gt;&lt;FONT face="courier new,courier" color="#0000FF"&gt;silver&lt;/FONT&gt;&lt;/STRONG&gt; schema that carries the &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;&lt;FONT color="#0000FF"&gt;class.email_address&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt; tag is now masked for all members of &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;&lt;FONT color="#0000FF"&gt;`account users`&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&amp;nbsp;(everyone essentially) except for specific principals (users or service principals which are exempt from the policy).&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;You can also scope policies at the &lt;STRONG&gt;&lt;FONT color="#FF0000"&gt;catalog level&lt;/FONT&gt;&lt;/STRONG&gt;.&amp;nbsp; A single &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;&lt;FONT color="#0000FF"&gt;ON CATALOG&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;&amp;nbsp;policy would cover every schema and every table in the entire catalog.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;&lt;FONT color="#FF0000"&gt;Tag&lt;/FONT&gt;&lt;/STRONG&gt; columns once (which is a good practice anyway) -&amp;gt; define the &lt;STRONG&gt;&lt;FONT color="#FF0000"&gt;Policy&lt;/FONT&gt;&lt;/STRONG&gt; once -&amp;gt; That's it!&lt;/P&gt;
&lt;H3&gt;&lt;SPAN&gt;ABAC for Row Filters Too&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;Everything above focused on column masking, but ABAC works for row filters as well. The pattern is the same: write a function that defines the filter logic (the &lt;STRONG&gt;&lt;FONT color="#FF0000"&gt;WHAT&lt;/FONT&gt;&lt;/STRONG&gt;), tag the relevant column, and create a policy.&lt;/SPAN&gt;&lt;/P&gt;
&lt;PRE&gt;&lt;SPAN&gt;-- Function: WHAT only (returns TRUE for permitted rows)&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;CREATE OR REPLACE FUNCTION silver.filter_apac_only(region STRING)&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;RETURNS BOOLEAN&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;RETURN region = 'APAC';&lt;/SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;-- Tag the column&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;ALTER TABLE silver.customers&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;ALTER COLUMN region SET TAGS ('region_filter');&lt;/SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;-- Policy: WHO + WHERE&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;CREATE OR REPLACE POLICY filter_apac_rows_policy&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;ON CATALOG bakehouse&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;ROW FILTER silver.filter_apac_only&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;TO `apac-users`&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;FOR TABLES&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;MATCH COLUMNS hasTag('region_filter') AS region&lt;/SPAN&gt;&lt;BR /&gt;&lt;SPAN&gt;USING COLUMNS (region);&lt;/SPAN&gt;&lt;/PRE&gt;
&lt;P&gt;&lt;SPAN&gt;Now every table in the &lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;bakehouse&lt;/FONT&gt;&lt;/STRONG&gt; catalog that has a column tagged with &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;region_filter&lt;/STRONG&gt;&lt;/FONT&gt; will automatically restrict &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;apac-users&lt;/STRONG&gt;&lt;/FONT&gt; to APAC rows only. No per-table wiring needed.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;Deciding on the FGAC Approach&lt;/H2&gt;
&lt;P&gt;Manual column masks, row filters and dynamic views may still have their place in your FGAC strategy, the attached decision tree provides some guidance on what to use when and why.&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="fgac_decision_tree.png" style="width: 999px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/23882i450AAAD0944C3A06/image-size/large?v=v2&amp;amp;px=999" role="button" title="fgac_decision_tree.png" alt="fgac_decision_tree.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Things to Know Before You Start&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;1. Governed tags are not the same as regular tags. Regular tags (the ones you create implicitly with &lt;FONT color="#0000FF"&gt;&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;SET TAGS&lt;/FONT&gt;&lt;/STRONG&gt;&lt;/FONT&gt;) are for search and discovery. ABAC policies only work with governed tags, which are defined through the Catalog Explorer API/UI and have controlled allowed values.&lt;/SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;2. Manual masks and ABAC policies cannot coexist on the same column.&amp;nbsp; If a column already has a manual mask, you need to drop it before an ABAC policy can take effect. Run &lt;STRONG&gt;&lt;FONT face="courier new,courier" color="#0000FF"&gt;ALTER TABLE ... ALTER COLUMN ... DROP MASK&lt;/FONT&gt;&lt;/STRONG&gt; first.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;3. Only one row filter policy can apply to a table per user at a time. Plan your policy scoping accordingly.&lt;/SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;4. Policies inherit downward. A catalog-level policy covers all schemas and tables within it. A schema-level policy covers all tables in the schema. Use narrower scopes for exceptions.&lt;/SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;5. DBR 16.4+ or Serverless is required. Users on older runtimes cannot query ABAC-secured tables at all. If you have mixed runtime versions, consider scoping policies to specific groups so that users outside those groups can still access the tables on older runtimes.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Wrapping Up&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Manual masks and row filters are fine when you have a small number of tables. Dynamic views are useful when you are sharing data through Delta Sharing. But if you are operating at any real scale, ABAC is the approach that actually works long term.&lt;/SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;The core insight is simple: separate the &lt;STRONG&gt;&lt;FONT color="#FF0000"&gt;WHAT&lt;/FONT&gt;&lt;/STRONG&gt; from the WHO. Let the function be a pure transformation. Let the policy handle targeting. Let the tag be the glue. When your next data engineer creates a table and tags the email column, the mask is already there waiting for it.&lt;/SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;SPAN&gt;That is security that scales.&lt;/SPAN&gt;&lt;/P&gt;</description>
    <pubDate>Wed, 11 Feb 2026 21:32:33 GMT</pubDate>
    <dc:creator>jeffreyaven</dc:creator>
    <dc:date>2026-02-11T21:32:33Z</dc:date>
    <item>
      <title>Fine-Grained Access Control at Scale with ABAC</title>
      <link>https://community.databricks.com/t5/technical-blog/fine-grained-access-control-at-scale-with-abac/ba-p/147786</link>
      <description>&lt;P&gt;&lt;SPAN&gt;Manual column masks and row filters work at small scale, but you need a solution that scales, tag once, define a policy, enforce everywhere. This post covers how ABAC uses governed tags and policies to apply fine-grained access control across your catalog without wiring up every table individually.&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;</description>
      <pubDate>Wed, 11 Feb 2026 21:32:33 GMT</pubDate>
      <guid>https://community.databricks.com/t5/technical-blog/fine-grained-access-control-at-scale-with-abac/ba-p/147786</guid>
      <dc:creator>jeffreyaven</dc:creator>
      <dc:date>2026-02-11T21:32:33Z</dc:date>
    </item>
    <item>
      <title>Re: Fine-Grained Access Control at Scale with ABAC</title>
      <link>https://community.databricks.com/t5/technical-blog/fine-grained-access-control-at-scale-with-abac/bc-p/148102#M909</link>
      <description>&lt;P&gt;Really nice post. I have a wish that someday I would see this solution in our company.&amp;nbsp;&lt;a href="https://community.databricks.com/t5/user/viewprofilepage/user-id/43054"&gt;@jeffreyaven&lt;/a&gt;&amp;nbsp;Masking is great, but what about ABAC for table privileges? Are you planning something on this area?&lt;/P&gt;&lt;P&gt;For example:&lt;/P&gt;&lt;P&gt;I could tag the tables as domain = procurement. Then I could create a policy saying, if user is in group procurement they have select privileges. If user is in group procurement + procurement-dev then they have also insert/update etc. With column masking and row filtering we could get to interesting combinations &lt;span class="lia-unicode-emoji" title=":slightly_smiling_face:"&gt;🙂&lt;/span&gt;&lt;/P&gt;&lt;P&gt;The reason why I'm asking. With UC you can inherit privileges on tables from schema level. But if you want lower granularity you have to do it on table level. Then you are basically granting privileges on table by table which is not that great...&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Thu, 12 Feb 2026 01:34:49 GMT</pubDate>
      <guid>https://community.databricks.com/t5/technical-blog/fine-grained-access-control-at-scale-with-abac/bc-p/148102#M909</guid>
      <dc:creator>pepco-personal</dc:creator>
      <dc:date>2026-02-12T01:34:49Z</dc:date>
    </item>
    <item>
      <title>Re: Fine-Grained Access Control at Scale with ABAC</title>
      <link>https://community.databricks.com/t5/technical-blog/fine-grained-access-control-at-scale-with-abac/bc-p/148103#M910</link>
      <description>&lt;P&gt;thanks&amp;nbsp;&lt;a href="https://community.databricks.com/t5/user/viewprofilepage/user-id/204567"&gt;@pepco-personal&lt;/a&gt;&amp;nbsp;you raise some good points, at this stage ABAC implements FGAC at row or column level (at scale), table privs would still rely on permission assignments at catalog, schema or object level, the two work in concert (to override inheritance and protect data at column or row level).&amp;nbsp; I can do some more investigation however.&lt;/P&gt;</description>
      <pubDate>Thu, 12 Feb 2026 03:23:43 GMT</pubDate>
      <guid>https://community.databricks.com/t5/technical-blog/fine-grained-access-control-at-scale-with-abac/bc-p/148103#M910</guid>
      <dc:creator>jeffreyaven</dc:creator>
      <dc:date>2026-02-12T03:23:43Z</dc:date>
    </item>
  </channel>
</rss>

