Crosshire
Unity Catalog topology
Govern15 Unity Catalog topology · 2 of 3

Ownership and the storage doors

Two questions decide whether the access model holds: who owns each production object, and who can create a path to the bytes underneath it.

Stand 29.07.2026

#Production is owned by groups

What
Every Unity Catalog securable has exactly one owner. The owner holds every privilege on it implicitly — including DROP, including the right to grant to anyone, including MANAGE, which is what lets a principal attach or remove the row-filter and column-mask policies protecting the object. Ownership is a governance control, not a metadata field.
The default is that whoever ran CREATE owns it. In production that means the deploy service principal owns most things, which is fine — provided it was decided rather than inherited. Everything else in dwh_prod is owned by a group.
Why
The failure mode is an offboarding. Someone owns gold.fct_order_line because they created it in a hurry in month two. They leave, the identity is deprovisioned, and the table now has an owner that does not resolve: nobody can alter its schema, nobody can grant on it, and repairing it requires the metastore admin — the one account you were trying not to use for daily work. It surfaces on a Tuesday, in the middle of a release that needs an ALTER TABLE.
The quieter version costs more. The object keeps working, the access review lists an owner nobody recognises, and the answer to 'who is accountable for this number?' is a person who left the company. Group ownership makes that question answer itself.
How
sql
Ownership is deployed, and it is checkable
ALTER CATALOG dwh_prod                     OWNER TO `dwh_platform_owners`;
ALTER SCHEMA  dwh_prod.gold                OWNER TO `dwh_gold_owners`;
ALTER TABLE   dwh_prod.gold.fct_order_line OWNER TO `dwh_gold_owners`;
ALTER TABLE   dwh_prod.gold.dim_customer   OWNER TO `dwh_gold_owners`;

-- zero rows = pass. Run it in CI, not once a quarter.
SELECT table_name, table_owner
FROM   dwh_prod.information_schema.tables
WHERE  table_schema = 'gold'
  AND  table_owner NOT IN ('dwh_gold_owners', 'sp_dwh_deploy');
The groups must be account-level groups. Where they come from does not matter — created in the account console, or provisioned from the identity provider — but the level does: workspace-local groups cannot be granted access to Unity Catalog data and cannot own metastore-level securables, and they are indistinguishable from account groups in the workspace admin screen.
Doing it
  • Set OWNER TO a group in the same DDL that creates the object, not in a cleanup pass afterwards
  • Use account-level groups onlyCheck that the group in your GRANT is not a workspace-local one with the same name.
  • Run the owner query in CI against dwh_prod and fail the build on any row
  • Give each owning group at least two members, and confirm both are still employed at every access review
Embedding it
  • Ask who owns gold.fct_order_lineA name means you have found the work; a group means ask who is in it and when they last checked.
  • Run the offboarding drill in the sandboxDeprovision a test identity that owns a table and let the team discover what stops working. An hour, and the argument never comes back.
  • Transfer ownership of dwh_dev to their group in week oneThey then operate the model rather than receive it at handover.
Defaults
  • Groups own everything in dwh_prod, including 'temporary' objects
  • The deploy service principal owns what it creates, deliberately, and its identity is written down
  • Account-level groups only, whatever provisions them; workspace-local groups are not part of the model
  • Ownership asserted in CI, because it drifts through CREATE rather than through ALTER
Gotchas
  • An orphaned owner after offboardingThe securable becomes unmodifiable and only a metastore admin can repair it — discovered by a failing release, never by a review.
  • Ownership drift is silent and continuousEvery new table starts owned by whoever ran the statement, so a scheme fixed by hand is wrong again after the next sprint. That is why the check belongs in CI.
  • An owner can remove the protection on their own objectMANAGE comes with ownership, so the group owning dim_customer can drop the column mask on email. If the owning group is wider than the group cleared for personal data, the mask is decoration.
  • Workspace-local groups, where the GRANT appears to succeed in the workspaceThe scheme works right up to the first metastore-level securable or the second workspace, at which point the group simply is not there.
  • The ownership check passing because it saw nothinginformation_schema returns only the objects the querying principal can see, so a CI service principal without USE SCHEMA on dwh_prod.gold gets zero rows — the same zero rows that mean pass. Assert the row count of the schema first, then assert the violation count is zero; a check that cannot fail is worse than no check, because it is now on the evidence list.

#External locations are the other door

