mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: create agent firewall sessions without requiring agent read access (#26990)
## 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.
This commit is contained in:
@@ -2,9 +2,8 @@ package agentapi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -47,6 +46,13 @@ type BoundaryLogsAPI struct {
|
|||||||
TemplateID uuid.UUID
|
TemplateID uuid.UUID
|
||||||
TemplateVersionID uuid.UUID
|
TemplateVersionID uuid.UUID
|
||||||
BoundaryUsageTracker *boundaryusage.Tracker
|
BoundaryUsageTracker *boundaryusage.Tracker
|
||||||
|
|
||||||
|
// mu guards ensuredSessions, which records session IDs already persisted
|
||||||
|
// by this connection so repeated batches skip the existence check and
|
||||||
|
// insert. The API is one instance per agent connection, so this lives for
|
||||||
|
// the session's lifetime.
|
||||||
|
mu sync.Mutex
|
||||||
|
ensuredSessions map[uuid.UUID]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *BoundaryLogsAPI) ReportBoundaryLogs(ctx context.Context, req *agentproto.ReportBoundaryLogsRequest) (*agentproto.ReportBoundaryLogsResponse, error) {
|
func (a *BoundaryLogsAPI) ReportBoundaryLogs(ctx context.Context, req *agentproto.ReportBoundaryLogsRequest) (*agentproto.ReportBoundaryLogsResponse, error) {
|
||||||
@@ -80,16 +86,19 @@ func (a *BoundaryLogsAPI) ReportBoundaryLogs(ctx context.Context, req *agentprot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if persistEnabled {
|
if persistEnabled && !a.sessionEnsured(sessionID) {
|
||||||
// Lazy-create the boundary session on first log arrival.
|
// Lazy-create the boundary session on first log arrival.
|
||||||
// If this fails (transient DB error), we continue so that
|
// If this fails (transient DB error), we continue so that
|
||||||
// logs are still persisted. The session will be created on
|
// logs are still persisted. The session will be created on
|
||||||
// a subsequent batch since every request carries the session
|
// a subsequent batch since every request carries the session
|
||||||
// details.
|
// details. On success we record the session so later batches
|
||||||
|
// skip the existence check and insert entirely.
|
||||||
if sessionErr := a.ensureSession(ctx, sessionID, req.GetConfinedProcessName(), now); sessionErr != nil {
|
if sessionErr := a.ensureSession(ctx, sessionID, req.GetConfinedProcessName(), now); sessionErr != nil {
|
||||||
a.Log.Error(ctx, "failed to ensure boundary session",
|
a.Log.Error(ctx, "failed to ensure boundary session",
|
||||||
slog.F("session_id", sessionID.String()),
|
slog.F("session_id", sessionID.String()),
|
||||||
slog.Error(sessionErr))
|
slog.Error(sessionErr))
|
||||||
|
} else {
|
||||||
|
a.markSessionEnsured(sessionID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +188,25 @@ func (a *BoundaryLogsAPI) ReportBoundaryLogs(ctx context.Context, req *agentprot
|
|||||||
return &agentproto.ReportBoundaryLogsResponse{}, nil
|
return &agentproto.ReportBoundaryLogsResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sessionEnsured reports whether this connection has already persisted the
|
||||||
|
// session, letting repeated batches skip the database round-trip.
|
||||||
|
func (a *BoundaryLogsAPI) sessionEnsured(sessionID uuid.UUID) bool {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
_, ok := a.ensuredSessions[sessionID]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// markSessionEnsured records that the session has been persisted.
|
||||||
|
func (a *BoundaryLogsAPI) markSessionEnsured(sessionID uuid.UUID) {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
if a.ensuredSessions == nil {
|
||||||
|
a.ensuredSessions = make(map[uuid.UUID]struct{})
|
||||||
|
}
|
||||||
|
a.ensuredSessions[sessionID] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
// ensureSession creates the boundary_sessions row if it does not
|
// ensureSession creates the boundary_sessions row if it does not
|
||||||
// already exist.
|
// already exist.
|
||||||
func (a *BoundaryLogsAPI) ensureSession(ctx context.Context, sessionID uuid.UUID, confinedProcess string, now time.Time) error {
|
func (a *BoundaryLogsAPI) ensureSession(ctx context.Context, sessionID uuid.UUID, confinedProcess string, now time.Time) error {
|
||||||
@@ -186,19 +214,7 @@ func (a *BoundaryLogsAPI) ensureSession(ctx context.Context, sessionID uuid.UUID
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the database in case another replica or reconnection
|
_, err := a.Database.InsertBoundarySession(ctx, database.InsertBoundarySessionParams{
|
||||||
// already created this session.
|
|
||||||
_, err := a.Database.GetBoundarySessionByID(ctx, sessionID)
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return xerrors.Errorf("check boundary session existence: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session does not exist; create it. started_at is the time
|
|
||||||
// the first log is received by coderd, per the RFC.
|
|
||||||
_, err = a.Database.InsertBoundarySession(ctx, database.InsertBoundarySessionParams{
|
|
||||||
ID: sessionID,
|
ID: sessionID,
|
||||||
WorkspaceAgentID: a.AgentID,
|
WorkspaceAgentID: a.AgentID,
|
||||||
OwnerID: uuid.NullUUID{UUID: a.OwnerID, Valid: true},
|
OwnerID: uuid.NullUUID{UUID: a.OwnerID, Valid: true},
|
||||||
@@ -207,13 +223,8 @@ func (a *BoundaryLogsAPI) ensureSession(ctx context.Context, sessionID uuid.UUID
|
|||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// A second coderd replica may receive a batch for this session
|
|
||||||
// before the first replica has finished inserting it. Both
|
|
||||||
// attempt the INSERT; the second fails with a primary-key
|
|
||||||
// unique violation. Treat it as success because the session
|
|
||||||
// now exists.
|
|
||||||
if database.IsUniqueViolation(err, database.UniqueBoundarySessionsPkey) {
|
if database.IsUniqueViolation(err, database.UniqueBoundarySessionsPkey) {
|
||||||
a.Log.Debug(ctx, "boundary session already created by another replica",
|
a.Log.Debug(ctx, "boundary session already created",
|
||||||
slog.F("session_id", sessionID.String()))
|
slog.F("session_id", sessionID.String()))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,20 @@ package agentapi_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.uber.org/mock/gomock"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"cdr.dev/slog/v3/sloggers/slogtest"
|
||||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||||
"github.com/coder/coder/v2/coderd/agentapi"
|
"github.com/coder/coder/v2/coderd/agentapi"
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||||
|
"github.com/coder/coder/v2/coderd/database/dbmock"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||||
"github.com/coder/coder/v2/testutil"
|
"github.com/coder/coder/v2/testutil"
|
||||||
@@ -454,3 +458,98 @@ func TestReportBoundaryLogs(t *testing.T) {
|
|||||||
require.Len(t, logs, 2, "logs from both agents must be persisted")
|
require.Len(t, logs, 2, "logs from both agents must be persisted")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// httpLogRequest builds a ReportBoundaryLogsRequest carrying a single allowed
|
||||||
|
// HTTP log for the given session.
|
||||||
|
func httpLogRequest(sessionID uuid.UUID, seq int32) *agentproto.ReportBoundaryLogsRequest {
|
||||||
|
return &agentproto.ReportBoundaryLogsRequest{
|
||||||
|
SessionId: sessionID.String(),
|
||||||
|
ConfinedProcessName: "claude-code",
|
||||||
|
Logs: []*agentproto.BoundaryLog{
|
||||||
|
{
|
||||||
|
Allowed: true,
|
||||||
|
Time: timestamppb.New(dbtime.Now()),
|
||||||
|
SequenceNumber: seq,
|
||||||
|
Resource: &agentproto.BoundaryLog_HttpRequest_{
|
||||||
|
HttpRequest: &agentproto.BoundaryLog_HttpRequest{
|
||||||
|
Method: "GET",
|
||||||
|
Url: "https://example.com",
|
||||||
|
MatchedRule: "domain=example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReportBoundaryLogsSessionGuard verifies that once a session has been
|
||||||
|
// ensured, later batches from the same connection skip the existence check and
|
||||||
|
// insert entirely, touching the database only for the logs themselves.
|
||||||
|
func TestReportBoundaryLogsSessionGuard(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
db := dbmock.NewMockStore(ctrl)
|
||||||
|
|
||||||
|
sessionID := uuid.New()
|
||||||
|
api := &agentapi.BoundaryLogsAPI{
|
||||||
|
Log: testutil.Logger(t),
|
||||||
|
Database: db,
|
||||||
|
AgentID: uuid.New(),
|
||||||
|
WorkspaceID: uuid.New(),
|
||||||
|
OwnerID: uuid.New(),
|
||||||
|
TemplateID: uuid.New(),
|
||||||
|
TemplateVersionID: uuid.New(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// The session is inserted once, even though two batches arrive. Times(1)
|
||||||
|
// fails the test if the guard does not suppress the second ensure attempt.
|
||||||
|
// Logs insert on every batch.
|
||||||
|
db.EXPECT().InsertBoundarySession(gomock.Any(), gomock.Any()).
|
||||||
|
Return(database.BoundarySession{}, nil).Times(1)
|
||||||
|
db.EXPECT().InsertBoundaryLogs(gomock.Any(), gomock.Any()).
|
||||||
|
Return([]database.BoundaryLog{}, nil).Times(2)
|
||||||
|
|
||||||
|
_, err := api.ReportBoundaryLogs(context.Background(), httpLogRequest(sessionID, 0))
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = api.ReportBoundaryLogs(context.Background(), httpLogRequest(sessionID, 1))
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestReportBoundaryLogsSessionRetriedOnError verifies that when ensureSession
|
||||||
|
// fails, the guard is not set, so the next batch retries the existence check.
|
||||||
|
func TestReportBoundaryLogsSessionRetriedOnError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
db := dbmock.NewMockStore(ctrl)
|
||||||
|
|
||||||
|
sessionID := uuid.New()
|
||||||
|
api := &agentapi.BoundaryLogsAPI{
|
||||||
|
// The first batch deliberately triggers a transient error, which
|
||||||
|
// logs at ERROR level. Ignore errors so slogtest does not fail.
|
||||||
|
Log: slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}),
|
||||||
|
Database: db,
|
||||||
|
AgentID: uuid.New(),
|
||||||
|
WorkspaceID: uuid.New(),
|
||||||
|
OwnerID: uuid.New(),
|
||||||
|
TemplateID: uuid.New(),
|
||||||
|
TemplateVersionID: uuid.New(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// First batch: insert fails transiently, so the session is not marked
|
||||||
|
// ensured. Second batch: insert is retried and succeeds. Logs insert on both.
|
||||||
|
gomock.InOrder(
|
||||||
|
db.EXPECT().InsertBoundarySession(gomock.Any(), gomock.Any()).
|
||||||
|
Return(database.BoundarySession{}, sql.ErrConnDone),
|
||||||
|
db.EXPECT().InsertBoundarySession(gomock.Any(), gomock.Any()).
|
||||||
|
Return(database.BoundarySession{}, nil),
|
||||||
|
)
|
||||||
|
db.EXPECT().InsertBoundaryLogs(gomock.Any(), gomock.Any()).
|
||||||
|
Return([]database.BoundaryLog{}, nil).Times(2)
|
||||||
|
|
||||||
|
_, err := api.ReportBoundaryLogs(context.Background(), httpLogRequest(sessionID, 0))
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = api.ReportBoundaryLogs(context.Background(), httpLogRequest(sessionID, 1))
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
Generated
+19
-9
@@ -35,15 +35,6 @@ Path to YAML config file.
|
|||||||
|
|
||||||
Allow rule (repeatable). These are merged with allowlist from config file. Format: "pattern" or "METHOD[,METHOD] pattern".
|
Allow rule (repeatable). These are merged with allowlist from config file. Format: "pattern" or "METHOD[,METHOD] pattern".
|
||||||
|
|
||||||
### --
|
|
||||||
|
|
||||||
| | |
|
|
||||||
|------|---------------------------|
|
|
||||||
| Type | <code>string-array</code> |
|
|
||||||
| YAML | <code>allowlist</code> |
|
|
||||||
|
|
||||||
Allowlist rules from config file (YAML only).
|
|
||||||
|
|
||||||
### --log-level
|
### --log-level
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
@@ -155,3 +146,22 @@ Path to the socket where the boundary log proxy server listens for audit logs.
|
|||||||
| Type | <code>bool</code> |
|
| Type | <code>bool</code> |
|
||||||
|
|
||||||
Print version information and exit.
|
Print version information and exit.
|
||||||
|
|
||||||
|
### --enable-session-correlation
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|-------------|----------------------------------------------------|
|
||||||
|
| Type | <code>bool</code> |
|
||||||
|
| Environment | <code>$BOUNDARY_SESSION_CORRELATION_ENABLED</code> |
|
||||||
|
| YAML | <code>session_correlation_enabled</code> |
|
||||||
|
|
||||||
|
Enable session correlation header injection. When no inject targets are configured, the target is auto-derived from CODER_AGENT_URL (set automatically inside Coder workspaces). Disable for deployments without Coder AI Gateway in front.
|
||||||
|
|
||||||
|
### --session-id-inject-target
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|-------------|-------------------------------------------------|
|
||||||
|
| Type | <code>string</code> |
|
||||||
|
| Environment | <code>$BOUNDARY_SESSION_ID_INJECT_TARGET</code> |
|
||||||
|
|
||||||
|
Inject target for session correlation headers. Repeat the flag once per target; each value describes exactly one target. Format: "domain=<host> [path=<glob>]". Example: --session-id-inject-target "domain=prod.coder.com path=/api/v2/aibridge/*".
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ OPTIONS:
|
|||||||
--disable-audit-logs bool, $DISABLE_AUDIT_LOGS
|
--disable-audit-logs bool, $DISABLE_AUDIT_LOGS
|
||||||
Disable sending of audit logs to the workspace agent when set to true.
|
Disable sending of audit logs to the workspace agent when set to true.
|
||||||
|
|
||||||
|
--enable-session-correlation bool, $BOUNDARY_SESSION_CORRELATION_ENABLED
|
||||||
|
Enable session correlation header injection. When no inject targets
|
||||||
|
are configured, the target is auto-derived from CODER_AGENT_URL (set
|
||||||
|
automatically inside Coder workspaces). Disable for deployments
|
||||||
|
without Coder AI Gateway in front.
|
||||||
|
|
||||||
--jail-type string, $BOUNDARY_JAIL_TYPE (default: nsjail)
|
--jail-type string, $BOUNDARY_JAIL_TYPE (default: nsjail)
|
||||||
Jail type to use for network isolation. Options: nsjail (default),
|
Jail type to use for network isolation. Options: nsjail (default),
|
||||||
landjail.
|
landjail.
|
||||||
@@ -50,6 +56,15 @@ OPTIONS:
|
|||||||
--proxy-port int, $PROXY_PORT (default: 8080)
|
--proxy-port int, $PROXY_PORT (default: 8080)
|
||||||
Set a port for HTTP proxy.
|
Set a port for HTTP proxy.
|
||||||
|
|
||||||
|
--session-id-inject-target string, $BOUNDARY_SESSION_ID_INJECT_TARGET
|
||||||
|
Inject target for session correlation headers. Repeat the flag once
|
||||||
|
per target; each value describes exactly one target. Format:
|
||||||
|
"domain=<host> [path=<glob>]". Example: --session-id-inject-target
|
||||||
|
"domain=prod.coder.com path=/api/v2/aibridge/*".
|
||||||
|
|
||||||
|
string-array
|
||||||
|
Inject targets from config file (YAML only).
|
||||||
|
|
||||||
--use-real-dns bool, $BOUNDARY_USE_REAL_DNS
|
--use-real-dns bool, $BOUNDARY_USE_REAL_DNS
|
||||||
Use real DNS in the jail instead of the dummy DNS (allows DNS
|
Use real DNS in the jail instead of the dummy DNS (allows DNS
|
||||||
exfiltration). Default: false.
|
exfiltration). Default: false.
|
||||||
|
|||||||
@@ -520,7 +520,7 @@ require (
|
|||||||
github.com/brianvoe/gofakeit/v7 v7.15.0
|
github.com/brianvoe/gofakeit/v7 v7.15.0
|
||||||
github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225
|
github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225
|
||||||
github.com/coder/aisdk-go v0.0.9
|
github.com/coder/aisdk-go v0.0.9
|
||||||
github.com/coder/boundary v0.9.0
|
github.com/coder/boundary v0.10.0
|
||||||
github.com/coder/preview v1.0.10-0.20260521153517-34deb0946c4f
|
github.com/coder/preview v1.0.10-0.20260521153517-34deb0946c4f
|
||||||
github.com/danieljoos/wincred v1.2.3
|
github.com/danieljoos/wincred v1.2.3
|
||||||
github.com/dgraph-io/ristretto/v2 v2.4.0
|
github.com/dgraph-io/ristretto/v2 v2.4.0
|
||||||
|
|||||||
@@ -316,8 +316,8 @@ github.com/coder/aisdk-go v0.0.9 h1:Vzo/k2qwVGLTR10ESDeP2Ecek1SdPfZlEjtTfMveiVo=
|
|||||||
github.com/coder/aisdk-go v0.0.9/go.mod h1:KF6/Vkono0FJJOtWtveh5j7yfNrSctVTpwgweYWSp5M=
|
github.com/coder/aisdk-go v0.0.9/go.mod h1:KF6/Vkono0FJJOtWtveh5j7yfNrSctVTpwgweYWSp5M=
|
||||||
github.com/coder/anthropic-sdk-go v0.0.0-20260428122333-47cab198e449 h1:X4XOtomDcJlr5/bmgcnrZiJeZIS+qixzVn1EWqgCZ4E=
|
github.com/coder/anthropic-sdk-go v0.0.0-20260428122333-47cab198e449 h1:X4XOtomDcJlr5/bmgcnrZiJeZIS+qixzVn1EWqgCZ4E=
|
||||||
github.com/coder/anthropic-sdk-go v0.0.0-20260428122333-47cab198e449/go.mod h1:hqlYqR7uPKOKfnNeicUbZp0Ps0GeYFlKYtwh5HGDCx8=
|
github.com/coder/anthropic-sdk-go v0.0.0-20260428122333-47cab198e449/go.mod h1:hqlYqR7uPKOKfnNeicUbZp0Ps0GeYFlKYtwh5HGDCx8=
|
||||||
github.com/coder/boundary v0.9.0 h1:JthV9N9R/4QFoPd6L7i04O3xAOtXvSVu4v7CiAaEzu0=
|
github.com/coder/boundary v0.10.0 h1:qX8iGKpAx5jm4wdbhYfLQLvNMiq16pkZiOjLLBklI+Q=
|
||||||
github.com/coder/boundary v0.9.0/go.mod h1:BhJhyKW/+zZQzaGZ3vn27if2k0Vx5xLXzq7ZCQx5gPk=
|
github.com/coder/boundary v0.10.0/go.mod h1:dDILpof96k+ixVbVDRA2ez+zmj+n0ZhJPSiidB6rtrw=
|
||||||
github.com/coder/bubbletea v1.2.2-0.20241212190825-007a1cdb2c41 h1:SBN/DA63+ZHwuWwPHPYoCZ/KLAjHv5g4h2MS4f2/MTI=
|
github.com/coder/bubbletea v1.2.2-0.20241212190825-007a1cdb2c41 h1:SBN/DA63+ZHwuWwPHPYoCZ/KLAjHv5g4h2MS4f2/MTI=
|
||||||
github.com/coder/bubbletea v1.2.2-0.20241212190825-007a1cdb2c41/go.mod h1:I9ULxr64UaOSUv7hcb3nX4kowodJCVS7vt7VVJk/kW4=
|
github.com/coder/bubbletea v1.2.2-0.20241212190825-007a1cdb2c41/go.mod h1:I9ULxr64UaOSUv7hcb3nX4kowodJCVS7vt7VVJk/kW4=
|
||||||
github.com/coder/clistat v1.2.1 h1:P9/10njXMyj5cWzIU5wkRsSy5LVQH49+tcGMsAgWX0w=
|
github.com/coder/clistat v1.2.1 h1:P9/10njXMyj5cWzIU5wkRsSy5LVQH49+tcGMsAgWX0w=
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ Aliases:
|
|||||||
{{- if eq $index 0 }}
|
{{- if eq $index 0 }}
|
||||||
## Options
|
## Options
|
||||||
{{- end }}
|
{{- end }}
|
||||||
### {{ with $opt.FlagShorthand}}-{{ . }}, {{end}}--{{ $opt.Flag }}
|
### {{ with $opt.FlagShorthand}}-{{ . }}, {{end}}{{ if $opt.Flag }}--{{ $opt.Flag }}{{ else }}{{ $opt.YAMLPath }}{{ end }}
|
||||||
{{" "}}
|
{{" "}}
|
||||||
{{ tableHeader }}
|
{{ tableHeader }}
|
||||||
| Type | {{ typeHelper $opt | wrapCode }} |
|
| Type | {{ typeHelper $opt | wrapCode }} |
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ func init() {
|
|||||||
if opt.Hidden {
|
if opt.Hidden {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Skip YAML-only options that have no CLI flag; documenting them
|
||||||
|
// as if they were flags is misleading in the CLI reference.
|
||||||
|
if opt.Flag == "" && opt.FlagShorthand == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
visible = append(visible, opt)
|
visible = append(visible, opt)
|
||||||
}
|
}
|
||||||
return visible
|
return visible
|
||||||
|
|||||||
Reference in New Issue
Block a user