88 "errors"
99 "fmt"
1010 "strings"
11+ "sync/atomic"
1112
1213 "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 {
2931 AgentID uuid.UUID
3032 AgentFn func (context.Context ) (database.WorkspaceAgent , error )
3133
32- 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}
3639
3740func (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
8487 displayApps = append (displayApps , app )
8588 }
8689
90+ 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+
87105 subAgent , err := a .Database .InsertWorkspaceAgent (ctx , database.InsertWorkspaceAgentParams {
88106 ID : uuid .New (),
89107 ParentID : uuid.NullUUID {Valid : true , UUID : parentAgent .ID },
@@ -110,6 +128,14 @@ func (a *SubAgentAPI) CreateSubAgent(ctx context.Context, req *agentproto.Create
110128 return nil , xerrors .Errorf ("insert sub agent: %w" , err )
111129 }
112130
131+ // 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+
113139 var appCreationErrors []* agentproto.CreateSubAgentResponse_AppCreationError
114140 appSlugs := make (map [string ]struct {})
115141
@@ -153,6 +179,18 @@ func (a *SubAgentAPI) CreateSubAgent(ctx context.Context, req *agentproto.Create
153179 }
154180 }
155181 sharingLevel := 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+ }
156194
157195 var openIn database.WorkspaceAppOpenIn
158196 switch app .GetOpenIn () {
0 commit comments