Repository navigation
🤖 feat: Share mode for CoderWorkspacePool claims (#153 PR 14) - #446
Conversation
|
@codex security review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
Security findingsAdvisory findings (6)
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
🛡️ Codex Security Review
Here are some automated security review suggestions for this pull request.
Reviewed commit: 06b99befc8
ℹ️ About Codex security reviews in GitHub
This is an experimental Codex feature. Security reviews are triggered when:
- You comment "@codex security review"
- A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review
Once complete, Codex will leave suggestions, or a comment if no findings are found.
Dogfood: Share mode by hand on a kind-dev.sh clusterCluster: Terminal recording (asciinema, rendered with agg at half speed, idle gaps capped): dogfood.mp4What it shows, in order:
The shared workspace's page as The same list after the cleanup: nothing is shared with |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 06b99befc8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ows it (#153 PR 14) Codex security review on #446: a user removed from every shareGroupIDs group, or an owner whose consent the admin withdrew, kept the use role. The member now checks the share policy at every poll and removes the operator's share once it fails, reports ShareRevoked, and never shares again. A failed Coder read keeps the share. Change-Id: Ib2c4111eaa8369f34f1f3a196e1b7325641bee50 Signed-off-by: Thomas Kosiewski <tk@coder.com>
|
@codex review |
|
@codex security review |
|
Codex Review: Didn't find any major issues. Swish! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
🛡️ Codex Security Review
Here are some automated security review suggestions for this pull request.
Reviewed commit: 4d2652b858
ℹ️ About Codex security reviews in GitHub
This is an experimental Codex feature. Security reviews are triggered when:
- You comment "@codex security review"
- A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review
Once complete, Codex will leave suggestions, or a comment if no findings are found.
Codex security review on #446: any claim author can add claims that the policy refuses, and the pool checked each on every reconcile, with a user read and a read of each listed group, through its one worker. The pool now reads each group once per reconcile, keeps a refusal for one poll, and stops the checks at Coder's 429. Change-Id: If96ac4f7c5495723c14577f841cfdcf92be462f8 Signed-off-by: Thomas Kosiewski <tk@coder.com>
|
@codex review |
|
@codex security review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7179222034
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
🛡️ Codex Security Review
Here are some automated security review suggestions for this pull request.
Reviewed commit: 7179222034
ℹ️ About Codex security reviews in GitHub
This is an experimental Codex feature. Security reviews are triggered when:
- You comment "@codex security review"
- A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review
Once complete, Codex will leave suggestions, or a comment if no findings are found.
|
@codex review |
|
@codex security review |
There was a problem hiding this comment.
🛡️ Codex Security Review
Here are some automated security review suggestions for this pull request.
Reviewed commit: 2eaa88780f
ℹ️ About Codex security reviews in GitHub
This is an experimental Codex feature. Security reviews are triggered when:
- You comment "@codex security review"
- A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review
Once complete, Codex will leave suggestions, or a comment if no findings are found.
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
…embers (#153 PR 14) CoderControlPlane spec.workspacePools.owners[].shareGroupIDs turns on Share mode for an owner. CoderWorkspacePool claims.mode accepts Share. CoderWorkspaceClaim gets spec.share.user and status.sharedUserID, and CoderWorkspacePoolMember gets status.sharedUserID and the Shared condition. Generated deepcopy, CRDs, install bundle, and API reference. Change-Id: I946f8f3d0e916d3baa5819e8b4c8ae54712964fb Signed-off-by: Thomas Kosiewski <tk@coder.com>
…drops on a tightened setting (#153 PR 14) Change-Id: I425ef9f9053e6fbef6057d5661d05dadd76c7f51 Signed-off-by: Thomas Kosiewski <tk@coder.com>
…organization's sharing policy (#153 PR 14) The pool checks the policy at resolve (ShareNotAllowed) and the claim's user before a bind (ShareUserNotFound, ShareUserNotEligible). The member checks both again right before its ACL write, stores the user ID before the write, grants only use, and reports the share from Coder's ACL in the Shared condition (ShareRefused, ShareRemoved). It never changes a sharing setting and never shares a second time. Change-Id: Ib876665ca9350127372083d1269d71a46dc668be Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: If0efd89adaad2590ce35a700ed21aafcdd8767c2 Signed-off-by: Thomas Kosiewski <tk@coder.com>
…e stubs for it (#153 PR 14) Change-Id: Ife15ccf6c8bdbcb7b3d06251e898c089174b582d Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I0d2ce2869eb4cfea5a327eaeb167f0295ae1ca9e Signed-off-by: Thomas Kosiewski <tk@coder.com>
…ows it (#153 PR 14) Codex security review on #446: a user removed from every shareGroupIDs group, or an owner whose consent the admin withdrew, kept the use role. The member now checks the share policy at every poll and removes the operator's share once it fails, reports ShareRevoked, and never shares again. A failed Coder read keeps the share. Change-Id: Ib2c4111eaa8369f34f1f3a196e1b7325641bee50 Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I9c392a6dbc1a8286e19285e03ca6019950126e47 Signed-off-by: Thomas Kosiewski <tk@coder.com>
…and honor a deployment-wide sharing disable (#153 PR 14) Change-Id: Ie5a37cd9a00218c468698450a4c5c5c66ddc501a Signed-off-by: Thomas Kosiewski <tk@coder.com>
Codex security review on #446: any claim author can add claims that the policy refuses, and the pool checked each on every reconcile, with a user read and a read of each listed group, through its one worker. The pool now reads each group once per reconcile, keeps a refusal for one poll, and stops the checks at Coder's 429. Change-Id: If96ac4f7c5495723c14577f841cfdcf92be462f8 Signed-off-by: Thomas Kosiewski <tk@coder.com>
…the rest Change-Id: I70abee16dd8b1fc8426a2a872a6c9bed52e9913b Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I5e227e8c7b8b5931c1aaff0792e5b9cf6ae7e5e7 Signed-off-by: Thomas Kosiewski <tk@coder.com>
…hile sharing is not allowed (#153 PR 14) A person can change the shared user's role from use to admin in Coder. The member now finds the stored user in the ACL with any role, so it keeps reporting the share, never writes the role back, and still removes the share once the policy no longer allows it. ShareRemoved now means the user is gone from the ACL. While the pool's resolve reports ShareNotAllowed, no claim of the pool can get its share. Each waiting claim now waits with that reason and is no demand, so a pool at replicas 0 deletes the unclaimed member it built for the claim. Codex review, round 3. Change-Id: Id79f117386b4a5e8c5586969fcbd3fdcc6b39f2e Signed-off-by: Thomas Kosiewski <tk@coder.com>
Members make no Coder call once the control plane's delete starts (plan 6.5 item 3), so they stop checking and removing shares. Tell admins to delete the Share claims first to end every share. Change-Id: I96bcde3fb0c4f0d6ad659e1c47100f4b3459da9e Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I46a2c1e70bf319585cdb851ae991c132ff66bc5a Signed-off-by: Thomas Kosiewski <tk@coder.com>
2eaa887 to
58c21ec
Compare
|
@codex review |
|
@codex security review |
There was a problem hiding this comment.
🛡️ Codex Security Review
Here are some automated security review suggestions for this pull request.
Reviewed commit: 58c21ecb55
ℹ️ About Codex security reviews in GitHub
This is an experimental Codex feature. Security reviews are triggered when:
- You comment "@codex security review"
- A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review
Once complete, Codex will leave suggestions, or a comment if no findings are found.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 58c21ecb55
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…d shared users open only subdomain apps Coder applies the removal of a share only when the workspace restarts, and the operator does not restart or stop it. The ShareRevoked and ShareRemoved messages now start with that, before the reason, because the claim keeps only the first 256 bytes, and they tell the admin how to end the access at once. The how-to says the same, and that a shared user can open only subdomain apps on a deployment with a wildcard access URL. #450 tracks an opt-in operator stop. Codex review on 58c21ec. Change-Id: I35b28230ebfacb626ccfca5ca998f11143df4f4f Signed-off-by: Thomas Kosiewski <tk@coder.com>
|
@codex review |
|
@codex security review |
|
Codex Review: Didn't find any major issues. What shall we delve into next? Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
🛡️ Codex Security Review
Here are some automated security review suggestions for this pull request.
Reviewed commit: 2924f36b4b
ℹ️ About Codex security reviews in GitHub
This is an experimental Codex feature. Security reviews are triggered when:
- You comment "@codex security review"
- A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review
Once complete, Codex will leave suggestions, or a comment if no findings are found.



Summary
Plan PR 14 of #153: Share mode for
CoderWorkspacePool. A claim on a pool withclaims.mode: Sharenames one Coder user inspec.share.user. The operator binds a ready workspace as in Reserve mode, then gives that user the Coderuserole on it (apps, SSH, start, stop; never delete). The operator's token is a site owner, and Coder lets a site owner share where the pool owner could not, so the controller enforces the organization's sharing setting and the admin's per-ownershareGroupIDsitself. It never changes a sharing setting.Refs #153
Background
Coder has no ownership transfer for normal workspaces, so pool workspaces stay with the pool owner (plan 3.1, 3.2). The branch is rebased on
mainwith PR 13 (#445) and PR 15a (#447, unclaimed-age refresh and idle reaping). Reserve mode (PR 12, #441) serves automation that already holds the owner's credentials. Share mode serves people, as an admin opt-in per owner (decision D2), because every claimant can act as the pool owner if the template exposes the owner's token (threat T3).R3: groups need a license (design decision, agreed before implementation)
Every Coder group route needs Premium. In v2.37.2 and v2.37.3,
/api/v2/groups*and/organizations/{org}/groupsruntemplateRBACEnabledMW(enterprise/coderd/coderd.go:514-520,631-641). On the unlicensed Kind Coder,GET /api/v2/groups/<org ID>andPOST /organizations/<org>/groupsboth answered 403 "Template RBAC is a Premium feature. Contact sales!" (probed live). The ACL routes andGET/PATCH /organizations/{org}/settings/workspace-sharinghave no license gate (coderd/coderd.go:1884-1888,enterprise/coderd/coderd.go:494-497).So a
shareGroupIDsentry equal to the template's organization ID counts as that organization'sEveryonegroup, checked as organization membership with no group read. Only the template's organization counts: another organization's ID is read like any group. Coder source for the equivalence (v2.37.3, same in v2.37.2):coderd/database/queries/groups.sql:169-176:InsertAllUsersGroupcreates the groupEveryonewithid = organization_id.coderd/database/modelmethods.go:849-851:Group.IsEveryone()isg.ID == g.OrganizationID.coderd/database/dump.sql:2501-2533: viewgroup_members_expandedadds every organization member to the group whose ID is the organization ID.Any other group is read with
GET /api/v2/groups/{id}. A 403 (no license) or 404 refuses the user, and the message names the group. Other failures wait asCoderUnavailable.Implementation
API (generated deepcopy, CRDs, install bundle, and reference docs updated):
CoderControlPlane.spec.workspacePools.owners[].shareGroupIDs: lower-case UUIDs, at most 16. Empty means Share mode is off for the owner.CoderWorkspacePool.spec.claims.modeacceptsShare(still immutable).CoderWorkspaceClaim.spec.share.user: a Coder username (at most 32 characters) or a lower-case UUID, never the prebuilds system user.status.sharedUserIDreports the user while Coder shows the share.CoderWorkspacePoolMember.status.sharedUserID(lower-case UUID, prebuilds user refused) and a newSharedcondition.Share policy (
internal/controller/coderworkspacepool_share.go, plan 6.8):everyoneallows,service_accountsallows only a service-account owner,noneor any other value refuses (ShareNotAllowed). A deployment-wide disable (sharing_globally_disabled) refuses too. Coder v2.37 then also answersnone.ShareUserNotFound), not the prebuilds user, active, a member of the template's organization, and in one of the owner's groups (ShareUserNotEligible).Pool reconciler:
ShareNotAllowedand builds nothing. Its waiting claims then wait with the same reason and are no demand, so a pool atreplicas: 0deletes the unclaimed members it built for them.Pendingwith its reason, never binds, and is no demand, so the pool builds no workspace for it. A claim whose check could not read Coder still counts as demand, so an outage does not delete the member built for it. Any claim author can add refused claims, so the pool reads each listed group once per reconcile, keeps a refusal for one poll (60 s), and stops the checks at Coder's 429: a refused claim costs about one Coder request per minute.spec.sharedoes not match the pool mode never binds, is no demand, and isStalledwithClaimModeMismatch.Readyonly while the member'sSharedcondition is True. It isStalledwithShareRefusedorShareRemoved, elseSharingWorkspace.Member reconciler (plan 6.2 step 11 rule 2), at every poll after the one bind extension and the identity guard read, and at once after a control plane change:
status.sharedUserID, never the claim's username again, so a rename or a reused username never moves the share.Shared=True(also after a crash that lost the status write). The operator writes onlyuse. A role that a person changed later, such asadmin, still counts, and the operator never writes it back.shareGroupIDsis empty, the owner leftowners, the user is suspended): one write{"user_roles":{"<id>":""}}removes the share,Shared=FalseShareRevoked(ShareRevokinguntil the removal works). A Coder read that fails keeps the share. Coder applies a share removal only when the workspace restarts, and the operator does not restart or stop it, so the message says that the user keeps access until then and how to end it at once (restart or stop the workspace, or delete the claim).ShareRemoved, with the same note on access until a restart. AfterShareRevokedorShareRemovedthe operator never shares again.PATCH /api/v2/workspaces/{id}/aclwith exactly{"user_roles":{"<id>":"use"}}. A refusal, a 403 included, isShareRefused, and the member keeps polling every 60 s.The member starts the claim informer in its own
SetupWithManager, because it reads its claim through the cache.Kind phase P9 (
hack/e2e-workspace-pool.sh, after P8, before the final cleanup): a second password user, created and logged in once, as the Share user. A Share pool withoutshareGroupIDsreportsShareNotAllowedand makes no member. With a non-Everyone group, the claim waits withShareUserNotEligibleand the message names the group. With the organization set tonone, the pool reportsShareNotAllowedand the claim does not bind. Witheveryoneand the organization ID, the claim isBoundandReady,GET /acllists only the Share user withuse, the Share user's token reads the workspace, and a delete build with that token answers 403. When the admin then emptiesshareGroupIDs, the member removes the share: the ACL is empty and the claim reportsShareRevoked. The phase then deletes its claim and pool, checks their workspaces deleted in Coder, and restores the sharing setting. The final cleanup still restoresspec.workspacePools. The driver keeps its two Coder users for a rerun. P1 now creates its owner through the samepassword_userhelper.Offline stubs (
hack/e2e-workspace-pool_test.sh): the fake Coder and controller model Share mode, and 7 new negative controls each break one P9 behavior (policy ignored without groups, group not checked,noneignored,admininstead ofuse, Share user cannot read, Share user can delete, share not revoked). The leak check covers both users' passwords and sessions.Docs: a "Share a workspace with a person" section in
docs/how-to/workspace-pools.md: the admin opt-in, the organization ID asEveryone(every active member, no license), other groups need Premium (403, group named), the sharing setting the controller never changes,Readyonly while Coder shows the share, subdomain apps and a wildcard access URL for the shared user's apps, access that lasts until the workspace restarts or stops after a share removal, and the warning that every claimant can act as the pool owner (owner token, external auth, Git SSH key), that anyone who can create pools on a control plane can act as every owner it lists, and thatmaxWorkspacescounts per control plane.Plan deviations
Readyfollows Coder and a forgedsharedUserIDnever marks a claim Ready. An independent pre-PR reviewer found both gaps.sharedUserIDon the member. TheSharedcondition separates "the share happened" from "Coder shows it now".Validation
Local gates on the PR head (
make test-scriptson4d2652b8;hack/has not changed since):make verify-vendor,make build,make lint,make test,make test-integration,make codegen manifests docs-reference build-installer(no diff),make docs-checkreference part plusmkdocs build --strictinnix develop, markdownlint-cli2 and cspell ondocs/**, actionlint: all passed.make test-scripts: see the comment below.envtest (new file
coderworkspacepool_share_test.go), each throughReconcilewith the real codersdk against the fake Coder:use, claimReadyonly after it.shareable_workspace_ownersvalue at resolve and right before the ACL write (everyone,service_accountswith a password owner and with a service-account owner,none, an unknown value), noshareGroupIDs, owner no longer listed, Coder's 403.ShareRemoved, no second write after the setting is relaxed again. A forgedSharedstatus is caught by the ACL read.shareGroupIDsemptied, the owner removed, the user suspended. Each removes the share with one write of"", reportsShareRevoked, and never shares again after the policy allows it again. A failed policy read keeps the share, and a failed removal isShareRevokinguntil a retry works.ShareNotAllowed, and the claim informer under a strict cache.admin: the claim staysReady, no write back, and a withdrawn policy still removes the entry.Mutation checks, each made a named test fail:
TestPoolShareNotAllowed,TestPoolMemberChecksSharePolicyBeforeACLWrite.TestPoolChecksShareUserBeforeBind/listed_group_without_the_user.Everyone:TestPoolChecksShareUserBeforeBind/another_organization's_ID.TestPoolChecksShareUserBeforeBind/prebuilds_user.TestPoolChecksShareUserBeforeBind,TestPoolRefusesClaimOfOtherMode.TestPoolKeepsDemandWhileShareCheckFails.TestPoolMemberShareKeepsItsUserAfterRename.TestPoolMemberShareSurvivesLostStatus,TestPoolMemberShareKeepsItsUserAfterRename.ReadytrustssharedUserID:TestPoolMemberReportsRemovedShare.TestPoolMemberRevokesWithdrawnShare.TestPoolChecksShareGroupsWithoutCache. No deployment-wide disable check:TestPoolShareNotAllowed.TestPoolBoundsShareChecksOfRefusedClaims.TestPoolMemberSharesThroughManager.userole:TestPoolMemberEnforcesPolicyAfterRoleChange.ShareNotAllowedpool still count as demand:TestPoolDropsShareDemandWhileShareNotAllowed,TestPoolShareNotAllowed.Offline stubs: all 23 scenarios pass (2.5 min).
Kind (CI replica on a
kind-dev.shcluster, kindest/node v1.32.0, Coder v2.37.2 without a license, operator built from this branch, CI steps 7 to 28): the pool driver passed P1 to P9, then the lifecycle driver and the wrong-CA step passed.After the first review round (head
1ea170cd, with the revocation step in P9), the pool driver passed again on the same cluster: P9 106 s, and the emptiedshareGroupIDsremoved the share in Coder at once (the member watches its control plane). The Share claim wasReady60.4 s after the driver allowed sharing again: the pool reads the setting at its next poll, once a minute. P9 adds about 1.7 minutes to the Kind job (R6), well below the 10-minute limit of decision D8.CI's E2E job on
2eaa8878(before the rebase on 🤖 feat: replace old unclaimed pool workspaces and reap idle claimed ones, both opt-in (#153 PR 15a) #447) ran P1 to P9: P9 passed in 88 s, and the Share claim wasReady58.8 s after the driver allowed sharing again.R3 checked on v2.37.2 without a license: groups answer 403, the ACL write, the ACL read, and the sharing setting work, and the Share user's delete build answers 403.
Dogfood (recording, a screenshot of the Share user's Coder UI,
coder ssh <owner>/<workspace>as the Share user) follows in a comment.Risks
use, and the how-to tells admins to check the template, external auth, and the Git SSH key.Everyonegroup works, so Share mode then admits every active member of that organization.service_accountsin that moment can see one share land after the change. Coder answers 403 fornoneitself.ResourceQuotaoncount/coderworkspaceclaims.coder.com.58c21ecb): afterShareRevokedorShareRemoved, the user keeps access until the workspace restarts or stops, as with Coder's own unshare. The messages and the how-to say so and tell the admin to restart or stop the workspace, or delete the claim. 🤖 CoderWorkspacePool Share mode: optionally stop the workspace when a share is revoked #450 tracks an opt-in operator stop.2924f36b): when idle reaping (🤖 feat: replace old unclaimed pool workspaces and reap idle claimed ones, both opt-in (#153 PR 15a) #447) keeps a used Share workspace and releases its member, the workspace stays shared, and no member checks the share policy for it any more. Removing the share at the release would cut the user off from the kept work, which 🤖 feat: replace old unclaimed pool workspaces and reap idle claimed ones, both opt-in (#153 PR 15a) #447's message says stays reachable, so it needs a design decision.ShareUserNotFoundandShareUserNotEligibletell anyone who can create and read claims whether a named Coder user exists, is active, and is in the organization and a listed group. Plan 6.4 defines these reasons, and the refusal names the group so that an admin can fix the pool. 🤖 CoderWorkspaceClaim Share mode: claim reasons reveal facts about named Coder users #449 tracks a generic reason or a docs note.Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:xhigh