feat(portal, permissions): add portal operator URNs - #7377
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
44b35df to
ebb0129
Compare
| // from. | ||
| func seedAppWithKeyspaces(t *testing.T, h *testutil.Harness, workspaceID, slugBase string, keyspaceIDs []string) seededApp { | ||
| // appMapping addresses a portal at an app rather than a keyspace. | ||
| func appMapping(appID string) portal.Mapping { |
There was a problem hiding this comment.
nit: this util doesn't save LOCs and just adds abstractions, should we remove it?
There was a problem hiding this comment.
it's to save width, not lines. i copied it from keyspaceMapping in the other portal tests, but that seeds an actual api and this one is just a struct literal with a name on it. if that's the part that's weird to you, I'll inline it instead
| // This is the only control standing between a dashboard token and minting: the | ||
| // WorkOS admin role grants unkey:v1:{workspace}:**#*, which covers the portal | ||
| // session URN stage 1 evaluates, so without this any workspace admin could mint | ||
| // a session authenticating as an arbitrary end user. Naming every enum member | ||
| // keeps a future credential type failing the exhaustive linter until someone | ||
| // decides whether it may mint. |
There was a problem hiding this comment.
couldn't a workspace admin also create a root key with the required permissions and just mint sessions?
what does this protect against?
There was a problem hiding this comment.
yes, an admin can make a root key with the permission and mint with it. this isn't trying to stop that.
what it stops is the browser credential being a mint credential. a dashboard JWT is just sitting in the tab, so without this an XSS on the dashboard mints impersonation URLs for any end user immediately and leaves nothing behind. with it you have to create a root key first, which is deliberate and audit logged.
we're being very explicit about only a root key can be used to mint a session because minting from the dashboard is what we're calling "impersonation."
| // CanonicalScopeQueries returns the canonical form of the requirement | ||
| // [ScopeQueries] expresses in legacy tuples, for one scope on one keyspace. | ||
| // | ||
| // The ok flag is load-bearing: rbac.And over zero children evaluates to valid, |
There was a problem hiding this comment.
The ok flag is load-bearing
:panic-intensifies:
There was a problem hiding this comment.
ok but jokes aside, I have no idea what this entire comment is trying to tell me
There was a problem hiding this comment.
is it a question about the comment itself, or the intent of the comment?
because what it means is that rbac.And with no children returns true. it looks through the children for a failure, doesn't find one, passes (pkg/rbac/rbac.go:88).
So if I drop the ok flag and always add the URN check, a scope that has no URN form adds an empty And to the Or. that passes, so the whole check for that keyspace passes, and the caller needs no permission at all.
"load bearing" is just saying "don't delete this, it looks pointless and isn't."
b32e013 to
fb64959
Compare
What does this PR do?
Fixes ENG-3149
Portal routes only understood legacy dotted tuples, so when root keys move to URNs all five of them break. This adds portal to the URN catalog and makes it actually work.
portals.project_id, new and non-nullable. A portal's canonical name isprojects/{project_id}/portals/{portal_id}, so it needs a project. Always the project of the app or keyspace it maps to.Decisions
Project-wide grants now reach portals, including session minting, because
Coversstrips a trailing double wildcard and prefix-matches. Nothing had to name portals for that. No root key carries a URN today, so this only affects grants the migration creates.Cross-project re-points now 412. A portal's URN names its project, so changing it would move the portal under a different set of grants while keeping its id. Not an edge case: keyspaces land in the default project and apps take a named one, so app to keyspace re-points nearly always cross.
WorkOS Dashboard developer and viewer roles gained portal permissions. Before this, only admin could manage portals, via a hardcoded *# string. Added
projects/*/portals/* read/write/deleteto developer andreadto viewer. Can easily change this if we want to refine dashboard permissions a bit more.Type of change
How should this be tested?
All automated. The suites that matter:
Tests that encode a decision rather than just coverage:
TestCreateSessionRefusesDashboardTokensandTestDashboardTokensStillManagePortalsTestProjectWideGrantsDeliberatelyReachPortalsTestCreateSessionCeilingInheritsRerollURNWeakness, a weakness inherited from the reroll route, which maps its create and encryption arms to the same canonical key-write leaf. Mirroring it is intentional: a stricter ceiling would refuse mints the operator endpoint allows.412_test.go, the cross-project refusalChecklist
Required
pnpm buildpnpm fmtmise run fmtconsole.logsgit pull origin mainAppreciated