mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix: expire token for prebuilds user when regenerating session token (#19667)
* provisionerdserver: Expires prebuild user token for workspace, if it exists, when regenerating session token. * dbauthz: disallow prebuilds user from creating api keys * dbpurge: added functionality to expire stale api keys owned by the prebuilds user
This commit is contained in:
@@ -2955,15 +2955,23 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
|
||||
return nil
|
||||
}
|
||||
|
||||
func workspaceSessionTokenName(workspace database.Workspace) string {
|
||||
return fmt.Sprintf("%s_%s_session_token", workspace.OwnerID, workspace.ID)
|
||||
func WorkspaceSessionTokenName(ownerID, workspaceID uuid.UUID) string {
|
||||
return fmt.Sprintf("%s_%s_session_token", ownerID, workspaceID)
|
||||
}
|
||||
|
||||
func (s *server) regenerateSessionToken(ctx context.Context, user database.User, workspace database.Workspace) (string, error) {
|
||||
// NOTE(Cian): Once a workspace is claimed, there's no reason for the session token to be valid any longer.
|
||||
// Not generating any session token at all for a system user may unintentionally break existing templates,
|
||||
// which we want to avoid. If there's no session token for the workspace belonging to the prebuilds user,
|
||||
// then there's nothing for us to worry about here.
|
||||
// TODO(Cian): Update this to handle _all_ system users. At the time of writing, only one system user exists.
|
||||
if err := deleteSessionTokenForUserAndWorkspace(ctx, s.Database, database.PrebuildsSystemUserID, workspace.ID); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
s.Logger.Error(ctx, "failed to delete prebuilds session token", slog.Error(err), slog.F("workspace_id", workspace.ID))
|
||||
}
|
||||
newkey, sessionToken, err := apikey.Generate(apikey.CreateParams{
|
||||
UserID: user.ID,
|
||||
LoginType: user.LoginType,
|
||||
TokenName: workspaceSessionTokenName(workspace),
|
||||
TokenName: WorkspaceSessionTokenName(workspace.OwnerID, workspace.ID),
|
||||
DefaultLifetime: s.DeploymentValues.Sessions.DefaultTokenDuration.Value(),
|
||||
LifetimeSeconds: int64(s.DeploymentValues.Sessions.MaximumTokenDuration.Value().Seconds()),
|
||||
})
|
||||
@@ -2991,10 +2999,14 @@ func (s *server) regenerateSessionToken(ctx context.Context, user database.User,
|
||||
}
|
||||
|
||||
func deleteSessionToken(ctx context.Context, db database.Store, workspace database.Workspace) error {
|
||||
return deleteSessionTokenForUserAndWorkspace(ctx, db, workspace.OwnerID, workspace.ID)
|
||||
}
|
||||
|
||||
func deleteSessionTokenForUserAndWorkspace(ctx context.Context, db database.Store, userID, workspaceID uuid.UUID) error {
|
||||
err := db.InTx(func(tx database.Store) error {
|
||||
key, err := tx.GetAPIKeyByName(ctx, database.GetAPIKeyByNameParams{
|
||||
UserID: workspace.OwnerID,
|
||||
TokenName: workspaceSessionTokenName(workspace),
|
||||
UserID: userID,
|
||||
TokenName: WorkspaceSessionTokenName(userID, workspaceID),
|
||||
})
|
||||
if err == nil {
|
||||
err = tx.DeleteAPIKeyByID(ctx, key.ID)
|
||||
|
||||
@@ -3999,6 +3999,70 @@ func TestNotifications(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_ExpirePrebuildsSessionToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given: a prebuilt workspace where an API key was previously created for the prebuilds user.
|
||||
var (
|
||||
ctx = testutil.Context(t, testutil.WaitShort)
|
||||
srv, db, ps, pd = setup(t, false, nil)
|
||||
user = dbgen.User(t, db, database.User{})
|
||||
template = dbgen.Template(t, db, database.Template{
|
||||
OrganizationID: pd.OrganizationID,
|
||||
CreatedBy: user.ID,
|
||||
})
|
||||
version = dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
TemplateID: uuid.NullUUID{UUID: template.ID, Valid: true},
|
||||
OrganizationID: pd.OrganizationID,
|
||||
CreatedBy: user.ID,
|
||||
})
|
||||
workspace = dbgen.Workspace(t, db, database.WorkspaceTable{
|
||||
OrganizationID: pd.OrganizationID,
|
||||
TemplateID: template.ID,
|
||||
OwnerID: database.PrebuildsSystemUserID,
|
||||
})
|
||||
workspaceBuildID = uuid.New()
|
||||
buildJob = dbgen.ProvisionerJob(t, db, ps, database.ProvisionerJob{
|
||||
OrganizationID: pd.OrganizationID,
|
||||
FileID: dbgen.File(t, db, database.File{CreatedBy: user.ID}).ID,
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
Input: must(json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
||||
WorkspaceBuildID: workspaceBuildID,
|
||||
})),
|
||||
InitiatorID: database.PrebuildsSystemUserID,
|
||||
Tags: pd.Tags,
|
||||
})
|
||||
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
ID: workspaceBuildID,
|
||||
WorkspaceID: workspace.ID,
|
||||
TemplateVersionID: version.ID,
|
||||
JobID: buildJob.ID,
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
InitiatorID: database.PrebuildsSystemUserID,
|
||||
})
|
||||
existingKey, _ = dbgen.APIKey(t, db, database.APIKey{
|
||||
UserID: database.PrebuildsSystemUserID,
|
||||
TokenName: provisionerdserver.WorkspaceSessionTokenName(database.PrebuildsSystemUserID, workspace.ID),
|
||||
})
|
||||
)
|
||||
|
||||
// When: the prebuild claim job is acquired
|
||||
fs := newFakeStream(ctx)
|
||||
err := srv.AcquireJobWithCancel(fs)
|
||||
require.NoError(t, err)
|
||||
job, err := fs.waitForJob()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, job)
|
||||
workspaceBuildJob := job.Type.(*proto.AcquiredJob_WorkspaceBuild_).WorkspaceBuild
|
||||
require.NotNil(t, workspaceBuildJob.Metadata)
|
||||
|
||||
// Assert test invariant: we acquired the expected build job
|
||||
require.Equal(t, workspaceBuildID.String(), workspaceBuildJob.WorkspaceBuildId)
|
||||
// Then: The session token should be deleted
|
||||
_, err = db.GetAPIKeyByID(ctx, existingKey.ID)
|
||||
require.ErrorIs(t, err, sql.ErrNoRows, "api key for prebuilds user should be deleted")
|
||||
}
|
||||
|
||||
type overrides struct {
|
||||
ctx context.Context
|
||||
deploymentValues *codersdk.DeploymentValues
|
||||
|
||||
Reference in New Issue
Block a user