Skip to content

Commit b78ec31

Browse files
jdomeracki-coderjohnstcnf0ssel
authored
fix: clamp template port sharing level in SubAgentAPI (#26061) (#26256)
Cherry-pick backport to `release/2.34`. Co-authored-by: Cian Johnston <cian@coder.com> Co-authored-by: Garrett Delfosse <delfossegarrett@gmail.com>
1 parent aba0853 commit b78ec31

5 files changed

Lines changed: 565 additions & 3 deletions

File tree

coderd/agentapi/api.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/coder/coder/v2/coderd/database/pubsub"
2727
"github.com/coder/coder/v2/coderd/externalauth"
2828
"github.com/coder/coder/v2/coderd/notifications"
29+
"github.com/coder/coder/v2/coderd/portsharing"
2930
"github.com/coder/coder/v2/coderd/prometheusmetrics"
3031
"github.com/coder/coder/v2/coderd/tracing"
3132
"github.com/coder/coder/v2/coderd/workspacestats"
@@ -90,6 +91,7 @@ type Options struct {
9091
NetworkTelemetryHandler func(batch []*tailnetproto.TelemetryEvent)
9192
BoundaryUsageTracker *boundaryusage.Tracker
9293
LifecycleMetrics *LifecycleMetrics
94+
PortSharer *atomic.Pointer[portsharing.PortSharer]
9395

9496
AccessURL *url.URL
9597
AppHostname string
@@ -230,6 +232,7 @@ func New(opts Options, workspace database.Workspace, agent database.WorkspaceAge
230232
Log: opts.Log,
231233
Clock: opts.Clock,
232234
Database: opts.Database,
235+
PortSharer: opts.PortSharer,
233236
}
234237

235238
api.BoundaryLogsAPI = &BoundaryLogsAPI{

coderd/agentapi/subagent.go

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"errors"
99
"fmt"
1010
"strings"
11+
"sync/atomic"
1112

1213
"github.com/google/uuid"
1314
"github.com/sqlc-dev/pqtype"
@@ -17,6 +18,7 @@ import (
1718
agentproto "github.com/coder/coder/v2/agent/proto"
1819
"github.com/coder/coder/v2/coderd/database"
1920
"github.com/coder/coder/v2/coderd/database/dbauthz"
21+
"github.com/coder/coder/v2/coderd/portsharing"
2022
"github.com/coder/coder/v2/codersdk"
2123
"github.com/coder/coder/v2/provisioner"
2224
"github.com/coder/quartz"
@@ -27,9 +29,10 @@ type SubAgentAPI struct {
2729
OrganizationID uuid.UUID
2830
AgentFn func(context.Context) (database.WorkspaceAgent, error)
2931

30-
Log slog.Logger
31-
Clock quartz.Clock
32-
Database database.Store
32+
Log slog.Logger
33+
Clock quartz.Clock
34+
Database database.Store
35+
PortSharer *atomic.Pointer[portsharing.PortSharer]
3336
}
3437

3538
func (a *SubAgentAPI) CreateSubAgent(ctx context.Context, req *agentproto.CreateSubAgentRequest) (*agentproto.CreateSubAgentResponse, error) {
@@ -129,6 +132,21 @@ func (a *SubAgentAPI) CreateSubAgent(ctx context.Context, req *agentproto.Create
129132
Detail: fmt.Sprintf("agent name %q does not match regex %q", agentName, provisioner.AgentNameRegex),
130133
}
131134
}
135+
var template database.Template
136+
if len(req.Apps) > 0 {
137+
workspace, err := a.Database.GetWorkspaceByAgentID(ctx, parentAgent.ID)
138+
if err != nil {
139+
return nil, xerrors.Errorf("get workspace by agent id: %w", err)
140+
}
141+
142+
// Intentional: SubAgentAPI auth context enforces template ACL.
143+
// Normal workspace operations depend on this.
144+
template, err = a.Database.GetTemplateByID(ctx, workspace.TemplateID)
145+
if err != nil {
146+
return nil, xerrors.Errorf("get template policy: %w. If template access was recently changed, restart the workspace to refresh agent permissions", err)
147+
}
148+
}
149+
132150
subAgent, err := a.Database.InsertWorkspaceAgent(ctx, database.InsertWorkspaceAgentParams{
133151
ID: uuid.New(),
134152
ParentID: uuid.NullUUID{Valid: true, UUID: parentAgent.ID},
@@ -155,6 +173,14 @@ func (a *SubAgentAPI) CreateSubAgent(ctx context.Context, req *agentproto.Create
155173
return nil, xerrors.Errorf("insert sub agent: %w", err)
156174
}
157175

176+
// A nil PortSharer uses the AGPL default, which permits all share levels.
177+
portSharer := portsharing.DefaultPortSharer
178+
if a.PortSharer != nil {
179+
if loaded := a.PortSharer.Load(); loaded != nil {
180+
portSharer = *loaded
181+
}
182+
}
183+
158184
var appCreationErrors []*agentproto.CreateSubAgentResponse_AppCreationError
159185
appSlugs := make(map[string]struct{})
160186

@@ -198,6 +224,18 @@ func (a *SubAgentAPI) CreateSubAgent(ctx context.Context, req *agentproto.Create
198224
}
199225
}
200226
sharingLevel := database.AppSharingLevel(strings.ToLower(protoSharingLevel))
227+
// Clamp instead of rejecting so a too-permissive app share level does
228+
// not block the sub-agent from starting.
229+
if err := portSharer.AuthorizedLevel(template, codersdk.WorkspaceAgentPortShareLevel(sharingLevel)); err != nil {
230+
a.Log.Warn(ctx, "clamping sub-agent app sharing level to template max port sharing level",
231+
slog.F("sub_agent_name", subAgent.Name),
232+
slog.F("sub_agent_id", subAgent.ID),
233+
slog.F("app_slug", slug),
234+
slog.F("requested_share_level", sharingLevel),
235+
slog.F("max_port_share_level", template.MaxPortSharingLevel),
236+
slog.Error(err))
237+
sharingLevel = template.MaxPortSharingLevel
238+
}
201239

202240
var openIn database.WorkspaceAppOpenIn
203241
switch app.GetOpenIn() {

coderd/database/dbauthz/dbauthz.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,11 @@ var (
412412
User: []rbac.Permission{},
413413
ByOrgID: map[string]rbac.OrgPermissions{
414414
orgID.String(): {
415+
Org: rbac.Permissions(map[string][]policy.Action{
416+
// SubAgentAPI needs to check metadata of templates
417+
// potentially shared via group_acl.
418+
rbac.ResourceTemplate.Type: {policy.ActionRead},
419+
}),
415420
Member: rbac.Permissions(map[string][]policy.Action{
416421
rbac.ResourceWorkspace.Type: {policy.ActionRead, policy.ActionUpdate, policy.ActionCreateAgent, policy.ActionDeleteAgent, policy.ActionUpdateAgent},
417422
}),

coderd/workspaceagentsrpc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ func (api *API) workspaceAgentRPC(rw http.ResponseWriter, r *http.Request) {
166166
PublishWorkspaceAgentLogsUpdateFn: api.publishWorkspaceAgentLogsUpdate,
167167
NetworkTelemetryHandler: api.NetworkTelemetryBatcher.Handler,
168168
BoundaryUsageTracker: api.BoundaryUsageTracker,
169+
PortSharer: &api.PortSharer,
169170

170171
AccessURL: api.AccessURL,
171172
AppHostname: api.AppHostname,

0 commit comments

Comments
 (0)