Repository navigation
Auto-provisioned gatekeeper accounts are identity-blind; a per-user scoped data gatekeeper needs the provisioning owner's identity (and eventually groups) #900
hogbenderest
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
What I'm building
A gatekeeper for querying a Cloudflare Basin warehouse (Iceberg on R2, via Basin SQL)
with governed, scope-checked SQL: guardrails, a free
EXPLAINplan verifier thatenforces the binding's grant per query, a daily scan budget, and observations on every
read. It supports two modes: per-user connect (each user pastes their own warehouse
credential) and deployment auto-provision (a shared credential in a Worker secret,
ambient singleton per user).
Auto-provision mode is the interesting one for governed data: consent moves from
connect-time to admin-time, so enforcement has to happen at the gatekeeper as the
query-time choke point. Today the scope of that enforcement can only be
deployment-wide (
BASIN_SCOPE), which means every user of a deployment gets the sameslice of the warehouse. For anything like per-table governance - finance tables for
finance, a public namespace for everyone - a deployment-wide scope is either
over-granting or useless.
The gap
The enforcement machinery is already per-binding: each user's ambient singleton is a
separate facet with its own props, and the plan verifier will enforce whatever scope
that facet carries. What is missing is the per-user decision: auto-provisioned
accounts are identity-blind by contract, so a gatekeeper cannot answer "which user is
this singleton for?":
GatekeeperVendor.createAccount()takes no arguments by design ("it takes no useridentity"), and the Workshop calls it without any (
#createAutoProvisionedAccount,packages/workshop-backend/src/user.ts).(
ownerDo.getSingletonGatekeeperClass(accountId)inoverseer.ts); the Workshoppasses nothing of its own. (Context's
sharingDomainis a deployment-staticservice-binding prop, not a per-user value.)
GatekeeperVendor.getSupportedResources(options.userId), documented as "a temporaryhack to enable a hidden internal gatekeeper".
edge, and nothing downstream consumes group membership.
A minimal, upstream-shaped mechanism
One optional flag on
AccountDescriptionand one optional method onGatekeeperUser:At auto-provisioning, right after
describe(), the Workshop callsnoteProvisionedOwner({ email: <the user's verified email> })when the descriptiondeclares the flag. Callers gate on the declared flag, matching the platform's
optional-method convention. The account can then resolve each user's grant (from an
operator-configured map keyed by email today, by group membership once a group source
exists) and mint the ambient singleton with that user's scope. The full design, a
working gatekeeper that consumes it, and a patch demonstrating the Workshop-side change
are in https://github.com/hogbenderest/gatekeeper-basin.
Why this shape
information for scoping, delivered by the trusted Workshop, not asserted by the
caller. (The gatekeeper should still treat it as scoping input and persist it
write-once, since whoever could rewrite the owner could rewrite the scope.)
gatekeepers that do not declare the flag.
the source of identity, and the gatekeeper never trusts the browser.
Follow-up the mechanism enables
Group/role plumbing. Once accounts can ask "what groups is my user in?", a data
gatekeeper can map
group -> scopeinstead ofemail -> scope, which is the shapeenterprise directory systems already maintain. The natural sources are the Access JWT
claims the deployment's identity provider already carries, or a SCIM-fed directory in
the Workshop's admin config. Neither needs to land with this proposal; the
email -> scopemap is sufficient and useful on its own.Alternatives considered
createAccount(owner)would change a public, security-reasoned contract method("takes no arguments, so it carries no user identity") rather than adding an
opt-in surface; the flag-gated note keeps that reasoning intact.
getSupportedResources(options.userId)would couple asecurity decision to a UI listing call that upstream explicitly calls a hack.
duplicate what gatekeepers are better placed to do: the gatekeeper is the choke
point that already holds the credential, the scope grammar, and the audit trail.
All reactions