Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 03262de

Browse files
committedSep 13, 2024
feat: add schema for key rotation
1 parent bfdc29f commit 03262de

File tree

17 files changed

+733
-0
lines changed

17 files changed

+733
-0
lines changed
 

‎coderd/database/dbauthz/dbauthz.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,13 @@ func (q *querier) DeleteCoordinator(ctx context.Context, id uuid.UUID) error {
10411041
return q.db.DeleteCoordinator(ctx, id)
10421042
}
10431043

1044+
func (q *querier) DeleteCryptoKey(ctx context.Context, arg database.DeleteCryptoKeyParams) (database.CryptoKey, error) {
1045+
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceSystem); err != nil {
1046+
return database.CryptoKey{}, err
1047+
}
1048+
return q.db.DeleteCryptoKey(ctx, arg)
1049+
}
1050+
10441051
func (q *querier) DeleteCustomRole(ctx context.Context, arg database.DeleteCustomRoleParams) error {
10451052
if arg.OrganizationID.UUID != uuid.Nil {
10461053
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceAssignOrgRole.InOrg(arg.OrganizationID.UUID)); err != nil {
@@ -1383,6 +1390,20 @@ func (q *querier) GetCoordinatorResumeTokenSigningKey(ctx context.Context) (stri
13831390
return q.db.GetCoordinatorResumeTokenSigningKey(ctx)
13841391
}
13851392

1393+
func (q *querier) GetCryptoKeyByFeatureAndSequence(ctx context.Context, arg database.GetCryptoKeyByFeatureAndSequenceParams) (database.CryptoKey, error) {
1394+
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
1395+
return database.CryptoKey{}, err
1396+
}
1397+
return q.db.GetCryptoKeyByFeatureAndSequence(ctx, arg)
1398+
}
1399+
1400+
func (q *querier) GetCryptoKeys(ctx context.Context) ([]database.CryptoKey, error) {
1401+
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
1402+
return nil, err
1403+
}
1404+
return q.db.GetCryptoKeys(ctx)
1405+
}
1406+
13861407
func (q *querier) GetDBCryptKeys(ctx context.Context) ([]database.DBCryptKey, error) {
13871408
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
13881409
return nil, err
@@ -1549,6 +1570,13 @@ func (q *querier) GetLastUpdateCheck(ctx context.Context) (string, error) {
15491570
return q.db.GetLastUpdateCheck(ctx)
15501571
}
15511572

1573+
func (q *querier) GetLatestCryptoKeyByFeature(ctx context.Context, feature database.CryptoKeyFeature) (database.CryptoKey, error) {
1574+
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
1575+
return database.CryptoKey{}, err
1576+
}
1577+
return q.db.GetLatestCryptoKeyByFeature(ctx, feature)
1578+
}
1579+
15521580
func (q *querier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (database.WorkspaceBuild, error) {
15531581
if _, err := q.GetWorkspaceByID(ctx, workspaceID); err != nil {
15541582
return database.WorkspaceBuild{}, err
@@ -2654,6 +2682,13 @@ func (q *querier) InsertAuditLog(ctx context.Context, arg database.InsertAuditLo
26542682
return insert(q.log, q.auth, rbac.ResourceAuditLog, q.db.InsertAuditLog)(ctx, arg)
26552683
}
26562684

2685+
func (q *querier) InsertCryptoKey(ctx context.Context, arg database.InsertCryptoKeyParams) (database.CryptoKey, error) {
2686+
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceSystem); err != nil {
2687+
return database.CryptoKey{}, err
2688+
}
2689+
return q.db.InsertCryptoKey(ctx, arg)
2690+
}
2691+
26572692
func (q *querier) InsertCustomRole(ctx context.Context, arg database.InsertCustomRoleParams) (database.CustomRole, error) {
26582693
// Org and site role upsert share the same query. So switch the assertion based on the org uuid.
26592694
if arg.OrganizationID.UUID != uuid.Nil {
@@ -3157,6 +3192,13 @@ func (q *querier) UpdateAPIKeyByID(ctx context.Context, arg database.UpdateAPIKe
31573192
return update(q.log, q.auth, fetch, q.db.UpdateAPIKeyByID)(ctx, arg)
31583193
}
31593194

3195+
func (q *querier) UpdateCryptoKeyDeletesAt(ctx context.Context, arg database.UpdateCryptoKeyDeletesAtParams) (database.CryptoKey, error) {
3196+
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceSystem); err != nil {
3197+
return database.CryptoKey{}, err
3198+
}
3199+
return q.db.UpdateCryptoKeyDeletesAt(ctx, arg)
3200+
}
3201+
31603202
func (q *querier) UpdateCustomRole(ctx context.Context, arg database.UpdateCustomRoleParams) (database.CustomRole, error) {
31613203
if arg.OrganizationID.UUID != uuid.Nil {
31623204
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceAssignOrgRole.InOrg(arg.OrganizationID.UUID)); err != nil {

‎coderd/database/dbgen/dbgen.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dbgen
22

33
import (
44
"context"
5+
"crypto/rand"
56
"crypto/sha256"
67
"database/sql"
78
"encoding/hex"
@@ -893,6 +894,36 @@ func CustomRole(t testing.TB, db database.Store, seed database.CustomRole) datab
893894
return role
894895
}
895896

897+
func CryptoKey(t testing.TB, db database.Store, seed database.CryptoKey) database.CryptoKey {
898+
t.Helper()
899+
900+
b := make([]byte, 96)
901+
_, err := rand.Read(b)
902+
require.NoError(t, err, "generate secret")
903+
904+
key, err := db.InsertCryptoKey(genCtx, database.InsertCryptoKeyParams{
905+
Sequence: takeFirst(seed.Sequence, 123),
906+
Secret: takeFirst(seed.Secret, sql.NullString{
907+
String: hex.EncodeToString(b),
908+
Valid: true,
909+
}),
910+
SecretKeyID: takeFirst(seed.SecretKeyID, sql.NullString{}),
911+
Feature: takeFirst(seed.Feature, database.CryptoKeyFeatureWorkspaceApps),
912+
StartsAt: takeFirst(seed.StartsAt, time.Now()),
913+
})
914+
require.NoError(t, err, "insert crypto key")
915+
916+
if seed.DeletesAt.Valid {
917+
key, err = db.UpdateCryptoKeyDeletesAt(genCtx, database.UpdateCryptoKeyDeletesAtParams{
918+
Feature: key.Feature,
919+
Sequence: key.Sequence,
920+
DeletesAt: sql.NullTime{Time: seed.DeletesAt.Time, Valid: true},
921+
})
922+
require.NoError(t, err, "update crypto key deletes_at")
923+
}
924+
return key
925+
}
926+
896927
func must[V any](v V, err error) V {
897928
if err != nil {
898929
panic(err)

‎coderd/database/dbmem/dbmem.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ type data struct {
153153
// New tables
154154
workspaceAgentStats []database.WorkspaceAgentStat
155155
auditLogs []database.AuditLog
156+
cryptoKeys []database.CryptoKey
156157
dbcryptKeys []database.DBCryptKey
157158
files []database.File
158159
externalAuthLinks []database.ExternalAuthLink
@@ -1434,6 +1435,25 @@ func (*FakeQuerier) DeleteCoordinator(context.Context, uuid.UUID) error {
14341435
return ErrUnimplemented
14351436
}
14361437

1438+
func (q *FakeQuerier) DeleteCryptoKey(_ context.Context, arg database.DeleteCryptoKeyParams) (database.CryptoKey, error) {
1439+
err := validateDatabaseType(arg)
1440+
if err != nil {
1441+
return database.CryptoKey{}, err
1442+
}
1443+
1444+
q.mutex.Lock()
1445+
defer q.mutex.Unlock()
1446+
1447+
for i, key := range q.cryptoKeys {
1448+
if key.Feature == arg.Feature && key.Sequence == arg.Sequence {
1449+
q.cryptoKeys[i].Secret.String = ""
1450+
q.cryptoKeys[i].Secret.Valid = false
1451+
return q.cryptoKeys[i], nil
1452+
}
1453+
}
1454+
return database.CryptoKey{}, sql.ErrNoRows
1455+
}
1456+
14371457
func (q *FakeQuerier) DeleteCustomRole(_ context.Context, arg database.DeleteCustomRoleParams) error {
14381458
err := validateDatabaseType(arg)
14391459
if err != nil {
@@ -2309,6 +2329,41 @@ func (q *FakeQuerier) GetCoordinatorResumeTokenSigningKey(_ context.Context) (st
23092329
return q.coordinatorResumeTokenSigningKey, nil
23102330
}
23112331

2332+
func (q *FakeQuerier) GetCryptoKeyByFeatureAndSequence(_ context.Context, arg database.GetCryptoKeyByFeatureAndSequenceParams) (database.CryptoKey, error) {
2333+
err := validateDatabaseType(arg)
2334+
if err != nil {
2335+
return database.CryptoKey{}, err
2336+
}
2337+
2338+
q.mutex.RLock()
2339+
defer q.mutex.RUnlock()
2340+
2341+
for _, key := range q.cryptoKeys {
2342+
if key.Feature == arg.Feature && key.Sequence == arg.Sequence {
2343+
// Keys with NULL secrets are considered deleted.
2344+
if key.Secret.Valid {
2345+
return key, nil
2346+
}
2347+
return database.CryptoKey{}, sql.ErrNoRows
2348+
}
2349+
}
2350+
2351+
return database.CryptoKey{}, sql.ErrNoRows
2352+
}
2353+
2354+
func (q *FakeQuerier) GetCryptoKeys(_ context.Context) ([]database.CryptoKey, error) {
2355+
q.mutex.RLock()
2356+
defer q.mutex.RUnlock()
2357+
2358+
keys := make([]database.CryptoKey, 0)
2359+
for _, key := range q.cryptoKeys {
2360+
if key.Secret.Valid {
2361+
keys = append(keys, key)
2362+
}
2363+
}
2364+
return keys, nil
2365+
}
2366+
23122367
func (q *FakeQuerier) GetDBCryptKeys(_ context.Context) ([]database.DBCryptKey, error) {
23132368
q.mutex.RLock()
23142369
defer q.mutex.RUnlock()
@@ -2806,6 +2861,23 @@ func (q *FakeQuerier) GetLastUpdateCheck(_ context.Context) (string, error) {
28062861
return string(q.lastUpdateCheck), nil
28072862
}
28082863

2864+
func (q *FakeQuerier) GetLatestCryptoKeyByFeature(_ context.Context, feature database.CryptoKeyFeature) (database.CryptoKey, error) {
2865+
q.mutex.RLock()
2866+
defer q.mutex.RUnlock()
2867+
2868+
var latestKey database.CryptoKey
2869+
for _, key := range q.cryptoKeys {
2870+
if key.Feature == feature && latestKey.Sequence < key.Sequence {
2871+
latestKey = key
2872+
}
2873+
}
2874+
2875+
if latestKey.Secret.Valid {
2876+
return latestKey, nil
2877+
}
2878+
return database.CryptoKey{}, sql.ErrNoRows
2879+
}
2880+
28092881
func (q *FakeQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (database.WorkspaceBuild, error) {
28102882
q.mutex.RLock()
28112883
defer q.mutex.RUnlock()
@@ -6305,6 +6377,28 @@ func (q *FakeQuerier) InsertAuditLog(_ context.Context, arg database.InsertAudit
63056377
return alog, nil
63066378
}
63076379

6380+
func (q *FakeQuerier) InsertCryptoKey(_ context.Context, arg database.InsertCryptoKeyParams) (database.CryptoKey, error) {
6381+
err := validateDatabaseType(arg)
6382+
if err != nil {
6383+
return database.CryptoKey{}, err
6384+
}
6385+
6386+
q.mutex.Lock()
6387+
defer q.mutex.Unlock()
6388+
6389+
key := database.CryptoKey{
6390+
Feature: arg.Feature,
6391+
Sequence: arg.Sequence,
6392+
Secret: arg.Secret,
6393+
SecretKeyID: arg.SecretKeyID,
6394+
StartsAt: arg.StartsAt,
6395+
}
6396+
6397+
q.cryptoKeys = append(q.cryptoKeys, key)
6398+
6399+
return key, nil
6400+
}
6401+
63086402
func (q *FakeQuerier) InsertCustomRole(_ context.Context, arg database.InsertCustomRoleParams) (database.CustomRole, error) {
63096403
err := validateDatabaseType(arg)
63106404
if err != nil {
@@ -7774,6 +7868,25 @@ func (q *FakeQuerier) UpdateAPIKeyByID(_ context.Context, arg database.UpdateAPI
77747868
return sql.ErrNoRows
77757869
}
77767870

7871+
func (q *FakeQuerier) UpdateCryptoKeyDeletesAt(_ context.Context, arg database.UpdateCryptoKeyDeletesAtParams) (database.CryptoKey, error) {
7872+
err := validateDatabaseType(arg)
7873+
if err != nil {
7874+
return database.CryptoKey{}, err
7875+
}
7876+
q.mutex.Lock()
7877+
defer q.mutex.Unlock()
7878+
7879+
for i, key := range q.cryptoKeys {
7880+
if key.Feature == arg.Feature && key.Sequence == arg.Sequence {
7881+
key.DeletesAt = arg.DeletesAt
7882+
q.cryptoKeys[i] = key
7883+
return key, nil
7884+
}
7885+
}
7886+
7887+
return database.CryptoKey{}, sql.ErrNoRows
7888+
}
7889+
77777890
func (q *FakeQuerier) UpdateCustomRole(_ context.Context, arg database.UpdateCustomRoleParams) (database.CustomRole, error) {
77787891
err := validateDatabaseType(arg)
77797892
if err != nil {

‎coderd/database/dbmetrics/dbmetrics.go

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.