What
Whether a table is managed or external is decided in the storage & cost route: managed everywhere, with two named exceptions, both in bronze. What belongs here is the pair of metastore-level securables behind those exceptions, because they are the only objects in Unity Catalog that grant access to bytes rather than to rows.
A landing zone another system writes into
The webshop drops JSON there, Autoloader reads it, and bronze.shop_order is still a MANAGED table — the files are external, the table is not.
A dataset a non-Databricks engine must read in place
Where a managed copy would mean two masters, and no agreement about which of them is wrong.
Those are the two, and both stop at bronze. Everything from silver upward is managed, which gives the governance review a hard test rather than a judgement call: every external location on this metastore must be traceable to one of those two sentences. One that is not is a door somebody opened for a reason nobody can now reconstruct — and unlike a stale grant, it will not show up in a table-privilege review.
The two securables that reach storage
ObjectWhat it isCreate privilegeWhat a grant on it gives
Storage credentialthe cloud identity Unity Catalog uses to reach storageCREATE STORAGE CREDENTIAL on the metastorethe ability to define locations over anything that identity can reach
External locationa path, plus the credential that reads itCREATE EXTERNAL LOCATION on the metastoreREAD FILES · WRITE FILES · CREATE EXTERNAL TABLE · CREATE EXTERNAL VOLUME
Why
A column mask on dim_customer.email is enforced when someone queries the table. It is not enforced on the files. Anyone holding READ FILES on an external location covering a table's path can read the Delta files directly — every masked column, every filtered row — and no table access event is recorded, because they never touched the table.
That is the strongest argument for managed tables that has nothing to do with maintenance: a managed table has no path you were handed and no external location over it, so the table is the only door. Where external is genuinely required, the location grant is part of the permissions concept, not a plumbing detail.
Above it sits the escalation. Whoever can create a storage credential can create locations over anything that cloud identity can reach — which, on a credential someone scoped broadly to unblock a Tuesday, can be an entire storage account including other teams' data. CREATE STORAGE CREDENTIAL is not a convenience permission.
How
sql
The webshop landing zone — created once, by the platform group
CREATE EXTERNAL LOCATION shop_landing
  URL 'abfss://landing@example.dfs.core.windows.net/shop_order/'
  WITH (STORAGE CREDENTIAL cred_landing)
  COMMENT 'Webshop JSON drop zone. Backs the external volume bronze.landing_shop; read by the Autoloader that loads bronze.shop_order.';

ALTER EXTERNAL LOCATION shop_landing OWNER TO `dwh_platform_owners`;

-- the ingestion service principal reads it. No human gets anything.
GRANT READ FILES ON EXTERNAL LOCATION shop_landing TO `sp_dwh_ingest`;
Doing it
  • Grant CREATE EXTERNAL LOCATION and CREATE STORAGE CREDENTIAL to one named groupEveryone else raises a request against a written path.
  • Scope each credential to the narrowest container the cloud provider allowsThen scope the external location narrower still.
  • Grant READ FILES to service principals only. A human who needs the data needs a table
  • Sweep SHOW EXTERNAL LOCATIONS quarterly and make each entry name the process that reads it, in its COMMENT
Embedding it
  • Demonstrate the bypass once, in dev, on a deliberately external tableMask a column, then read the same table's files through an external location. It ends the 'isn't the mask enough?' conversation in one sitting.
  • Have the team write the request-and-approval path for a new external location before the first request arrivesA control invented under time pressure gets granted under time pressure.
  • Make the quarterly location sweep theirs, and have them justify each entry to each other rather than to you
Defaults
  • Two metastore-level create privileges, one group, written into the permissions concept
  • READ FILES to service principals; humans read tables
  • Managed everywhere data is not written by another systemThe access-control argument and the maintenance argument point the same way.
Gotchas
  • READ FILES over a table's path bypasses column masks, row filters and the table audit event in one moveNothing errors, nothing appears in a table-grant review, and the person doing it usually believes they are being efficient.
  • A storage credential scoped to the storage account instead of one containerIt works on day one, so nobody revisits it, and every external location created with it afterwards can be pointed anywhere in that account.
  • External locations and credentials left visible to every workspaceThe catalog boundary you built holds; the storage boundary underneath it does not, and the dev workspace can reach the production landing zone.
  • READ FILES granted to a person 'just for debugging'It is never removed, it does not show up in any table-privilege review, and it is invisible to a governance scheme built entirely on table grants.