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:
Cian Johnston
2025-09-02 09:38:43 +01:00
committed by GitHub
parent a2a758d5a6
commit 06cbb2890f
14 changed files with 344 additions and 8 deletions
+15
View File
@@ -1787,6 +1787,13 @@ func (q *querier) EnqueueNotificationMessage(ctx context.Context, arg database.E
return q.db.EnqueueNotificationMessage(ctx, arg)
}
func (q *querier) ExpirePrebuildsAPIKeys(ctx context.Context, now time.Time) error {
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceApiKey); err != nil {
return err
}
return q.db.ExpirePrebuildsAPIKeys(ctx, now)
}
func (q *querier) FavoriteWorkspace(ctx context.Context, id uuid.UUID) error {
fetch := func(ctx context.Context, id uuid.UUID) (database.Workspace, error) {
return q.db.GetWorkspaceByID(ctx, id)
@@ -3727,6 +3734,14 @@ func (q *querier) GetWorkspacesEligibleForTransition(ctx context.Context, now ti
}
func (q *querier) InsertAPIKey(ctx context.Context, arg database.InsertAPIKeyParams) (database.APIKey, error) {
// TODO(Cian): ideally this would be encoded in the policy, but system users are just members and we
// don't currently have a capability to conditionally deny creating resources by owner ID in a role.
// We also need to enrich rbac.Actor with IsSystem so that we can distinguish all system users.
// For now, there is only one system user (prebuilds).
if act, ok := ActorFromContext(ctx); ok && act.ID == database.PrebuildsSystemUserID.String() {
return database.APIKey{}, logNotAuthorizedError(ctx, q.log, NotAuthorizedError{Err: xerrors.Errorf("prebuild user may not create api keys")})
}
return insert(q.log, q.auth,
rbac.ResourceApiKey.WithOwner(arg.UserID.String()),
q.db.InsertAPIKey)(ctx, arg)
+21
View File
@@ -18,6 +18,7 @@ import (
"golang.org/x/xerrors"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/db2sdk"
@@ -1297,6 +1298,10 @@ func (s *MethodTestSuite) TestUser() {
dbm.EXPECT().DeleteAPIKeysByUserID(gomock.Any(), key.UserID).Return(nil).AnyTimes()
check.Args(key.UserID).Asserts(rbac.ResourceApiKey.WithOwner(key.UserID.String()), policy.ActionDelete).Returns()
}))
s.Run("ExpirePrebuildsAPIKeys", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
dbm.EXPECT().ExpirePrebuildsAPIKeys(gomock.Any(), gomock.Any()).Times(1).Return(nil)
check.Args(dbtime.Now()).Asserts(rbac.ResourceApiKey, policy.ActionDelete).Returns()
}))
s.Run("GetQuotaAllowanceForUser", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
u := testutil.Fake(s.T(), faker, database.User{})
arg := database.GetQuotaAllowanceForUserParams{UserID: u.ID, OrganizationID: uuid.New()}
@@ -4287,3 +4292,19 @@ func (s *MethodTestSuite) TestUsageEvents() {
}).Asserts(rbac.ResourceUsageEvent, policy.ActionRead)
}))
}
// Ensures that the prebuilds actor may never insert an api key.
func TestInsertAPIKey_AsPrebuildsUser(t *testing.T) {
t.Parallel()
prebuildsSubj := rbac.Subject{
ID: database.PrebuildsSystemUserID.String(),
}
ctx := dbauthz.As(testutil.Context(t, testutil.WaitShort), prebuildsSubj)
mDB := dbmock.NewMockStore(gomock.NewController(t))
log := slogtest.Make(t, nil)
mDB.EXPECT().Wrappers().Times(1).Return([]string{})
dbz := dbauthz.New(mDB, nil, log, nil)
faker := gofakeit.New(0)
_, err := dbz.InsertAPIKey(ctx, testutil.Fake(t, faker, database.InsertAPIKeyParams{}))
require.True(t, dbauthz.IsNotAuthorizedError(err))
}