Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,4 @@ nodes/node_renderer_capability_projection/0002_grant_omninode_runtime_renderer_c
nodes/node_projection_delegation/0037_delegation_events_uuid_mixed_representation_guard_before_set_role.sql node:node_projection_delegation node:node_projection_delegation tenant node:node_projection_delegation:0037_delegation_events_uuid_mixed_representation_guard_before_set_role.sql 9bd85df5dfc2cbb5079ae63a653462208a3260f3d60b78441e43a7a128d47e43
nodes/node_projection_delegation/0038_delegation_events_writer_identity.sql node:node_projection_delegation node:node_projection_delegation tenant node:node_projection_delegation:0038_delegation_events_writer_identity.sql df162d3593a5bd298ec1c4334d04e5507133d36d7a1544c36e749eb8ca6c7a07
nodes/node_projection_delegation/0039_delegation_aggregate_views_per_tenant.sql node:node_projection_delegation node:node_projection_delegation tenant node:node_projection_delegation:0039_delegation_aggregate_views_per_tenant.sql 1997a4c1219271efd3bb5589e8611a777d3d6b17ce96c528216f95caa2d7e0fc
nodes/node_projection_delegation/0040_delegation_aggregate_views_owner_realign.sql node:node_projection_delegation node:node_projection_delegation tenant node:node_projection_delegation:0040_delegation_aggregate_views_owner_realign.sql d94dbfec2fd9479f71bbe9689b94a7f9f74c00f0f28138da77fba9ec313a7a58
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
-- OMN-18159: the four aggregate views read as their INVOKER, not their owner.
--
-- WHAT THIS CLOSES
-- 0039 had to DROP and CREATE each view rather than CREATE OR REPLACE, because
-- Postgres refuses a replace that changes the column list and every one of
-- them gained `tenant_id`. A DROP discards the view's OWNER along with its
-- privileges. 0039 restored the privileges at its foot; it did not restore the
-- owner, and the owner is not a cosmetic property of a view.
--
-- WHY THAT MATTERS, AND WHY THIS IS A SECURITY FIX RATHER THAN TIDYING
-- By default a view reads its base tables with the privileges of the VIEW'S
-- OWNER. The role that reaches 0039 is not the role that created these views:
-- 0032, 0033 and 0034 each issue `RESET ROLE`, so everything applied after
-- them runs as the migration runner's own identity, which on a lane and in
-- this repo's fixtures is a SUPERUSER. `delegation_events` is FORCEd under
-- row-level security precisely so that even its owner is filtered -- but a
-- superuser is exempt from row-level security unconditionally. So after 0039 a
-- read through any of these four saw EVERY tenant's rows regardless of
-- `app.tenant_id`, which is the unscoped-serving leak the whole of OMN-18159
-- exists to remove, reintroduced by the mechanism that removed it.
--
-- MEASURED, not argued. Applying this node's migration directory to a clean
-- postgres:16-alpine under `SET ROLE <migrator>`:
-- through 0038: all four views owned by <migrator>, relacl NULL
-- through 0039: all four owned by `postgres`, while `delegation_events`
-- stays owned by <migrator>
-- It also denied the migrator's own reads -- three tests in
-- tests/test_omn18139_real_postgres_tenant_guc_representation.py failed with
-- `permission denied for view projection_delegation_summary` -- which is how
-- it was found.
--
-- WHY INVOKER RIGHTS RATHER THAN PUTTING THE OWNER BACK
-- Restoring the owner would fix today's instance and leave the class open: the
-- next migration that has to change one of these views' columns must DROP it
-- again, and would re-own it again. `security_invoker` makes the question moot
-- -- the view reads as whoever queries it, so the tenant policy on
-- `delegation_events` evaluates against the CALLER whatever the view's owner
-- happens to be. It is also expressible as four static statements; the
-- owner-realigning form needs `EXECUTE format(...)` to name a role resolved at
-- runtime, and the application-database SQL gate refuses a procedural block
-- whose relation targets cannot be proven statically, correctly.
--
-- WHO CAN STILL READ. Both grantees hold SELECT on `delegation_events` in
-- their own right -- `app_dashboard` from 0023, `tenant_projection_writer`
-- from node_projection_delegation_inference_response/0004 -- so invoker rights
-- take nothing away from either. They are the only two: before 0039 these
-- views carried no grants at all, and 0039 added exactly those two.
--
-- WHAT A CALLER SEES. The same thing it saw before 0039: rows for the tenant
-- its session scope names, and none without one. That is the intended contract
-- for the kernel's read seam, which sets the scope from the `tenant_id` filter.
--
-- IDEMPOTENT: `ALTER VIEW ... SET` is a no-op when the option already holds.

ALTER VIEW projection_delegation_summary SET (security_invoker = true);
ALTER VIEW projection_delegation_model_routing SET (security_invoker = true);
ALTER VIEW projection_delegation_quality_gate SET (security_invoker = true);
ALTER VIEW projection_delegation_token_usage SET (security_invoker = true);
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,22 @@ def test_checked_in_manifest_is_exact_and_all_blockers_are_explicit() -> None:
# as the row above -- the omnimarket source PR waits on this vendored
# migration, so the vendor parity gate can prove a clean redeploy creates
# the grouped views before the publisher reads them.
assert len(result.declarations) == 177
#
# 177 -> 178 for OMN-18159's
# nodes/node_projection_delegation/
# 0040_delegation_aggregate_views_owner_realign.sql. 0039 had to DROP and
# CREATE each of those four views -- a replace cannot change the column
# list -- and a DROP discards the view's OWNER. The role reaching 0039 is
# not the one that created them (0032, 0033 and 0034 each RESET ROLE), so
# they came back owned by the migration runner, a superuser, and a view
# reads its base tables as its owner. A superuser bypasses row-level
# security unconditionally, so every read through the four returned all
# tenants' rows regardless of app.tenant_id. 0040 realigns the owner back
# to delegation_events'. 0039's bytes are frozen (applied on the .201 dev
# lane with a recorded content_sha256 and declared here), so the repair is
# additive, which is also what makes it correct for a lane that already
# applied 0039.
assert len(result.declarations) == 178
assert result.blocked == ()
assert len(result.legacy_node_declarations) == 2
assert len(result.cloud_aliases) == 30
Expand Down
Loading