Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions agent/agentssh/agentssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ const (
// MagicProcessCmdlineJetBrains is a string in a process's command line that
// uniquely identifies it as JetBrains software.
MagicProcessCmdlineJetBrains = "idea.vendor.name=JetBrains"
MagicProcessCmdlineToolbox = "com.jetbrains.toolbox"
MagicProcessCmdlineGateway = "remote-dev-server"

// BlockedFileTransferErrorCode indicates that SSH server restricted the raw command from performing
// the file transfer.
Expand Down
17 changes: 16 additions & 1 deletion agent/agentssh/jetbrainstrack.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func NewJetbrainsChannelWatcher(ctx ssh.Context, logger slog.Logger, reportConne

// If this is not JetBrains, then we do not need to do anything special. We
// attempt to match on something that appears unique to JetBrains software.
if !strings.Contains(strings.ToLower(cmdline), strings.ToLower(MagicProcessCmdlineJetBrains)) {
if !isJetbrainsProcess(cmdline) {
return newChannel
}

Expand Down Expand Up @@ -104,3 +104,18 @@ func (c *ChannelOnClose) Close() error {
c.once.Do(c.done)
return c.Channel.Close()
}

func isJetbrainsProcess(cmdline string) bool {
opts := []string{
MagicProcessCmdlineJetBrains,
MagicProcessCmdlineToolbox,
MagicProcessCmdlineGateway,
}

for _, opt := range opts {
if strings.Contains(strings.ToLower(cmdline), strings.ToLower(opt)) {
return true
}
}
return false
}
1 change: 0 additions & 1 deletion cli/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ func TestDelete(t *testing.T) {
// The API checks if the user has any workspaces, so we cannot delete a user
// this way.
ctx := testutil.Context(t, testutil.WaitShort)
// nolint:gocritic // Unit test
err := api.Database.UpdateUserDeletedByID(dbauthz.AsSystemRestricted(ctx), deleteMeUser.ID)
require.NoError(t, err)

Expand Down
1 change: 0 additions & 1 deletion cli/provisioners_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ func TestProvisioners_Golden(t *testing.T) {
// Replace UUIDs with predictable values for golden files.
replace := make(map[string]string)
updateReplaceUUIDs := func(coderdAPI *coderd.API) {
//nolint:gocritic // This is a test.
systemCtx := dbauthz.AsSystemRestricted(context.Background())
provisioners, err := coderdAPI.Database.GetProvisionerDaemons(systemCtx)
require.NoError(t, err)
Expand Down
2 changes: 1 addition & 1 deletion cli/schedule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ func TestScheduleOverride(t *testing.T) {
ownerClient, _, _, ws := setupTestSchedule(t, sched)
now := time.Now()
// To avoid the likelihood of time-related flakes, only matching up to the hour.
expectedDeadline := time.Now().In(loc).Add(10 * time.Hour).Format("2006-01-02T15:")
expectedDeadline := now.In(loc).Add(10 * time.Hour).Format("2006-01-02T15:")

// When: we override the stop schedule
inv, root := clitest.New(t,
Expand Down
26 changes: 13 additions & 13 deletions coderd/agentapi/subagent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func TestSubAgentAPI(t *testing.T) {
agentID, err := uuid.FromBytes(createResp.Agent.Id)
require.NoError(t, err)

agent, err := api.Database.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), agentID) //nolint:gocritic // this is a test.
agent, err := api.Database.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), agentID)
require.NoError(t, err)

assert.Equal(t, tt.agentName, agent.Name)
Expand Down Expand Up @@ -621,7 +621,7 @@ func TestSubAgentAPI(t *testing.T) {
agentID, err := uuid.FromBytes(createResp.Agent.Id)
require.NoError(t, err)

apps, err := api.Database.GetWorkspaceAppsByAgentID(dbauthz.AsSystemRestricted(ctx), agentID) //nolint:gocritic // this is a test.
apps, err := api.Database.GetWorkspaceAppsByAgentID(dbauthz.AsSystemRestricted(ctx), agentID)
require.NoError(t, err)

// Sort the apps for determinism
Expand Down Expand Up @@ -751,7 +751,7 @@ func TestSubAgentAPI(t *testing.T) {
agentID, err := uuid.FromBytes(createResp.Agent.Id)
require.NoError(t, err)

apps, err := db.GetWorkspaceAppsByAgentID(dbauthz.AsSystemRestricted(ctx), agentID) //nolint:gocritic // this is a test.
apps, err := db.GetWorkspaceAppsByAgentID(dbauthz.AsSystemRestricted(ctx), agentID)
require.NoError(t, err)
require.Len(t, apps, 1)
require.Equal(t, "k5jd7a99-duplicate-slug", apps[0].Slug)
Expand Down Expand Up @@ -789,7 +789,7 @@ func TestSubAgentAPI(t *testing.T) {
require.NoError(t, err)

// Then: It is deleted.
_, err = db.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), childAgent.ID) //nolint:gocritic // this is a test.
_, err = db.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), childAgent.ID)
require.ErrorIs(t, err, sql.ErrNoRows)
})

Expand Down Expand Up @@ -830,10 +830,10 @@ func TestSubAgentAPI(t *testing.T) {
require.NoError(t, err)

// Then: The correct one is deleted.
_, err = api.Database.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), childAgentOne.ID) //nolint:gocritic // this is a test.
_, err = api.Database.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), childAgentOne.ID)
require.ErrorIs(t, err, sql.ErrNoRows)

