Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commita8ae9b3

Browse files
authored
feat: enforce upper bounds on workspace TTL and Deadline (#1902)
* Enforces upper bound for workspace TTL* Enforces upper bound for workspace deadline
1 parent17a57a4 commita8ae9b3

File tree

2 files changed

+143
-36
lines changed

2 files changed

+143
-36
lines changed

‎coderd/workspaces.go

Lines changed: 75 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -347,10 +347,18 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
347347
dbAutostartSchedule.String=*createWorkspace.AutostartSchedule
348348
}
349349

350-
vardbTTL sql.NullInt64
351-
ifcreateWorkspace.TTL!=nil&&*createWorkspace.TTL>0 {
352-
dbTTL.Valid=true
353-
dbTTL.Int64=int64(*createWorkspace.TTL)
350+
dbTTL,err:=validWorkspaceTTL(createWorkspace.TTL)
351+
iferr!=nil {
352+
httpapi.Write(rw,http.StatusBadRequest, httpapi.Response{
353+
Message:"validate workspace ttl",
354+
Errors: []httpapi.Error{
355+
{
356+
Field:"ttl",
357+
Detail:err.Error(),
358+
},
359+
},
360+
})
361+
return
354362
}
355363

356364
workspace,err:=api.Database.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{
@@ -559,14 +567,21 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
559567
return
560568
}
561569

562-
vardbTTL sql.NullInt64
563-
ifreq.TTL!=nil&&*req.TTL>0 {
564-
truncated:=req.TTL.Truncate(time.Minute)
565-
dbTTL.Int64=int64(truncated)
566-
dbTTL.Valid=true
570+
dbTTL,err:=validWorkspaceTTL(req.TTL)
571+
iferr!=nil {
572+
httpapi.Write(rw,http.StatusBadRequest, httpapi.Response{
573+
Message:"validate workspace ttl",
574+
Errors: []httpapi.Error{
575+
{
576+
Field:"ttl",
577+
Detail:err.Error(),
578+
},
579+
},
580+
})
581+
return
567582
}
568583

569-
err:=api.Database.UpdateWorkspaceTTL(r.Context(), database.UpdateWorkspaceTTLParams{
584+
err=api.Database.UpdateWorkspaceTTL(r.Context(), database.UpdateWorkspaceTTLParams{
570585
ID:workspace.ID,
571586
Ttl:dbTTL,
572587
})
@@ -590,36 +605,29 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) {
590605
return
591606
}
592607

593-
varcode=http.StatusOK
608+
code:=http.StatusOK
609+
resp:= httpapi.Response{}
594610

595611
err:=api.Database.InTx(func(s database.Store)error {
596612
build,err:=s.GetLatestWorkspaceBuildByWorkspaceID(r.Context(),workspace.ID)
597613
iferr!=nil {
598614
code=http.StatusInternalServerError
615+
resp.Message="workspace not found"
599616
returnxerrors.Errorf("get latest workspace build: %w",err)
600617
}
601618

602619
ifbuild.Transition!=database.WorkspaceTransitionStart {
603620
code=http.StatusConflict
621+
resp.Message="workspace must be started, current status: "+string(build.Transition)
604622
returnxerrors.Errorf("workspace must be started, current status: %s",build.Transition)
605623
}
606624

607625
newDeadline:=req.Deadline.UTC()
608-
ifnewDeadline.IsZero() {
609-
// This should not be possible because the struct validation field enforces a non-zero value.
610-
code=http.StatusBadRequest
611-
returnxerrors.New("new deadline cannot be zero")
612-
}
613-
614-
ifnewDeadline.Before(build.Deadline)||newDeadline.Before(time.Now()) {
626+
iferr:=validWorkspaceDeadline(build.Deadline,newDeadline);err!=nil {
615627
code=http.StatusBadRequest
616-
returnxerrors.Errorf("new deadline %q must be after existing deadline %q",newDeadline.Format(time.RFC3339),build.Deadline.Format(time.RFC3339))
617-
}
618-
619-
// Disallow updates within less than one minute
620-
ifwithinDuration(newDeadline,build.Deadline,time.Minute) {
621-
code=http.StatusNotModified
622-
returnnil
628+
resp.Message="bad extend workspace request"
629+
resp.Errors=append(resp.Errors, httpapi.Error{Field:"deadline",Detail:err.Error()})
630+
returnerr
623631
}
624632

625633
iferr:=s.UpdateWorkspaceBuildByID(r.Context(), database.UpdateWorkspaceBuildByIDParams{
@@ -628,15 +636,17 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) {
628636
ProvisionerState:build.ProvisionerState,
629637
Deadline:newDeadline,
630638
});err!=nil {
639+
code=http.StatusInternalServerError
640+
resp.Message="failed to extend workspace deadline"
631641
returnxerrors.Errorf("update workspace build: %w",err)
632642
}
643+
resp.Message="deadline updated to "+newDeadline.Format(time.RFC3339)
633644

634645
returnnil
635646
})
636647

637-
varresp= httpapi.Response{}
638648
iferr!=nil {
639-
resp.Message=err.Error()
649+
api.Logger.Info(r.Context(),"extending workspace",slog.Error(err))
640650
}
641651
httpapi.Write(rw,code,resp)
642652
}
@@ -850,11 +860,44 @@ func convertSQLNullInt64(i sql.NullInt64) *time.Duration {
850860
return (*time.Duration)(&i.Int64)
851861
}
852862

853-
funcwithinDuration(t1,t2 time.Time,d time.Duration)bool {
854-
dt:=t1.Sub(t2)
855-
ifdt<-d||dt>d {
856-
returnfalse
863+
funcvalidWorkspaceTTL(ttl*time.Duration) (sql.NullInt64,error) {
864+
ifttl==nil {
865+
return sql.NullInt64{},nil
866+
}
867+
868+
truncated:=ttl.Truncate(time.Minute)
869+
iftruncated<time.Minute {
870+
return sql.NullInt64{},xerrors.New("ttl must be at least one minute")
871+
}
872+
873+
iftruncated>24*7*time.Hour {
874+
return sql.NullInt64{},xerrors.New("ttl must be less than 7 days")
875+
}
876+
877+
return sql.NullInt64{
878+
Valid:true,
879+
Int64:int64(truncated),
880+
},nil
881+
}
882+
883+
funcvalidWorkspaceDeadline(old,new time.Time)error {
884+
ifold.IsZero() {
885+
returnxerrors.New("nothing to do: no existing deadline set")
886+
}
887+
888+
now:=time.Now()
889+
ifnew.Before(now) {
890+
returnxerrors.New("new deadline must be in the future")
891+
}
892+
893+
delta:=new.Sub(old)
894+
ifdelta<time.Minute {
895+
returnxerrors.New("minimum extension is one minute")
896+
}
897+
898+
ifdelta>24*time.Hour {
899+
returnxerrors.New("maximum extension is 24 hours")
857900
}
858901

859-
returntrue
902+
returnnil
860903
}

‎coderd/workspaces_test.go

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,49 @@ func TestPostWorkspacesByOrganization(t *testing.T) {
164164
coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
165165
_=coderdtest.CreateWorkspace(t,client,user.OrganizationID,template.ID)
166166
})
167+
168+
t.Run("InvalidTTL",func(t*testing.T) {
169+
t.Parallel()
170+
t.Run("BelowMin",func(t*testing.T) {
171+
t.Parallel()
172+
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
173+
user:=coderdtest.CreateFirstUser(t,client)
174+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
175+
template:=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID)
176+
coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
177+
req:= codersdk.CreateWorkspaceRequest{
178+
TemplateID:template.ID,
179+
Name:"testing",
180+
AutostartSchedule:ptr("CRON_TZ=US/Central * * * * *"),
181+
TTL:ptr(59*time.Second),
182+
}
183+
_,err:=client.CreateWorkspace(context.Background(),template.OrganizationID,req)
184+
require.Error(t,err)
185+
varapiErr*codersdk.Error
186+
require.ErrorAs(t,err,&apiErr)
187+
require.Equal(t,http.StatusBadRequest,apiErr.StatusCode())
188+
})
189+
190+
t.Run("AboveMax",func(t*testing.T) {
191+
t.Parallel()
192+
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
193+
user:=coderdtest.CreateFirstUser(t,client)
194+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
195+
template:=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID)
196+
coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
197+
req:= codersdk.CreateWorkspaceRequest{
198+
TemplateID:template.ID,
199+
Name:"testing",
200+
AutostartSchedule:ptr("CRON_TZ=US/Central * * * * *"),
201+
TTL:ptr(24*7*time.Hour+time.Minute),
202+
}
203+
_,err:=client.CreateWorkspace(context.Background(),template.OrganizationID,req)
204+
require.Error(t,err)
205+
varapiErr*codersdk.Error
206+
require.ErrorAs(t,err,&apiErr)
207+
require.Equal(t,http.StatusBadRequest,apiErr.StatusCode())
208+
})
209+
})
167210
}
168211

169212
funcTestWorkspacesByOrganization(t*testing.T) {
@@ -552,10 +595,25 @@ func TestWorkspaceUpdateTTL(t *testing.T) {
552595
expectedError:"",
553596
},
554597
{
555-
name:"enable ttl",
556-
ttl:ptr(time.Hour),
598+
name:"below minimum ttl",
599+
ttl:ptr(30*time.Second),
600+
expectedError:"ttl must be at least one minute",
601+
},
602+
{
603+
name:"minimum ttl",
604+
ttl:ptr(time.Minute),
605+
expectedError:"",
606+
},
607+
{
608+
name:"maximum ttl",
609+
ttl:ptr(24*7*time.Hour),
557610
expectedError:"",
558611
},
612+
{
613+
name:"above maximum ttl",
614+
ttl:ptr(24*7*time.Hour+time.Minute),
615+
expectedError:"ttl must be less than 7 days",
616+
},
559617
}
560618

561619
for_,testCase:=rangetestCases {
@@ -583,7 +641,7 @@ func TestWorkspaceUpdateTTL(t *testing.T) {
583641
})
584642

585643
iftestCase.expectedError!="" {
586-
require.EqualError(t,err,testCase.expectedError,"unexpected error when setting workspace autostop schedule")
644+
require.ErrorContains(t,err,testCase.expectedError,"unexpected error when setting workspace autostop schedule")
587645
return
588646
}
589647

@@ -657,7 +715,13 @@ func TestWorkspaceExtend(t *testing.T) {
657715
err=client.PutExtendWorkspace(ctx,workspace.ID, codersdk.PutExtendWorkspaceRequest{
658716
Deadline:oldDeadline,
659717
})
660-
require.ErrorContains(t,err,"must be after existing deadline","setting an earlier deadline should fail")
718+
require.ErrorContains(t,err,"deadline: minimum extension is one minute","setting an earlier deadline should fail")
719+
720+
// Updating with a time far in the future should also fail
721+
err=client.PutExtendWorkspace(ctx,workspace.ID, codersdk.PutExtendWorkspaceRequest{
722+
Deadline:oldDeadline.AddDate(1,0,0),
723+
})
724+
require.ErrorContains(t,err,"deadline: maximum extension is 24 hours","setting an earlier deadline should fail")
661725

662726
// Ensure deadline still set correctly
663727
updated,err=client.Workspace(ctx,workspace.ID)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp