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

Commit63eaf5c

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

File tree

17 files changed

+1244
-28
lines changed

17 files changed

+1244
-28
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: 80 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"
@@ -18,6 +19,7 @@ import (
1819
"github.com/coder/coder/v2/coderd/database/dbtime"
1920
"github.com/coder/coder/v2/coderd/httpapi"
2021
"github.com/coder/coder/v2/coderd/httpmw"
22+
"github.com/coder/coder/v2/coderd/rbac"
2123
"github.com/coder/coder/v2/coderd/rbac/policy"
2224
"github.com/coder/coder/v2/coderd/telemetry"
2325
"github.com/coder/coder/v2/codersdk"
@@ -75,8 +77,7 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
7577
}
7678

7779
ifcreateToken.Lifetime!=0 {
78-
err:=api.validateAPIKeyLifetime(createToken.Lifetime)
79-
iferr!=nil {
80+
iferr:=api.validateAPIKeyLifetime(ctx,createToken.Lifetime,user.ID);err!=nil {
8081
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
8182
Message:"Failed to validate create API key request.",
8283
Detail:err.Error(),
@@ -338,35 +339,101 @@ func (api *API) deleteAPIKey(rw http.ResponseWriter, r *http.Request) {
338339
// @Success 200 {object} codersdk.TokenConfig
339340
// @Router /users/{user}/keys/tokens/tokenconfig [get]
340341
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
342+
ctx:=r.Context()
343+
user:=httpmw.UserParam(r)
344+
345+
varroleIdentifiers []rbac.RoleIdentifier
346+
347+
ifuser.ID!=uuid.Nil {
348+
subject,userStatus,err:=httpmw.UserRBACSubject(ctx,api.Database,user.ID,rbac.ScopeAll)
349+
switch {
350+
caseerr!=nil:
351+
api.Logger.Error(ctx,"failed to get user RBAC subject for token config","user_id",user.ID.String(),"error",err)
352+
roleIdentifiers= []rbac.RoleIdentifier{}
353+
caseuserStatus==database.UserStatusSuspended:
354+
roleIdentifiers= []rbac.RoleIdentifier{}
355+
default:
356+
// Extract role names from the RBAC subject and convert to internal format
357+
roleIdentifiers=subject.Roles.Names()
358+
}
359+
}else {
360+
api.Logger.Warn(ctx,"user ID is nil in token config request context")
361+
roleIdentifiers= []rbac.RoleIdentifier{}
345362
}
346363

364+
maxLifetime:=api.getMaxTokenLifetimeForUserRoles(roleIdentifiers)
365+
347366
httpapi.Write(
348-
r.Context(),rw,http.StatusOK,
367+
ctx,rw,http.StatusOK,
349368
codersdk.TokenConfig{
350-
MaxTokenLifetime:values.Sessions.MaximumTokenDuration.Value(),
369+
MaxTokenLifetime:maxLifetime,
351370
},
352371
)
353372
}
354373

355-
func (api*API)validateAPIKeyLifetime(lifetime time.Duration)error {
374+
func (api*API)validateAPIKeyLifetime(ctx context.Context,lifetime time.Duration,userID uuid.UUID)error {
356375
iflifetime<=0 {
357376
returnxerrors.New("lifetime must be positive number greater than 0")
358377
}
359378

360-
iflifetime>api.DeploymentValues.Sessions.MaximumTokenDuration.Value() {
379+
subject,userStatus,err:=httpmw.UserRBACSubject(ctx,api.Database,userID,rbac.ScopeAll)
380+
iferr!=nil {
381+
api.Logger.Error(ctx,"failed to get user RBAC subject during token validation","user_id",userID.String(),"error",err)
382+
ifxerrors.Is(err,sql.ErrNoRows) {
383+
returnxerrors.Errorf("user %s not found",userID)
384+
}
385+
returnxerrors.Errorf("internal server error validating token lifetime for user %s",userID)
386+
}
387+
388+
ifuserStatus==database.UserStatusSuspended {
389+
returnxerrors.Errorf("user %s is suspended and cannot create tokens",userID)
390+
}
391+
392+
// Extract role names from the RBAC subject and convert to internal format
393+
roleIdentifiers:=subject.Roles.Names()
394+
maxAllowedLifetime:=api.getMaxTokenLifetimeForUserRoles(roleIdentifiers)
395+
396+
iflifetime>maxAllowedLifetime {
361397
returnxerrors.Errorf(
362-
"lifetime must be less than %v",
363-
api.DeploymentValues.Sessions.MaximumTokenDuration,
398+
"requested lifetime of %v exceeds the maximum allowed %v based on your roles",
399+
lifetime,
400+
maxAllowedLifetime,
364401
)
365402
}
366-
367403
returnnil
368404
}
369405

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

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp