mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
chore: enforce that provisioners can only acquire jobs in their own organization (#12600)
* chore: add org ID as optional param to AcquireJob * chore: plumb through organization id to provisioner daemons * add org id to provisioner domain key * enforce org id argument * dbgen provisioner jobs defaults to default org
This commit is contained in:
@@ -2093,7 +2093,7 @@ func (s *MethodTestSuite) TestSystemFunctions() {
|
||||
j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{
|
||||
StartedAt: sql.NullTime{Valid: false},
|
||||
})
|
||||
check.Args(database.AcquireProvisionerJobParams{Types: []database.ProvisionerType{j.Provisioner}, Tags: must(json.Marshal(j.Tags))}).
|
||||
check.Args(database.AcquireProvisionerJobParams{OrganizationID: j.OrganizationID, Types: []database.ProvisionerType{j.Provisioner}, Tags: must(json.Marshal(j.Tags))}).
|
||||
Asserts( /*rbac.ResourceSystem, rbac.ActionUpdate*/ )
|
||||
}))
|
||||
s.Run("UpdateProvisionerJobWithCompleteByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
|
||||
@@ -187,6 +187,7 @@ func (b WorkspaceBuildBuilder) Do() WorkspaceResponse {
|
||||
// import job as well
|
||||
for {
|
||||
j, err := b.db.AcquireProvisionerJob(ownerCtx, database.AcquireProvisionerJobParams{
|
||||
OrganizationID: job.OrganizationID,
|
||||
StartedAt: sql.NullTime{
|
||||
Time: dbtime.Now(),
|
||||
Valid: true,
|
||||
|
||||
@@ -387,6 +387,12 @@ func GroupMember(t testing.TB, db database.Store, orig database.GroupMember) dat
|
||||
func ProvisionerJob(t testing.TB, db database.Store, ps pubsub.Pubsub, orig database.ProvisionerJob) database.ProvisionerJob {
|
||||
t.Helper()
|
||||
|
||||
var defOrgID uuid.UUID
|
||||
if orig.OrganizationID == uuid.Nil {
|
||||
defOrg, _ := db.GetDefaultOrganization(genCtx)
|
||||
defOrgID = defOrg.ID
|
||||
}
|
||||
|
||||
jobID := takeFirst(orig.ID, uuid.New())
|
||||
// Always set some tags to prevent Acquire from grabbing jobs it should not.
|
||||
if !orig.StartedAt.Time.IsZero() {
|
||||
@@ -401,7 +407,7 @@ func ProvisionerJob(t testing.TB, db database.Store, ps pubsub.Pubsub, orig data
|
||||
ID: jobID,
|
||||
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
|
||||
UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()),
|
||||
OrganizationID: takeFirst(orig.OrganizationID, uuid.New()),
|
||||
OrganizationID: takeFirst(orig.OrganizationID, defOrgID, uuid.New()),
|
||||
InitiatorID: takeFirst(orig.InitiatorID, uuid.New()),
|
||||
Provisioner: takeFirst(orig.Provisioner, database.ProvisionerTypeEcho),
|
||||
StorageMethod: takeFirst(orig.StorageMethod, database.ProvisionerStorageMethodFile),
|
||||
@@ -418,10 +424,11 @@ func ProvisionerJob(t testing.TB, db database.Store, ps pubsub.Pubsub, orig data
|
||||
}
|
||||
if !orig.StartedAt.Time.IsZero() {
|
||||
job, err = db.AcquireProvisionerJob(genCtx, database.AcquireProvisionerJobParams{
|
||||
StartedAt: orig.StartedAt,
|
||||
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
||||
Tags: must(json.Marshal(orig.Tags)),
|
||||
WorkerID: uuid.NullUUID{},
|
||||
StartedAt: orig.StartedAt,
|
||||
OrganizationID: job.OrganizationID,
|
||||
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
|
||||
Tags: must(json.Marshal(orig.Tags)),
|
||||
WorkerID: uuid.NullUUID{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// There is no easy way to make sure we acquire the correct job.
|
||||
|
||||
@@ -803,6 +803,9 @@ func (q *FakeQuerier) AcquireProvisionerJob(_ context.Context, arg database.Acqu
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for index, provisionerJob := range q.provisionerJobs {
|
||||
if provisionerJob.OrganizationID != arg.OrganizationID {
|
||||
continue
|
||||
}
|
||||
if provisionerJob.StartedAt.Valid {
|
||||
continue
|
||||
}
|
||||
@@ -7861,15 +7864,16 @@ func (q *FakeQuerier) UpsertProvisionerDaemon(_ context.Context, arg database.Up
|
||||
}
|
||||
}
|
||||
d := database.ProvisionerDaemon{
|
||||
ID: uuid.New(),
|
||||
CreatedAt: arg.CreatedAt,
|
||||
Name: arg.Name,
|
||||
Provisioners: arg.Provisioners,
|
||||
Tags: maps.Clone(arg.Tags),
|
||||
ReplicaID: uuid.NullUUID{},
|
||||
LastSeenAt: arg.LastSeenAt,
|
||||
Version: arg.Version,
|
||||
APIVersion: arg.APIVersion,
|
||||
ID: uuid.New(),
|
||||
CreatedAt: arg.CreatedAt,
|
||||
Name: arg.Name,
|
||||
Provisioners: arg.Provisioners,
|
||||
Tags: maps.Clone(arg.Tags),
|
||||
ReplicaID: uuid.NullUUID{},
|
||||
LastSeenAt: arg.LastSeenAt,
|
||||
Version: arg.Version,
|
||||
APIVersion: arg.APIVersion,
|
||||
OrganizationID: arg.OrganizationID,
|
||||
}
|
||||
q.provisionerDaemons = append(q.provisionerDaemons, d)
|
||||
return d, nil
|
||||
|
||||
@@ -363,6 +363,7 @@ func TestQueuePosition(t *testing.T) {
|
||||
}
|
||||
|
||||
job, err := db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
||||
OrganizationID: org.ID,
|
||||
StartedAt: sql.NullTime{
|
||||
Time: dbtime.Now(),
|
||||
Valid: true,
|
||||
|
||||
@@ -3941,14 +3941,15 @@ WHERE
|
||||
provisioner_jobs AS nested
|
||||
WHERE
|
||||
nested.started_at IS NULL
|
||||
AND nested.organization_id = $3
|
||||
-- Ensure the caller has the correct provisioner.
|
||||
AND nested.provisioner = ANY($3 :: provisioner_type [ ])
|
||||
AND nested.provisioner = ANY($4 :: provisioner_type [ ])
|
||||
AND CASE
|
||||
-- Special case for untagged provisioners: only match untagged jobs.
|
||||
WHEN nested.tags :: jsonb = '{"scope": "organization", "owner": ""}' :: jsonb
|
||||
THEN nested.tags :: jsonb = $4 :: jsonb
|
||||
THEN nested.tags :: jsonb = $5 :: jsonb
|
||||
-- Ensure the caller satisfies all job tags.
|
||||
ELSE nested.tags :: jsonb <@ $4 :: jsonb
|
||||
ELSE nested.tags :: jsonb <@ $5 :: jsonb
|
||||
END
|
||||
ORDER BY
|
||||
nested.created_at
|
||||
@@ -3960,10 +3961,11 @@ WHERE
|
||||
`
|
||||
|
||||
type AcquireProvisionerJobParams struct {
|
||||
StartedAt sql.NullTime `db:"started_at" json:"started_at"`
|
||||
WorkerID uuid.NullUUID `db:"worker_id" json:"worker_id"`
|
||||
Types []ProvisionerType `db:"types" json:"types"`
|
||||
Tags json.RawMessage `db:"tags" json:"tags"`
|
||||
StartedAt sql.NullTime `db:"started_at" json:"started_at"`
|
||||
WorkerID uuid.NullUUID `db:"worker_id" json:"worker_id"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
Types []ProvisionerType `db:"types" json:"types"`
|
||||
Tags json.RawMessage `db:"tags" json:"tags"`
|
||||
}
|
||||
|
||||
// Acquires the lock for a single job that isn't started, completed,
|
||||
@@ -3976,6 +3978,7 @@ func (q *sqlQuerier) AcquireProvisionerJob(ctx context.Context, arg AcquireProvi
|
||||
row := q.db.QueryRowContext(ctx, acquireProvisionerJob,
|
||||
arg.StartedAt,
|
||||
arg.WorkerID,
|
||||
arg.OrganizationID,
|
||||
pq.Array(arg.Types),
|
||||
arg.Tags,
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ WHERE
|
||||
provisioner_jobs AS nested
|
||||
WHERE
|
||||
nested.started_at IS NULL
|
||||
AND nested.organization_id = @organization_id
|
||||
-- Ensure the caller has the correct provisioner.
|
||||
AND nested.provisioner = ANY(@types :: provisioner_type [ ])
|
||||
AND CASE
|
||||
|
||||
Reference in New Issue
Block a user