Partner-wide / system-level Software Library entries (currently strictly per-org) #633
Replies: 5 comments
-
|
Real pain, agreed — N-org MSPs hitting "clone every package per org" is exactly the kind of friction we don't want in the Software Library. Want to call out the RLS shape carefully before you start, since this is the part that's easy to get wrong: On the proposed SELECT policy. Allowing On the scripts RLS hole. Good catch — if
Your call — I'd lean toward the first since it's easier to review. On On Allowlist update. PR welcome once we've decided how to sequence the scripts fix. |
Beta Was this translation helpful? Give feedback.
-
|
Thanks, Todd. Taking your suggestion — going Path 1: a focused scripts.is_system RLS-fix PR first, then the software_catalog system-rows PR mirrors the now-correct pattern. Verified the hole on current main:
Plan for the fix PR, sanity-checking the shape before opening:
Quick clarifying question before I write the migration: do you want the same idempotency dance you've used elsewhere ( If reproduction holds and you confirm the migration idempotency preference, I'll open it as a tight stand-alone PR — no scope creep into the software_catalog work. |
Beta Was this translation helpful? Give feedback.
-
|
On idempotency: straight DROP POLICY IF EXISTS breeze_org_isolation_select ON scripts;
CREATE POLICY breeze_org_isolation_select ON scripts
FOR SELECT USING (is_system = true OR breeze_has_org_access(org_id));Re-running converges to the same final state, so it's idempotent in the sense the workflow requires. The Precedent for the approach is One caveat so you don't copy it verbatim: Also confirmed your reproduction independently: the Sequence and plan are right (failing rls-coverage test first, policy-replacement migration, INSERT/UPDATE/DELETE unchanged with system writes via |
Beta Was this translation helpful? Give feedback.
-
|
Draft PR up at #715. Path 1 — tight standalone scripts.is_system SELECT-policy fix per the sequence and idempotency you confirmed:
The software_catalog system-rows PR will mirror this corrected pattern after this one lands. |
Beta Was this translation helpful? Give feedback.
-
|
PR #715 merged. Greenlight on the |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Partner-wide / system-level Software Library entries (currently strictly per-org)
The pain
software_catalog.org_idisNOT NULLand RLS scopes every read/write to a single org. For an MSP partner with N organizations, every Software Library entry has to be manually created N times — once per org — even though the catalog content (vendor, download URL, silent-install args, SHA-256, file size) is identical across all of them.The cost grows with the partner. A 5-org MSP curating 50 packages needs 250 catalog rows + their version rows; a 60-org MSP needs 3,000. Worse, every new org added later starts with an empty Software Library and has to have the entire catalog cloned into it before the operator can deploy anything to that org's devices. Every package update, URL refresh, or silent-args tweak becomes an N-place maintenance problem.
This mirrors a gap that already exists in the
scriptstable — and which Breeze partially solved there with theis_systemflag, but with an unfixed-upstream RLS hole that makes is_system=true rows invisible to non-system users (see "Note on the scripts equivalent" below).Proposal — mirror the scripts pattern, fix the RLS hole at the same time
Schema:
Routes (
apps/api/src/routes/software.ts):GET /catalog: optionalincludeSystem=truequery param; when set, the WHERE clause becomes(software_catalog.org_id = :orgId OR software_catalog.is_system = true). Default behavior unchanged for backwards compat.POST /catalog: onlyauth.scope === 'system'may setisSystem: true. WhenisSystem: true,org_idis forced to NULL.PUT /catalog/:id,DELETE /catalog/:id: only system scope can mutate system rows.Versions follow the catalog.
software_versions.catalog_idalready FK's to a UUID, no changes needed — a system catalog row's versions are visible to anyone who can see the catalog row.Inventory matching (
software_inventory.catalog_id) keeps working: when a device's installed software matches a system catalog row by name+vendor, the inventory row gets the system catalog id. Inventory's own RLS is bydevice_id/org_id, so a Tucker device sees its own inventory pointing at a system row that everyone-in-the-partner sees.Deployments (
software_deployments) keep working: a deployment record is org-scoped (software_deployments.org_id), and references asoftware_version_idwhose catalog row may now be either org-scoped or system. FK is still satisfied; the deployer just gets a longer list of versions to choose from.Note on the scripts equivalent (already partially shipped, with a gap)
scripts.is_systemalready exists upstream, andapps/api/src/routes/scripts.tsalready filters withor(eq(scripts.orgId, query.orgId), eq(scripts.isSystem, true))whenincludeSystem=true. But the SELECT RLS policy onscriptsisbreeze_has_org_access(org_id), which evaluates tofalsefororg_id IS NULL— so partner / org users can never see is_system=true scripts under upstream RLS. The handler-level OR is dead-letter behind that policy.Self-hosted operators currently have to add a permissive
scripts_system_visible_to_allpolicy themselves. Folding that fix into the same upstream PR (or as a sibling commit) would close that hole at the same time.Why this over per-org replication
Per-org replication works as a stop-gap, but the maintenance cost is O(orgs × packages) and every newly-onboarded org starts at zero until somebody re-runs the cloning script. A NULL-org system row is one place; new orgs inherit it the moment they're created.
Backwards compat
org_id NOT NULLsemantics (the CHECK constraint enforces it).GET /catalogwithoutincludeSystem=truereturns exactly today's result set.POST /catalogwithoutisSystem: trueis unchanged.Scope of PR
Roughly:
software.ts(~3 lines)scriptsRLS hole the same wayI have a complete patch drafted locally; happy to PR if there's interest. Wanted to surface as a Discussion first since this has an opinionated design choice (NULL-org vs. partner-scoped).
Alternative considered: partner-scoped catalog
Add a
partner_idcolumn instead ofis_system, with a "visible to anyone in this partner" semantic. More fine-grained for SaaS Breeze, less useful for self-hosted single-partner deployments. Theis_systemmirror is simpler and matches existing precedent. Could be a v2.Beta Was this translation helpful? Give feedback.
All reactions