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

Commit8b9e310

Browse files
committed
feat: add role-based token lifetime configuration
Change-Id: Ief7d00a53f20340b36060e7c5f15499a672696f3Signed-off-by: Thomas Kosiewski <tk@coder.com>
1 parent1d48131 commit8b9e310

File tree

17 files changed

+1206
-22
lines changed

17 files changed

+1206
-22
lines changed

‎cli/server.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,11 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
813813
returnxerrors.Errorf("set deployment id: %w",err)
814814
}
815815

816+
// Process the role-based token lifetime configurations
817+
iferr:=coderd.ProcessRoleTokenLifetimesConfig(ctx,&vals.Sessions,options.Database,options.Logger);err!=nil {
818+
returnxerrors.Errorf("failed to initialize role token lifetimes configuration: %w",err)
819+
}
820+
816821
// Manage push notifications.
817822
experiments:=coderd.ReadExperiments(options.Logger,options.DeploymentValues.Experiments.Value())
818823
ifexperiments.Enabled(codersdk.ExperimentWebPush) {

‎cli/testdata/coder_server_--help.golden

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ OPTIONS:
6161
server postgres-builtin-url". Note that any special characters in the
6262
URL must be URL-encoded.
6363

64+
--role-token-lifetimes string, $CODER_ROLE_TOKEN_LIFETIMES (default: {})
65+
A JSON mapping of role names to maximum token lifetimes (e.g.,
66+
`{"owner": "720h", "MyOrg/admin": "168h"}`). Overrides the global
67+
max_token_lifetime for specified roles. Site-level roles are direct
68+
names (e.g., 'admin'). Organization-level roles use the format
69+
'OrgName/rolename'. Durations use Go duration format: hours (h),
70+
minutes (m), seconds (s). Common conversions: 1 day = 24h, 7 days =
71+
168h, 30 days = 720h.
72+
6473
--ssh-keygen-algorithm string, $CODER_SSH_KEYGEN_ALGORITHM (default: ed25519)
6574
The algorithm to use for generating ssh keys. Accepted values are
6675
"ed25519", "ecdsa", or "rsa4096".

‎cli/testdata/server-config.yaml.golden

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,14 @@ experiments: []
445445
# performed once per day.
446446
# (default: false, type: bool)
447447
updateCheck: false
448+
# A JSON mapping of role names to maximum token lifetimes (e.g., `{"owner":
449+
# "720h", "MyOrg/admin": "168h"}`). Overrides the global max_token_lifetime for
450+
# specified roles. Site-level roles are direct names (e.g., 'admin').
451+
# Organization-level roles use the format 'OrgName/rolename'. Durations use Go
452+
# duration format: hours (h), minutes (m), seconds (s). Common conversions: 1 day
453+
# = 24h, 7 days = 168h, 30 days = 720h.
454+
# (default: {}, type: string)
455+
roleTokenLifetimes: '{}'
448456
# The default lifetime duration for API tokens. This value is used when creating a
449457
# token without specifying a duration, such as when authenticating the CLI or an
450458
# IDE plugin.

‎coderd/apidoc/docs.go

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/apidoc/swagger.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/apikey.go

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package coderd
22

33
import (
44
"context"
5+
"database/sql"
56
"fmt"
67
"net/http"
78
"strconv"
@@ -75,8 +76,7 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
7576
}
7677

7778
ifcreateToken.Lifetime!=0 {
78-
err:=api.validateAPIKeyLifetime(createToken.Lifetime)
79-
iferr!=nil {
79+
iferr:=api.validateAPIKeyLifetime(ctx,createToken.Lifetime,user.ID);err!=nil {
8080
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
8181
Message:"Failed to validate create API key request.",
8282
Detail:err.Error(),
@@ -338,35 +338,104 @@ func (api *API) deleteAPIKey(rw http.ResponseWriter, r *http.Request) {
338338
// @Success 200 {object} codersdk.TokenConfig
339339
// @Router /users/{user}/keys/tokens/tokenconfig [get]
340340
func (api*API)tokenConfig(rw http.ResponseWriter,r*http.Request) {
341-
values,err:=api.DeploymentValues.WithoutSecrets()
342-
iferr!=nil {
343-
httpapi.InternalServerError(rw,err)
344-
return
341+
ctx:=r.Context()
342+
// Prefer httpmw.UserParam(r) as it's consistently used in this file (e.g., postToken, postAPIKey).
343+
// The user object from UserParam should contain the ID.
344+
user:=httpmw.UserParam(r)
345+
346+
varuserRoles []string
347+
348+
// Check if user has a valid ID.
349+
ifuser.ID!=uuid.Nil {
350+
authzInfo,err:=api.Database.GetAuthorizationUserRoles(ctx,user.ID)
351+
iferr!=nil {
352+
api.Logger.Error(ctx,"failed to get authorization roles for token config","user_id",user.ID.String(),"error",err)
353+
// Fallback to global max if roles can't be fetched
354+
userRoles= []string{}
355+
}else {
356+
userRoles=authzInfo.Roles
357+
}
358+
}else {
359+
// This case implies an issue with user retrieval (e.g. UserParam returning a zero User struct)
360+
// or an unauthenticated request reaching this point.
361+
// Logging as Warn because it's unexpected for an authenticated endpoint.
362+
api.Logger.Warn(ctx,"user ID is nil in token config request context")
363+
// Fallback to global max, implying default/maximum permissions.
364+
userRoles= []string{}
345365
}
346366

367+
maxLifetime:=api.getMaxTokenLifetimeForUserRoles(userRoles)
368+
347369
httpapi.Write(
348-
r.Context(),rw,http.StatusOK,
370+
ctx,rw,http.StatusOK,
349371
codersdk.TokenConfig{
350-
MaxTokenLifetime:values.Sessions.MaximumTokenDuration.Value(),
372+
MaxTokenLifetime:maxLifetime,
351373
},
352374
)
353375
}
354376

355-
func (api*API)validateAPIKeyLifetime(lifetime time.Duration)error {
377+
func (api*API)validateAPIKeyLifetime(ctx context.Context,lifetime time.Duration,userID uuid.UUID)error {
356378
iflifetime<=0 {
357379
returnxerrors.New("lifetime must be positive number greater than 0")
358380
}
359381

360-
iflifetime>api.DeploymentValues.Sessions.MaximumTokenDuration.Value() {
382+
authzInfo,err:=api.Database.GetAuthorizationUserRoles(ctx,userID)
383+
iferr!=nil {
384+
api.Logger.Error(ctx,"failed to get user authorization info during token validation","user_id",userID.String(),"error",err)
385+
ifxerrors.Is(err,sql.ErrNoRows) {
386+
returnxerrors.Errorf("user %s not found",userID)
387+
}
388+
returnxerrors.Errorf("internal server error validating token lifetime for user %s",userID)
389+
}
390+
391+
// Check if user is suspended
392+
ifauthzInfo.Status==database.UserStatusSuspended {
393+
returnxerrors.Errorf("user %s is suspended and cannot create tokens",userID)
394+
}
395+
396+
maxAllowedLifetime:=api.getMaxTokenLifetimeForUserRoles(authzInfo.Roles)
397+
398+
iflifetime>maxAllowedLifetime {
361399
returnxerrors.Errorf(
362-
"lifetime must be less than %v",
363-
api.DeploymentValues.Sessions.MaximumTokenDuration,
400+
"requested lifetime of %v exceeds the maximum allowed %v based on your roles",
401+
lifetime,
402+
maxAllowedLifetime,
364403
)
365404
}
366-
367405
returnnil
368406
}
369407

408+
// getMaxTokenLifetimeForUserRoles determines the most generous token lifetime a user is entitled to
409+
// based on their roles and the CODER_ROLE_TOKEN_LIFETIMES configuration.
410+
// Roles are expected in the internal format ("rolename" or "rolename:org_id").
411+
func (api*API)getMaxTokenLifetimeForUserRoles(roles []string) time.Duration {
412+
globalMaxDefault:=api.DeploymentValues.Sessions.MaximumTokenDuration.Value()
413+
414+
// Early return for empty config
415+
ifapi.DeploymentValues.Sessions.RoleTokenLifetimes.Value()==""||
416+
api.DeploymentValues.Sessions.RoleTokenLifetimes.Value()=="{}" {
417+
returnglobalMaxDefault
418+
}
419+
420+
// Early return for no roles
421+
iflen(roles)==0 {
422+
returnglobalMaxDefault
423+
}
424+
425+
// Find the maximum lifetime among all roles
426+
// This includes both role-specific lifetimes and the global default
427+
maxLifetime:=globalMaxDefault
428+
429+
for_,roleStr:=rangeroles {
430+
roleDuration:=api.DeploymentValues.Sessions.MaxTokenLifetimeForRole(roleStr)
431+
ifroleDuration>maxLifetime {
432+
maxLifetime=roleDuration
433+
}
434+
}
435+
436+
returnmaxLifetime
437+
}
438+
370439
func (api*API)createAPIKey(ctx context.Context,params apikey.CreateParams) (*http.Cookie,*database.APIKey,error) {
371440
key,sessionToken,err:=apikey.Generate(params)
372441
iferr!=nil {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp