“Just create another secure view.” For a long time, that was our answer to every data access request. An analyst needs to see customer data but without PII? Make a view.
The finance team needs a different slice? Make another view. Before we knew it, we had a tangled web of dozens of views, each with slightly different logic. When a privacy law changed, we had to manually update every single one. It was unmanageable, unauditable, and downright scary.
We weren’t managing a security policy; we were just patching holes.
We had to decouple the policy from the implementation. The breakthrough was realizing we could treat security as metadata and use dbt to enforce it dynamically at query time. The solution we built uses Google Cloud Data Catalog as our source of truth for policy and a clever dbt macro as our enforcement engine. First, we went through a process of “tagging our assets” in Data Catalog. We created a “Sensitivity” tag and applied it to any column containing PII, like email or phone_number. This became our centralized policy definition. Changing a tag here is like flipping a switch for the entire data warehouse.
The enforcement happens in a dbt macro we called mask(). When a user runs a query that uses one of our models, the mask() macro kicks in. It checks the session_user() against a simple permissions table we manage in dbt to see what their role is (e.g., ‘analyst’, ‘finance_auditor’). Then, it would (ideally, using the GCP API) check the Data Catalog tags for the column being requested.
If the column is tagged PII and the user’s role is analyst, the macro dynamically replaces the column’s value with ’MASKED’. Otherwise, it returns the real value. We apply this macro to the sensitive columns in our core models. Now, we manage one set of models and one set of policies. No more endless views, no more patching holes. Just clean, auditable, policy-as-code governance.