_, err = api.Database.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), childAgentTwo.ID) //nolint:gocritic // this is a test.
_, err = api.Database.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), childAgentTwo.ID)
require.NoError(t, err)
})

Expand Down Expand Up @@ -871,7 +871,7 @@ func TestSubAgentAPI(t *testing.T) {
var notAuthorizedError dbauthz.NotAuthorizedError
require.ErrorAs(t, err, &notAuthorizedError)

_, err = db.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), childAgentOne.ID) //nolint:gocritic // this is a test.
_, err = db.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), childAgentOne.ID)
require.NoError(t, err)
})

Expand Down Expand Up @@ -912,7 +912,7 @@ func TestSubAgentAPI(t *testing.T) {
require.NoError(t, err)

// Verify that the apps were created
apps, err := api.Database.GetWorkspaceAppsByAgentID(dbauthz.AsSystemRestricted(ctx), subAgentID) //nolint:gocritic // this is a test.
apps, err := api.Database.GetWorkspaceAppsByAgentID(dbauthz.AsSystemRestricted(ctx), subAgentID)
require.NoError(t, err)
require.Len(t, apps, 2)

Expand All @@ -923,7 +923,7 @@ func TestSubAgentAPI(t *testing.T) {
require.NoError(t, err)

// Then: The agent is deleted
_, err = api.Database.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), subAgentID) //nolint:gocritic // this is a test.
_, err = api.Database.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), subAgentID)
require.ErrorIs(t, err, sql.ErrNoRows)

// And: The apps are *retained* to avoid causing issues
Expand Down Expand Up @@ -1068,7 +1068,7 @@ func TestSubAgentAPI(t *testing.T) {
agentID, err := uuid.FromBytes(createResp.Agent.Id)
require.NoError(t, err)

subAgent, err := api.Database.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), agentID) //nolint:gocritic // this is a test.
subAgent, err := api.Database.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), agentID)
require.NoError(t, err)

require.Equal(t, len(tt.expectedApps), len(subAgent.DisplayApps), "display apps count mismatch")
Expand Down Expand Up @@ -1118,14 +1118,14 @@ func TestSubAgentAPI(t *testing.T) {
require.NoError(t, err)

// Verify display apps
subAgent, err := api.Database.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), agentID) //nolint:gocritic // this is a test.
subAgent, err := api.Database.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), agentID)
require.NoError(t, err)
require.Len(t, subAgent.DisplayApps, 2)
require.Equal(t, database.DisplayAppVscode, subAgent.DisplayApps[0])
require.Equal(t, database.DisplayAppWebTerminal, subAgent.DisplayApps[1])

// Verify regular apps
apps, err := api.Database.GetWorkspaceAppsByAgentID(dbauthz.AsSystemRestricted(ctx), agentID) //nolint:gocritic // this is a test.
apps, err := api.Database.GetWorkspaceAppsByAgentID(dbauthz.AsSystemRestricted(ctx), agentID)
require.NoError(t, err)
require.Len(t, apps, 1)
require.Equal(t, "v4qhkq17-custom-app", apps[0].Slug)
Expand Down Expand Up @@ -1190,7 +1190,7 @@ func TestSubAgentAPI(t *testing.T) {
})

// When: We list the sub agents.
listResp, err := api.ListSubAgents(ctx, &proto.ListSubAgentsRequest{}) //nolint:gocritic // this is a test.
listResp, err := api.ListSubAgents(ctx, &proto.ListSubAgentsRequest{})
require.NoError(t, err)

listedChildAgents := listResp.Agents
Expand Down
14 changes: 14 additions & 0 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/coder/coder/v2/coderd/oauth2provider"
"github.com/coder/coder/v2/coderd/pproflabel"
"github.com/coder/coder/v2/coderd/prebuilds"
"github.com/coder/coder/v2/coderd/usage"
"github.com/coder/coder/v2/coderd/wsbuilder"

