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

Commit415d8b1

Browse files
committed
Merge branch 'main' into exportstats
2 parents37ad03f +bb0a996 commit415d8b1

40 files changed

+2767
-926
lines changed

‎cli/server.go

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"crypto/tls"
1111
"crypto/x509"
1212
"database/sql"
13+
"encoding/hex"
1314
"errors"
1415
"fmt"
1516
"io"
@@ -587,19 +588,62 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
587588
deferoptions.Pubsub.Close()
588589
}
589590

590-
deploymentID,err:=options.Database.GetDeploymentID(ctx)
591-
iferrors.Is(err,sql.ErrNoRows) {
592-
err=nil
593-
}
594-
iferr!=nil {
595-
returnxerrors.Errorf("get deployment id: %w",err)
596-
}
597-
ifdeploymentID=="" {
598-
deploymentID=uuid.NewString()
599-
err=options.Database.InsertDeploymentID(ctx,deploymentID)
591+
vardeploymentIDstring
592+
err=options.Database.InTx(func(tx database.Store)error {
593+
// This will block until the lock is acquired, and will be
594+
// automatically released when the transaction ends.
595+
err:=tx.AcquireLock(ctx,database.LockIDDeploymentSetup)
596+
iferr!=nil {
597+
returnxerrors.Errorf("acquire lock: %w",err)
598+
}
599+
600+
deploymentID,err=tx.GetDeploymentID(ctx)
601+
iferr!=nil&&!xerrors.Is(err,sql.ErrNoRows) {
602+
returnxerrors.Errorf("get deployment id: %w",err)
603+
}
604+
ifdeploymentID=="" {
605+
deploymentID=uuid.NewString()
606+
err=tx.InsertDeploymentID(ctx,deploymentID)
607+
iferr!=nil {
608+
returnxerrors.Errorf("set deployment id: %w",err)
609+
}
610+
}
611+
612+
// Read the app signing key from the DB. We store it hex
613+
// encoded since the config table uses strings for the value and
614+
// we don't want to deal with automatic encoding issues.
615+
appSigningKeyStr,err:=tx.GetAppSigningKey(ctx)
616+
iferr!=nil&&!xerrors.Is(err,sql.ErrNoRows) {
617+
returnxerrors.Errorf("get app signing key: %w",err)
618+
}
619+
ifappSigningKeyStr=="" {
620+
// Generate 64 byte secure random string.
621+
b:=make([]byte,64)
622+
_,err:=rand.Read(b)
623+
iferr!=nil {
624+
returnxerrors.Errorf("generate fresh app signing key: %w",err)
625+
}
626+
627+
appSigningKeyStr=hex.EncodeToString(b)
628+
err=tx.InsertAppSigningKey(ctx,appSigningKeyStr)
629+
iferr!=nil {
630+
returnxerrors.Errorf("insert freshly generated app signing key to database: %w",err)
631+
}
632+
}
633+
634+
appSigningKey,err:=hex.DecodeString(appSigningKeyStr)
600635
iferr!=nil {
601-
returnxerrors.Errorf("set deployment id: %w",err)
636+
returnxerrors.Errorf("decode app signing key from database as hex: %w",err)
637+
}
638+
iflen(appSigningKey)!=64 {
639+
returnxerrors.Errorf("app signing key must be 64 bytes, key in database is %d bytes",len(appSigningKey))
602640
}
641+
642+
options.AppSigningKey=appSigningKey
643+
returnnil
644+
},nil)
645+
iferr!=nil {
646+
returnerr
603647
}
604648

605649
// Disable telemetry if the in-memory database is used unless explicitly defined!

‎coderd/coderd.go

Lines changed: 32 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import (
5656
"github.com/coder/coder/coderd/tracing"
5757
"github.com/coder/coder/coderd/updatecheck"
5858
"github.com/coder/coder/coderd/util/slice"
59+
"github.com/coder/coder/coderd/workspaceapps"
5960
"github.com/coder/coder/coderd/wsconncache"
6061
"github.com/coder/coder/codersdk"
6162
"github.com/coder/coder/provisionerd/proto"
@@ -120,6 +121,9 @@ type Options struct {
120121
SwaggerEndpointbool
121122
SetUserGroupsfunc(ctx context.Context,tx database.Store,userID uuid.UUID,groupNames []string)error
122123
TemplateScheduleStore schedule.TemplateScheduleStore
124+
// AppSigningKey denotes the symmetric key to use for signing app tickets.
125+
// The key must be 64 bytes long.
126+
AppSigningKey []byte
123127

124128
// APIRateLimit is the minutely throughput rate limit per user or ip.
125129
// Setting a rate limit <0 will disable the rate limiter across the entire
@@ -214,6 +218,9 @@ func New(options *Options) *API {
214218
ifoptions.TemplateScheduleStore==nil {
215219
options.TemplateScheduleStore=schedule.NewAGPLTemplateScheduleStore()
216220
}
221+
iflen(options.AppSigningKey)!=64 {
222+
panic("coderd: AppSigningKey must be 64 bytes long")
223+
}
217224

218225
siteCacheDir:=options.CacheDir
219226
ifsiteCacheDir!="" {
@@ -239,6 +246,11 @@ func New(options *Options) *API {
239246
// static files since it only affects browsers.
240247
staticHandler=httpmw.HSTS(staticHandler,options.StrictTransportSecurityCfg)
241248

249+
oauthConfigs:=&httpmw.OAuth2Configs{
250+
Github:options.GithubOAuth2Config,
251+
OIDC:options.OIDCConfig,
252+
}
253+
242254
r:=chi.NewRouter()
243255
ctx,cancel:=context.WithCancel(context.Background())
244256
api:=&API{
@@ -253,6 +265,15 @@ func New(options *Options) *API {
253265
Authorizer:options.Authorizer,
254266
Logger:options.Logger,
255267
},
268+
WorkspaceAppsProvider:workspaceapps.New(
269+
options.Logger.Named("workspaceapps"),
270+
options.AccessURL,
271+
options.Authorizer,
272+
options.Database,
273+
options.DeploymentConfig,
274+
oauthConfigs,
275+
options.AppSigningKey,
276+
),
256277
metricsCache:metricsCache,
257278
Auditor: atomic.Pointer[audit.Auditor]{},
258279
TemplateScheduleStore: atomic.Pointer[schedule.TemplateScheduleStore]{},
@@ -269,20 +290,16 @@ func New(options *Options) *API {
269290
api.TemplateScheduleStore.Store(&options.TemplateScheduleStore)
270291
api.workspaceAgentCache=wsconncache.New(api.dialWorkspaceAgentTailnet,0)
271292
api.TailnetCoordinator.Store(&options.TailnetCoordinator)
272-
oauthConfigs:=&httpmw.OAuth2Configs{
273-
Github:options.GithubOAuth2Config,
274-
OIDC:options.OIDCConfig,
275-
}
276293

277-
apiKeyMiddleware:=httpmw.ExtractAPIKey(httpmw.ExtractAPIKeyConfig{
294+
apiKeyMiddleware:=httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{
278295
DB:options.Database,
279296
OAuth2Configs:oauthConfigs,
280297
RedirectToLogin:false,
281298
DisableSessionExpiryRefresh:options.DeploymentConfig.DisableSessionExpiryRefresh.Value,
282299
Optional:false,
283300
})
284301
// Same as above but it redirects to the login page.
285-
apiKeyMiddlewareRedirect:=httpmw.ExtractAPIKey(httpmw.ExtractAPIKeyConfig{
302+
apiKeyMiddlewareRedirect:=httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{
286303
DB:options.Database,
287304
OAuth2Configs:oauthConfigs,
288305
RedirectToLogin:true,
@@ -308,23 +325,9 @@ func New(options *Options) *API {
308325
httpmw.Prometheus(options.PrometheusRegistry),
309326
// handleSubdomainApplications checks if the first subdomain is a valid
310327
// app URL. If it is, it will serve that application.
311-
api.handleSubdomainApplications(
312-
apiRateLimiter,
313-
// Middleware to impose on the served application.
314-
httpmw.ExtractAPIKey(httpmw.ExtractAPIKeyConfig{
315-
DB:options.Database,
316-
OAuth2Configs:oauthConfigs,
317-
// The code handles the the case where the user is not
318-
// authenticated automatically.
319-
RedirectToLogin:false,
320-
DisableSessionExpiryRefresh:options.DeploymentConfig.DisableSessionExpiryRefresh.Value,
321-
Optional:true,
322-
}),
323-
httpmw.AsAuthzSystem(
324-
httpmw.ExtractUserParam(api.Database,false),
325-
httpmw.ExtractWorkspaceAndAgentParam(api.Database),
326-
),
327-
),
328+
//
329+
// Workspace apps do their own auth.
330+
api.handleSubdomainApplications(apiRateLimiter),
328331
// Build-Version is helpful for debugging.
329332
func(next http.Handler) http.Handler {
330333
returnhttp.HandlerFunc(func(w http.ResponseWriter,r*http.Request) {
@@ -348,26 +351,8 @@ func New(options *Options) *API {
348351
r.Get("/healthz",func(w http.ResponseWriter,r*http.Request) {_,_=w.Write([]byte("OK")) })
349352

350353
apps:=func(r chi.Router) {
351-
r.Use(
352-
apiRateLimiter,
353-
httpmw.ExtractAPIKey(httpmw.ExtractAPIKeyConfig{
354-
DB:options.Database,
355-
OAuth2Configs:oauthConfigs,
356-
// Optional is true to allow for public apps. If an
357-
// authorization check fails and the user is not authenticated,
358-
// they will be redirected to the login page by the app handler.
359-
RedirectToLogin:false,
360-
DisableSessionExpiryRefresh:options.DeploymentConfig.DisableSessionExpiryRefresh.Value,
361-
Optional:true,
362-
}),
363-
httpmw.AsAuthzSystem(
364-
// Redirect to the login page if the user tries to open an app with
365-
// "me" as the username and they are not logged in.
366-
httpmw.ExtractUserParam(api.Database,true),
367-
// Extracts the <workspace.agent> from the url
368-
httpmw.ExtractWorkspaceAndAgentParam(api.Database),
369-
),
370-
)
354+
// Workspace apps do their own auth.
355+
r.Use(apiRateLimiter)
371356
r.HandleFunc("/*",api.workspaceAppsProxyPath)
372357
}
373358
// %40 is the encoded character of the @ symbol. VS Code Web does
@@ -746,9 +731,10 @@ type API struct {
746731
WebsocketWaitGroup sync.WaitGroup
747732
derpCloseFuncfunc()
748733

749-
metricsCache*metricscache.Cache
750-
workspaceAgentCache*wsconncache.Cache
751-
updateChecker*updatecheck.Checker
734+
metricsCache*metricscache.Cache
735+
workspaceAgentCache*wsconncache.Cache
736+
updateChecker*updatecheck.Checker
737+
WorkspaceAppsProvider*workspaceapps.Provider
752738

753739
// Experiments contains the list of experiments currently enabled.
754740
// This is used to gate features that are not yet ready for production.

‎coderd/coderdtest/coderdtest.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"crypto/x509"
1212
"crypto/x509/pkix"
1313
"encoding/base64"
14+
"encoding/hex"
1415
"encoding/json"
1516
"encoding/pem"
1617
"errors"
@@ -82,6 +83,10 @@ import (
8283
"github.com/coder/coder/testutil"
8384
)
8485

86+
// AppSigningKey is a 64-byte key used to sign JWTs for workspace app tickets in
87+
// tests.
88+
varAppSigningKey=must(hex.DecodeString("64656164626565666465616462656566646561646265656664656164626565666465616462656566646561646265656664656164626565666465616462656566"))
89+
8590
typeOptionsstruct {
8691
// AccessURL denotes a custom access URL. By default we use the httptest
8792
// server's URL. Setting this may result in unexpected behavior (especially
@@ -330,6 +335,7 @@ func NewOptions(t *testing.T, options *Options) (func(http.Handler), context.Can
330335
DeploymentConfig:options.DeploymentConfig,
331336
UpdateCheckOptions:options.UpdateCheckOptions,
332337
SwaggerEndpoint:options.SwaggerEndpoint,
338+
AppSigningKey:AppSigningKey,
333339
}
334340
}
335341

‎coderd/database/dbauthz/querier.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ func (q *querier) Ping(ctx context.Context) (time.Duration, error) {
1919
returnq.db.Ping(ctx)
2020
}
2121

22+
func (q*querier)AcquireLock(ctx context.Context,idint64)error {
23+
returnq.db.AcquireLock(ctx,id)
24+
}
25+
26+
func (q*querier)TryAcquireLock(ctx context.Context,idint64) (bool,error) {
27+
returnq.db.TryAcquireLock(ctx,id)
28+
}
29+
2230
// InTx runs the given function in a transaction.
2331
func (q*querier)InTx(functionfunc(querier database.Store)error,txOpts*sql.TxOptions)error {
2432
returnq.db.InTx(func(tx database.Store)error {
@@ -317,6 +325,16 @@ func (q *querier) GetLogoURL(ctx context.Context) (string, error) {
317325
returnq.db.GetLogoURL(ctx)
318326
}
319327

328+
func (q*querier)GetAppSigningKey(ctx context.Context) (string,error) {
329+
// No authz checks
330+
returnq.db.GetAppSigningKey(ctx)
331+
}
332+
333+
func (q*querier)InsertAppSigningKey(ctx context.Context,datastring)error {
334+
// No authz checks as this is done during startup
335+
returnq.db.InsertAppSigningKey(ctx,data)
336+
}
337+
320338
func (q*querier)GetServiceBanner(ctx context.Context) (string,error) {
321339
// No authz checks
322340
returnq.db.GetServiceBanner(ctx)

‎coderd/database/dbfake/databasefake.go

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ func New() database.Store {
6464
workspaceApps:make([]database.WorkspaceApp,0),
6565
workspaces:make([]database.Workspace,0),
6666
licenses:make([]database.License,0),
67+
locks:map[int64]struct{}{},
6768
},
6869
}
6970
}
@@ -89,6 +90,11 @@ type fakeQuerier struct {
8990
*data
9091
}
9192

93+
typefakeTxstruct {
94+
*fakeQuerier
95+
locksmap[int64]struct{}
96+
}
97+
9298
typedatastruct {
9399
// Legacy tables
94100
apiKeys []database.APIKey
@@ -124,11 +130,15 @@ type data struct {
124130
workspaceResources []database.WorkspaceResource
125131
workspaces []database.Workspace
126132

133+
// Locks is a map of lock names. Any keys within the map are currently
134+
// locked.
135+
locksmap[int64]struct{}
127136
deploymentIDstring
128137
derpMeshKeystring
129138
lastUpdateCheck []byte
130139
serviceBanner []byte
131140
logoURLstring
141+
appSigningKeystring
132142
lastLicenseIDint32
133143
}
134144

@@ -196,11 +206,50 @@ func (*fakeQuerier) Ping(_ context.Context) (time.Duration, error) {
196206
return0,nil
197207
}
198208

209+
func (*fakeQuerier)AcquireLock(_ context.Context,_int64)error {
210+
returnxerrors.New("AcquireLock must only be called within a transaction")
211+
}
212+
213+
func (*fakeQuerier)TryAcquireLock(_ context.Context,_int64) (bool,error) {
214+
returnfalse,xerrors.New("TryAcquireLock must only be called within a transaction")
215+
}
216+
217+
func (tx*fakeTx)AcquireLock(_ context.Context,idint64)error {
218+
if_,ok:=tx.fakeQuerier.locks[id];ok {
219+
returnxerrors.Errorf("cannot acquire lock %d: already held",id)
220+
}
221+
tx.fakeQuerier.locks[id]=struct{}{}
222+
tx.locks[id]=struct{}{}
223+
returnnil
224+
}
225+
226+
func (tx*fakeTx)TryAcquireLock(_ context.Context,idint64) (bool,error) {
227+
if_,ok:=tx.fakeQuerier.locks[id];ok {
228+
returnfalse,nil
229+
}
230+
tx.fakeQuerier.locks[id]=struct{}{}
231+
tx.locks[id]=struct{}{}
232+
returntrue,nil
233+
}
234+
235+
func (tx*fakeTx)releaseLocks() {
236+
forid:=rangetx.locks {
237+
delete(tx.fakeQuerier.locks,id)
238+
}
239+
tx.locks=map[int64]struct{}{}
240+
}
241+
199242
// InTx doesn't rollback data properly for in-memory yet.
200243
func (q*fakeQuerier)InTx(fnfunc(database.Store)error,_*sql.TxOptions)error {
201244
q.mutex.Lock()
202245
deferq.mutex.Unlock()
203-
returnfn(&fakeQuerier{mutex:inTxMutex{},data:q.data})
246+
tx:=&fakeTx{
247+
fakeQuerier:&fakeQuerier{mutex:inTxMutex{},data:q.data},
248+
locks:map[int64]struct{}{},
249+
}
250+
defertx.releaseLocks()
251+
252+
returnfn(tx)
204253
}
205254

206255
func (q*fakeQuerier)AcquireProvisionerJob(_ context.Context,arg database.AcquireProvisionerJobParams) (database.ProvisionerJob,error) {
@@ -4099,6 +4148,21 @@ func (q *fakeQuerier) GetLogoURL(_ context.Context) (string, error) {
40994148
returnq.logoURL,nil
41004149
}
41014150

4151+
func (q*fakeQuerier)GetAppSigningKey(_ context.Context) (string,error) {
4152+
q.mutex.RLock()
4153+
deferq.mutex.RUnlock()
4154+
4155+
returnq.appSigningKey,nil
4156+
}
4157+
4158+
func (q*fakeQuerier)InsertAppSigningKey(_ context.Context,datastring)error {
4159+
q.mutex.Lock()
4160+
deferq.mutex.Unlock()
4161+
4162+
q.appSigningKey=data
4163+
returnnil
4164+
}
4165+
41024166
func (q*fakeQuerier)InsertLicense(
41034167
_ context.Context,arg database.InsertLicenseParams,
41044168
) (database.License,error) {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp