mirror of
https://github.com/coder/coder.git
synced 2026-09-01 14:53:15 +08:00
fc188fdaee
## Overview Part of the **boundary correlation** feature. Fixes lazy creation of `boundary_sessions` rows so it works within the agent's RBAC constraints, and consumes the new `ConfinedProcessName` field reported by boundary. Pairs with coder/boundary#206, which adds `ConfinedProcessName` to `ReportBoundaryLogsRequest`. This branch bumps the `github.com/coder/boundary` module to pick up that work. ## Problem `ensureSession` did a pre-insert existence check via `GetBoundarySessionByID`. Agents are **not permitted to read boundary sessions**, so that read path is not viable when the session is created from an agent-reported log batch. ## Changes - **Remove the pre-insert read.** `ensureSession` now inserts directly and treats a primary-key unique violation as success, covering sessions already created by a prior batch, a reconnection, or another coderd replica — without requiring read access. - **Per-connection guard.** Add a mutex-protected `ensuredSessions` set so repeated log batches on the same connection skip the existence check and insert entirely, touching the database only for the logs. On a transient insert failure the session is left unmarked so the next batch retries. - **Consume `ConfinedProcessName`.** Pass `req.GetConfinedProcessName()` through to the session insert. - **Bump boundary module** from `v0.9.0` to `v0.9.1-0.20260706095856-35ba90f9e8b2`. - **Tests.** - Add `TestReportBoundaryLogsAgentRBAC` (`coderd/boundary_logs_test.go`), an integration test that connects as a real workspace agent, verifies the session and log are persisted under agent RBAC, and asserts the agent subject cannot read boundary sessions — guarding against reintroducing a pre-insert read. - Add `TestReportBoundaryLogsSessionGuard` (session inserted once across two batches, logs inserted per batch) and `TestReportBoundaryLogsSessionRetriedOnError` (insert retried after a transient error). - Regenerate `agent-firewall` CLI docs/golden files and adjust the clidocgen template to render the YAML path when a flag has no long name. > 🤖 This PR was opened by Coder Agents on behalf of @SasSwart.
108 lines
3.7 KiB
Go
108 lines
3.7 KiB
Go
package coderd_test
|
|
|
|
import (
|
|
"context"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
|
|
agentproto "github.com/coder/coder/v2/agent/proto"
|
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
|
"github.com/coder/coder/v2/coderd/database/dbfake"
|
|
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
|
"github.com/coder/coder/v2/coderd/rbac"
|
|
"github.com/coder/coder/v2/codersdk/agentsdk"
|
|
"github.com/coder/coder/v2/testutil"
|
|
)
|
|
|
|
// TestReportBoundaryLogsAgentRBAC guards against regressions where
|
|
// a pre-insert read (e.g. GetBoundarySessionByID) would be silently denied for
|
|
// agents and prevent session creation.
|
|
func TestReportBoundaryLogsAgentRBAC(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
store, ps := dbtestutil.NewDB(t)
|
|
client := coderdtest.New(t, &coderdtest.Options{Database: store, Pubsub: ps})
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
r := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{
|
|
OrganizationID: user.OrganizationID,
|
|
OwnerID: user.UserID,
|
|
}).WithAgent().Do()
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
|
|
// Connect as a real workspace agent.
|
|
ac := agentsdk.New(client.URL, agentsdk.WithFixedToken(r.AgentToken))
|
|
conn, err := ac.ConnectRPC(ctx)
|
|
require.NoError(t, err)
|
|
defer conn.Close()
|
|
|
|
agentClient := agentproto.NewDRPCAgentClient(conn)
|
|
sessionID := uuid.New()
|
|
|
|
_, err = agentClient.ReportBoundaryLogs(ctx, &agentproto.ReportBoundaryLogsRequest{
|
|
SessionId: sessionID.String(),
|
|
ConfinedProcessName: "claude-code",
|
|
Logs: []*agentproto.BoundaryLog{
|
|
{
|
|
Allowed: true,
|
|
Time: timestamppb.New(dbtime.Now()),
|
|
SequenceNumber: 0,
|
|
Resource: &agentproto.BoundaryLog_HttpRequest_{
|
|
HttpRequest: &agentproto.BoundaryLog_HttpRequest{
|
|
Method: "GET",
|
|
Url: "https://example.com",
|
|
MatchedRule: "domain=example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Verify persistence via the raw store: because ReportBoundaryLogs swallows
|
|
// DB errors and returns success regardless, only a direct read proves the
|
|
// session and log were actually persisted under agent RBAC.
|
|
sess, err := store.GetBoundarySessionByID(ctx, sessionID)
|
|
require.NoError(t, err, "session must be persisted")
|
|
require.Equal(t, r.Agents[0].ID, sess.WorkspaceAgentID)
|
|
|
|
logs, err := store.ListBoundaryLogsBySessionID(ctx, database.ListBoundaryLogsBySessionIDParams{
|
|
SessionID: sessionID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, logs, 1, "log must be persisted")
|
|
|
|
// Assert that the agent subject cannot read boundary sessions.
|
|
memberRole, err := rbac.RoleByName(rbac.RoleMember())
|
|
require.NoError(t, err)
|
|
agentSubject := rbac.Subject{
|
|
ID: r.Workspace.OwnerID.String(),
|
|
Roles: rbac.Roles{memberRole},
|
|
Scope: rbac.WorkspaceAgentScope(rbac.WorkspaceAgentScopeParams{
|
|
WorkspaceID: r.Workspace.ID,
|
|
OwnerID: r.Workspace.OwnerID,
|
|
TemplateID: r.Workspace.TemplateID,
|
|
VersionID: r.Build.TemplateVersionID,
|
|
}),
|
|
}.WithCachedASTValue()
|
|
|
|
auth := rbac.NewStrictCachingAuthorizer(prometheus.NewRegistry())
|
|
acsPtr := &atomic.Pointer[dbauthz.AccessControlStore]{}
|
|
var acs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{}
|
|
acsPtr.Store(&acs)
|
|
authzStore := dbauthz.New(store, auth, testutil.Logger(t), acsPtr)
|
|
|
|
agentCtx := dbauthz.As(context.Background(), agentSubject)
|
|
_, err = authzStore.GetBoundarySessionByID(agentCtx, sessionID)
|
|
require.True(t, dbauthz.IsNotAuthorizedError(err),
|
|
"agents must not be able to read boundary sessions, got: %v", err)
|
|
}
|