"github.com/andybalholm/brotli"
Expand Down Expand Up @@ -200,6 +201,7 @@ type Options struct {
TemplateScheduleStore *atomic.Pointer[schedule.TemplateScheduleStore]
UserQuietHoursScheduleStore *atomic.Pointer[schedule.UserQuietHoursScheduleStore]
AccessControlStore *atomic.Pointer[dbauthz.AccessControlStore]
UsageInserter *atomic.Pointer[usage.Inserter]
// CoordinatorResumeTokenProvider is used to provide and validate resume
// tokens issued by and passed to the coordinator DRPC API.
CoordinatorResumeTokenProvider tailnet.ResumeTokenProvider
Expand Down Expand Up @@ -428,6 +430,13 @@ func New(options *Options) *API {
v := schedule.NewAGPLUserQuietHoursScheduleStore()
options.UserQuietHoursScheduleStore.Store(&v)
}
if options.UsageInserter == nil {
options.UsageInserter = &atomic.Pointer[usage.Inserter]{}
}
if options.UsageInserter.Load() == nil {
inserter := usage.NewAGPLInserter()
options.UsageInserter.Store(&inserter)
}
if options.OneTimePasscodeValidityPeriod == 0 {
options.OneTimePasscodeValidityPeriod = 20 * time.Minute
}
Expand Down Expand Up @@ -590,6 +599,7 @@ func New(options *Options) *API {
UserQuietHoursScheduleStore: options.UserQuietHoursScheduleStore,
AccessControlStore: options.AccessControlStore,
BuildUsageChecker: &buildUsageChecker,
UsageInserter: options.UsageInserter,
FileCache: files.New(options.PrometheusRegistry, options.Authorizer),
Experiments: experiments,
WebpushDispatcher: options.WebPushDispatcher,
Expand Down Expand Up @@ -1690,6 +1700,9 @@ type API struct {
// BuildUsageChecker is a pointer as it's passed around to multiple
// components.
BuildUsageChecker *atomic.Pointer[wsbuilder.UsageChecker]
// UsageInserter is a pointer to an atomic pointer because it is passed to
// multiple components.
UsageInserter *atomic.Pointer[usage.Inserter]

UpdatesProvider tailnet.WorkspaceUpdatesProvider

Expand Down Expand Up @@ -1905,6 +1918,7 @@ func (api *API) CreateInMemoryTaggedProvisionerDaemon(dialCtx context.Context, n
&api.Auditor,
api.TemplateScheduleStore,
api.UserQuietHoursScheduleStore,
api.UsageInserter,
api.DeploymentValues,
provisionerdserver.Options{
OIDCConfig: api.OIDCConfig,
Expand Down
28 changes: 16 additions & 12 deletions coderd/database/dbauthz/dbauthz.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ var (
// Provisionerd creates workspaces resources monitor
rbac.ResourceWorkspaceAgentResourceMonitor.Type: {policy.ActionCreate},
rbac.ResourceWorkspaceAgentDevcontainers.Type: {policy.ActionCreate},
// Provisionerd creates usage events
rbac.ResourceUsageEvent.Type: {policy.ActionCreate},
}),
Org: map[string][]rbac.Permission{},
User: []rbac.Permission{},
Expand Down Expand Up @@ -510,17 +512,19 @@ var (
Scope: rbac.ScopeAll,
}.WithCachedASTValue()

subjectUsageTracker = rbac.Subject{
Type: rbac.SubjectTypeUsageTracker,
FriendlyName: "Usage Tracker",
subjectUsagePublisher = rbac.Subject{
Type: rbac.SubjectTypeUsagePublisher,
FriendlyName: "Usage Publisher",
ID: uuid.Nil.String(),
Roles: rbac.Roles([]rbac.Role{
{
Identifier: rbac.RoleIdentifier{Name: "usage-tracker"},
DisplayName: "Usage Tracker",
Identifier: rbac.RoleIdentifier{Name: "usage-publisher"},
DisplayName: "Usage Publisher",
Site: rbac.Permissions(map[string][]policy.Action{
rbac.ResourceLicense.Type: {policy.ActionRead},
rbac.ResourceUsageEvent.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate},
rbac.ResourceLicense.Type: {policy.ActionRead},
// The usage publisher doesn't create events, just
// reads/processes them.
rbac.ResourceUsageEvent.Type: {policy.ActionRead, policy.ActionUpdate},
}),
Org: map[string][]rbac.Permission{},
User: []rbac.Permission{},
Expand Down Expand Up @@ -604,10 +608,10 @@ func AsFileReader(ctx context.Context) context.Context {
return As(ctx, subjectFileReader)
}

// AsUsageTracker returns a context with an actor that has permissions required
// for creating, reading, and updating usage events.
func AsUsageTracker(ctx context.Context) context.Context {
return As(ctx, subjectUsageTracker)
// AsUsagePublisher returns a context with an actor that has permissions
// required for creating, reading, and updating usage events.
func AsUsagePublisher(ctx context.Context) context.Context {
return As(ctx, subjectUsagePublisher)
}

var AsRemoveActor = rbac.Subject{
Expand Down Expand Up @@ -3038,7 +3042,7 @@ func (q *querier) GetTemplatesWithFilter(ctx context.Context, arg database.GetTe
}

func (q *querier) GetUnexpiredLicenses(ctx context.Context) ([]database.License, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceLicense); err != nil {
return nil, err
}
return q.db.GetUnexpiredLicenses(ctx)
Expand Down
Loading
Loading