mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(coderd): require ssh access for workspace chats (#23094)
### Motivation - The chat creation flow associated a workspace agent for a chat if the requester could read the workspace, enabling privilege escalation where users without SSH/app-connect permissions could cause the daemon to open privileged agent connections and execute commands. - The intent is to ensure that attaching a workspace agent to a chat only happens when the requester has the workspace SSH permission so the chat daemon cannot be abused to bypass RBAC. ### Description - Require request-scoped authorization for workspace agent usage by changing `validateCreateChatWorkspaceSelection` to accept the `*http.Request` and calling `api.Authorize(r, policy.ActionSSH, workspace)` before selecting the workspace for a chat. - Pass the HTTP request into the validator from `postChats` so authorization is evaluated in the request context (`postChats` now calls `validateCreateChatWorkspaceSelection(ctx, r, req)`). - Add a regression test `WorkspaceAccessibleButNoSSH` in `coderd/chats_test.go` which creates an org-admin-scoped user (read access but no `ActionSSH`) and asserts that creating a chat with `WorkspaceID` is denied. ### Testing - Ran `gofmt -w coderd/chats.go coderd/chats_test.go` which succeeded. - Attempted to run repository pre-commit checks (`make pre-commit`) and targeted `go test` invocations; these checks could not be completed in this environment due to missing local tooling and environment constraints (protobuf include resolution, containerized DB access via Docker socket, and long-running golden generation tasks), so full CI/pre-commit verification and end-to-end test runs did not complete here. - Added a focused regression unit test (`WorkspaceAccessibleButNoSSH`) to prevent reintroduction of the authorization bypass; this test is included in the change and should be executed in CI where the full toolchain and test environment are available. ------ [Codex Task](https://chatgpt.com/codex/tasks/task_b_69b432502670832e91d14e937745de46)
This commit is contained in:
+8
-1
@@ -237,7 +237,7 @@ func (api *API) postChats(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
workspaceSelection, validationStatus, validationError := api.validateCreateChatWorkspaceSelection(ctx, req)
|
||||
workspaceSelection, validationStatus, validationError := api.validateCreateChatWorkspaceSelection(ctx, r, req)
|
||||
if validationError != nil {
|
||||
httpapi.Write(ctx, rw, validationStatus, *validationError)
|
||||
return
|
||||
@@ -1899,6 +1899,7 @@ type createChatWorkspaceSelection struct {
|
||||
|
||||
func (api *API) validateCreateChatWorkspaceSelection(
|
||||
ctx context.Context,
|
||||
r *http.Request,
|
||||
req codersdk.CreateChatRequest,
|
||||
) (
|
||||
createChatWorkspaceSelection,
|
||||
@@ -1927,6 +1928,12 @@ func (api *API) validateCreateChatWorkspaceSelection(
|
||||
Valid: true,
|
||||
}
|
||||
|
||||
if !api.Authorize(r, policy.ActionSSH, workspace) {
|
||||
return selection, http.StatusBadRequest, &codersdk.Response{
|
||||
Message: "Workspace not found or you do not have access to this resource",
|
||||
}
|
||||
}
|
||||
|
||||
return selection, 0, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/database/dbfake"
|
||||
"github.com/coder/coder/v2/coderd/externalauth"
|
||||
coderdpubsub "github.com/coder/coder/v2/coderd/pubsub"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
"github.com/coder/websocket"
|
||||
@@ -162,6 +163,41 @@ func TestPostChats(t *testing.T) {
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("WorkspaceAccessibleButNoSSH", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
adminClient, db := newChatClientWithDatabase(t)
|
||||
firstUser := coderdtest.CreateFirstUser(t, adminClient)
|
||||
orgAdminClient, _ := coderdtest.CreateAnotherUser(
|
||||
t,
|
||||
adminClient,
|
||||
firstUser.OrganizationID,
|
||||
rbac.ScopedRoleOrgAdmin(firstUser.OrganizationID),
|
||||
)
|
||||
|
||||
workspaceBuild := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
|
||||
OrganizationID: firstUser.OrganizationID,
|
||||
OwnerID: firstUser.UserID,
|
||||
}).WithAgent().Do()
|
||||
|
||||
_, err := orgAdminClient.CreateChat(ctx, codersdk.CreateChatRequest{
|
||||
Content: []codersdk.ChatInputPart{
|
||||
{
|
||||
Type: codersdk.ChatInputPartTypeText,
|
||||
Text: "hello",
|
||||
},
|
||||
},
|
||||
WorkspaceID: &workspaceBuild.Workspace.ID,
|
||||
})
|
||||
sdkErr := requireSDKError(t, err, http.StatusBadRequest)
|
||||
require.Equal(
|
||||
t,
|
||||
"Workspace not found or you do not have access to this resource",
|
||||
sdkErr.Message,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("WorkspaceNotFound", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user