feat: gate workspace ACL grants on a use_shared capability#27281
Draft
jscottmiller wants to merge 5 commits into
Draft
feat: gate workspace ACL grants on a use_shared capability#27281jscottmiller wants to merge 5 commits into
jscottmiller wants to merge 5 commits into
Conversation
Adds a new action, use_shared, that acts as a capability precondition for ACL entries. The rego policy only grants ACL-based access to an object when the subject also holds the object type's use_shared action at the member level in the object's organization. This prevents sharing workspaces with users who lack workspace capabilities, such as AI-Gateway-only accounts, and revokes shared access live when the capability is revoked. The mechanism is declaration-driven: any resource type that declares ActionUseShared in its policy action set is gated, with no rego edits per type. The flag is derived from policy.RBACPermissions in Go (rbac.ACLUseGated) and passed as the input field input.object.acl_use_gated. Because it is computed from the object type alone, it is known during partial evaluation and the precondition resolves at prepare time; residual queries and the regosql conversion are unaffected. The field is included in the AST fast path, the partial input, and Object's JSON encoding so all input paths stay consistent. Currently gated: workspace and workspace_dormant. The capability travels with organization-workspace-access (use_shared is included in the dormant action list so ACL recipients keep access to shared workspaces that go dormant). shared_use_orgs follows standard vote semantics: a negated member-level use_shared permission removes the org even when a positive grant exists. Share-time validation in patchWorkspaceACL rejects recipients who fail the capability check with a validation error, so shares don't silently grant nothing; access-time enforcement still applies if the capability is revoked later. New api_key_scope enum values are added via migration 000542 and remain internal-only (not in the external scope catalog). Documented in POLICY.md.
… actions The admin workspace ACL role expands from AvailableActions(), so adding use_shared to the workspace action set silently grew the stored action list and broke the exact-set match in convertToWorkspaceRole: admin entries stored before the change rendered as role-less (deleted) in ACL API responses, and a read-modify-write client would then remove the share. use_shared is a capability precondition evaluated from the subject's role permissions, never from ACL entries, so storing it in an ACL action list is inert. Omitting it restores round-tripping for pre-existing entries.
…hare time Group membership changes after a share is created, so per-member use_shared eligibility is enforced at access time only. A share to a group whose members all currently lack the capability is accepted and grants nothing until a member gains it.
not input.object.acl_use_gated evaluates true when the field is undefined, so an input path that omitted the field would skip the ACL use_shared precondition entirely. An explicit == false comparison is undefined on a missing field, failing the rule and denying the ACL grant. All current input paths set the field; behavior is unchanged for them.
…vior, skipped Converts the dormancy ACL reproduction into a desired-behavior test: recipients keep access to a shared workspace through dormancy, an admin-role recipient can wake it, and the list and direct-access views agree. Skipped while DormantRBAC() attaches no ACLs; removing the skip activates the test once it does.
Docs preview📖 View docs preview for |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a new workspace action,
use_shared, that acts as a capability precondition for ACL entries: the rego policy only grants ACL-based access to an object while the subject holds the object type'suse_sharedaction at the member level in the object's organization. This prevents sharing workspaces with users who lack workspace capabilities (such as AI-Gateway-only accounts) and revokes shared access live when the capability is revoked, without deleting the ACL entry; regaining the capability restores the share.Mechanism
Declaration-driven: any resource type that declares
ActionUseSharedin its policy action set is gated, with no per-type rego edits. The flag is derived frompolicy.RBACPermissionsin Go (rbac.ACLUseGated) and passed as the input fieldinput.object.acl_use_gated, using an explicit== falsecomparison so input paths that omit the field fail closed. Because the flag is computed from the object type alone, it is known during partial evaluation and the precondition resolves at prepare time; residual queries and the regosql conversion are unaffected. The field is included in the AST fast path, the partial input, andObject's JSON encoding so all input paths stay consistent.Currently gated:
workspaceandworkspace_dormant. The capability travels withorganization-workspace-accessand, whileMinimumImplicitMemberis off, with the elevation bundled intoorganization-member, so default deployments see no behavior change.shared_use_orgsfollows standard vote semantics: a negated member-leveluse_sharedpermission removes the org even when a positive grant exists.API surface
PATCH /workspaces/:id/aclrejects user recipients who fail the capability check with a validation error, so shares don't silently grant nothing. Group entries are intentionally not validated at share time (membership changes after the share); eligibility is enforced per-member at access time.use_sharedis omitted from theadminworkspace ACL role's stored action set: it is a role-carried capability that ACL entries never grant, and including it would break the exact-set round-trip of previously stored admin entries inconvertToWorkspaceRole.api_key_scopeenum values are added by migration and remain internal-only (not in the external scope catalog).Includes a skipped desired-behavior test (
TestWorkspaceSharingDormancySurvivesACL) documenting a pre-existing bug whereDormantRBAC()drops ACLs; the precondition neither causes nor fixes it. Documented incoderd/rbac/POLICY.md.Part of the gateway-accounts exploration.
Stack
Part 3 of the gateway-accounts stack. Each PR builds on the previous:
permission-based-licensingexperiment and gated on the AI Governance add-on,user_limitcounts only users the RBAC engine authorizes to create workspaces.organization-ai-gateway-accessorg role carrying the AI Bridge interception permissions (extracted from the member floors, backfilled into org default roles by migration) and enforces it at AI Gateway authentication; bridge usage stops claiming AI Governance seats under the experiment.use_sharedcapability precondition for workspace ACL grants, so workspace sharing is ineffective for (and rejected toward) users without workspace capabilities, evaluated live on every authorization.Related but independent: #27278 hides the Workspaces page create CTAs for users without workspace-create permission.