fix: clamp template port sharing level in SubAgentAPI (#26061) (#26318) · coder/coder@c1889d0
@@ -8,6 +8,7 @@ import (
88"errors"
99"fmt"
1010"strings"
11+"sync/atomic"
11121213"github.com/google/uuid"
1314"github.com/sqlc-dev/pqtype"
@@ -19,6 +20,7 @@ import (
1920 agentproto "github.com/coder/coder/v2/agent/proto"
2021"github.com/coder/coder/v2/coderd/database"
2122"github.com/coder/coder/v2/coderd/database/dbauthz"
23+"github.com/coder/coder/v2/coderd/portsharing"
2224"github.com/coder/coder/v2/codersdk"
2325"github.com/coder/coder/v2/provisioner"
2426)
@@ -29,9 +31,10 @@ type SubAgentAPI struct {
2931AgentID uuid.UUID
3032AgentFn func(context.Context) (database.WorkspaceAgent, error)
313332-Log slog.Logger
33-Clock quartz.Clock
34-Database database.Store
34+Log slog.Logger
35+Clock quartz.Clock
36+Database database.Store
37+PortSharer *atomic.Pointer[portsharing.PortSharer]
3538}
36393740func (a *SubAgentAPI) CreateSubAgent(ctx context.Context, req *agentproto.CreateSubAgentRequest) (*agentproto.CreateSubAgentResponse, error) {
@@ -84,6 +87,21 @@ func (a *SubAgentAPI) CreateSubAgent(ctx context.Context, req *agentproto.Create
8487displayApps = append(displayApps, app)
8588 }
868990+var template database.Template
91+if len(req.Apps) > 0 {
92+workspace, err := a.Database.GetWorkspaceByAgentID(ctx, parentAgent.ID)
93+if err != nil {
94+return nil, xerrors.Errorf("get workspace by agent id: %w", err)
95+ }
96+97+// Intentional: SubAgentAPI auth context enforces template ACL.
98+// Normal workspace operations depend on this.
99+template, err = a.Database.GetTemplateByID(ctx, workspace.TemplateID)
100+if err != nil {
101+return nil, xerrors.Errorf("get template policy: %w. If template access was recently changed, restart the workspace to refresh agent permissions", err)
102+ }
103+ }
104+87105subAgent, err := a.Database.InsertWorkspaceAgent(ctx, database.InsertWorkspaceAgentParams{
88106ID: uuid.New(),
89107ParentID: uuid.NullUUID{Valid: true, UUID: parentAgent.ID},
@@ -110,6 +128,14 @@ func (a *SubAgentAPI) CreateSubAgent(ctx context.Context, req *agentproto.Create
110128return nil, xerrors.Errorf("insert sub agent: %w", err)
111129 }
112130131+// A nil PortSharer uses the AGPL default, which permits all share levels.
132+portSharer := portsharing.DefaultPortSharer
133+if a.PortSharer != nil {
134+if loaded := a.PortSharer.Load(); loaded != nil {
135+portSharer = *loaded
136+ }
137+ }
138+113139var appCreationErrors []*agentproto.CreateSubAgentResponse_AppCreationError
114140appSlugs := make(map[string]struct{})
115141@@ -153,6 +179,18 @@ func (a *SubAgentAPI) CreateSubAgent(ctx context.Context, req *agentproto.Create
153179 }
154180 }
155181sharingLevel := database.AppSharingLevel(strings.ToLower(protoSharingLevel))
182+// Clamp instead of rejecting so a too-permissive app share level does
183+// not block the sub-agent from starting.
184+if err := portSharer.AuthorizedLevel(template, codersdk.WorkspaceAgentPortShareLevel(sharingLevel)); err != nil {
185+a.Log.Warn(ctx, "clamping sub-agent app sharing level to template max port sharing level",
186+slog.F("sub_agent_name", subAgent.Name),
187+slog.F("sub_agent_id", subAgent.ID),
188+slog.F("app_slug", slug),
189+slog.F("requested_share_level", sharingLevel),
190+slog.F("max_port_share_level", template.MaxPortSharingLevel),
191+slog.Error(err))
192+sharingLevel = template.MaxPortSharingLevel
193+ }
156194157195var openIn database.WorkspaceAppOpenIn
158196switch app.GetOpenIn() {