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
3538func (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 () {
0 commit comments