◐ Shell
clean mode source ↗

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 {

2931

AgentID uuid.UUID

3032

AgentFn 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

}

36393740

func (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

}

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+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

}

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+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+

}

156194157195

var openIn database.WorkspaceAppOpenIn

158196

switch app.GetOpenIn() {