Skip to content

Commit 8d5701a

Browse files
johnstcnkylecarbs
andcommitted
feat(coderd): add dbcrypt package
This commit builds upon the previous work in #7959: - Moved dbcrypt package to enterprise/dbcrypt - Modified original dbcrypt behaviour to not delete un-decryptable rows. - Added a table dbcrypt_sentinel used to determine database encryption status. - Added support for multiple encryption keys in dbcrypt. NOTE: This is part 1 of a 2-part PR. This PR focuses mainly on the dbcrypt and database packages. A separate PR will add the required plumbing to integrate this into enterprise/coderd properly. Co-authored-by: Kyle Carberry <kyle@coder.com>
1 parent 16ef97a commit 8d5701a

20 files changed

+1152
-2
lines changed

coderd/database/dbauthz/dbauthz.go

+28
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,13 @@ func (q *querier) GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUI
828828
return q.db.GetAuthorizationUserRoles(ctx, userID)
829829
}
830830

831+
func (q *querier) GetDBCryptSentinelValue(ctx context.Context) (string, error) {
832+
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
833+
return "", err
834+
}
835+
return q.db.GetDBCryptSentinelValue(ctx)
836+
}
837+
831838
func (q *querier) GetDERPMeshKey(ctx context.Context) (string, error) {
832839
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
833840
return "", err
@@ -904,6 +911,13 @@ func (q *querier) GetGitAuthLink(ctx context.Context, arg database.GetGitAuthLin
904911
return fetch(q.log, q.auth, q.db.GetGitAuthLink)(ctx, arg)
905912
}
906913

914+
func (q *querier) GetGitAuthLinksByUserID(ctx context.Context, userID uuid.UUID) ([]database.GitAuthLink, error) {
915+
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
916+
return nil, err
917+
}
918+
return q.db.GetGitAuthLinksByUserID(ctx, userID)
919+
}
920+
907921
func (q *querier) GetGitSSHKey(ctx context.Context, userID uuid.UUID) (database.GitSSHKey, error) {
908922
return fetch(q.log, q.auth, q.db.GetGitSSHKey)(ctx, userID)
909923
}
@@ -1472,6 +1486,13 @@ func (q *querier) GetUserLinkByUserIDLoginType(ctx context.Context, arg database
14721486
return q.db.GetUserLinkByUserIDLoginType(ctx, arg)
14731487
}
14741488

1489+
func (q *querier) GetUserLinksByUserID(ctx context.Context, userID uuid.UUID) ([]database.UserLink, error) {
1490+
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
1491+
return nil, err
1492+
}
1493+
return q.db.GetUserLinksByUserID(ctx, userID)
1494+
}
1495+
14751496
func (q *querier) GetUsers(ctx context.Context, arg database.GetUsersParams) ([]database.GetUsersRow, error) {
14761497
// This does the filtering in SQL.
14771498
prep, err := prepareSQLFilter(ctx, q.auth, rbac.ActionRead, rbac.ResourceUser.Type)
@@ -2134,6 +2155,13 @@ func (q *querier) RegisterWorkspaceProxy(ctx context.Context, arg database.Regis
21342155
return updateWithReturn(q.log, q.auth, fetch, q.db.RegisterWorkspaceProxy)(ctx, arg)
21352156
}
21362157

2158+
func (q *querier) SetDBCryptSentinelValue(ctx context.Context, value string) error {
2159+
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
2160+
return err
2161+
}
2162+
return q.db.SetDBCryptSentinelValue(ctx, value)
2163+
}
2164+
21372165
func (q *querier) TryAcquireLock(ctx context.Context, id int64) (bool, error) {
21382166
return q.db.TryAcquireLock(ctx, id)
21392167
}

coderd/database/dbfake/dbfake.go

+42
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func New() database.Store {
4444
organizationMembers: make([]database.OrganizationMember, 0),
4545
organizations: make([]database.Organization, 0),
4646
users: make([]database.User, 0),
47+
dbcryptSentinelValue: nil,
4748
gitAuthLinks: make([]database.GitAuthLink, 0),
4849
groups: make([]database.Group, 0),
4950
groupMembers: make([]database.GroupMember, 0),
@@ -116,6 +117,7 @@ type data struct {
116117
// New tables
117118
workspaceAgentStats []database.WorkspaceAgentStat
118119
auditLogs []database.AuditLog
120+
dbcryptSentinelValue *string
119121
files []database.File
120122
gitAuthLinks []database.GitAuthLink
121123
gitSSHKey []database.GitSSHKey
@@ -1150,6 +1152,15 @@ func (q *FakeQuerier) GetAuthorizationUserRoles(_ context.Context, userID uuid.U
11501152
}, nil
11511153
}
11521154

1155+
func (q *FakeQuerier) GetDBCryptSentinelValue(_ context.Context) (string, error) {
1156+
q.mutex.RLock()
1157+
defer q.mutex.RUnlock()
1158+
if q.dbcryptSentinelValue == nil {
1159+
return "", sql.ErrNoRows
1160+
}
1161+
return *q.dbcryptSentinelValue, nil
1162+
}
1163+
11531164
func (q *FakeQuerier) GetDERPMeshKey(_ context.Context) (string, error) {
11541165
q.mutex.RLock()
11551166
defer q.mutex.RUnlock()
@@ -1392,6 +1403,18 @@ func (q *FakeQuerier) GetGitAuthLink(_ context.Context, arg database.GetGitAuthL
13921403
return database.GitAuthLink{}, sql.ErrNoRows
13931404
}
13941405

1406+
func (q *FakeQuerier) GetGitAuthLinksByUserID(_ context.Context, userID uuid.UUID) ([]database.GitAuthLink, error) {
1407+
q.mutex.RLock()
1408+
defer q.mutex.RUnlock()
1409+
gals := make([]database.GitAuthLink, 0)
1410+
for _, gal := range q.gitAuthLinks {
1411+
if gal.UserID == userID {
1412+
gals = append(gals, gal)
1413+
}
1414+
}
1415+
return gals, nil
1416+
}
1417+
13951418
func (q *FakeQuerier) GetGitSSHKey(_ context.Context, userID uuid.UUID) (database.GitSSHKey, error) {
13961419
q.mutex.RLock()
13971420
defer q.mutex.RUnlock()
@@ -2832,6 +2855,18 @@ func (q *FakeQuerier) GetUserLinkByUserIDLoginType(_ context.Context, params dat
28322855
return database.UserLink{}, sql.ErrNoRows
28332856
}
28342857

2858+
func (q *FakeQuerier) GetUserLinksByUserID(_ context.Context, userID uuid.UUID) ([]database.UserLink, error) {
2859+
q.mutex.RLock()
2860+
defer q.mutex.RUnlock()
2861+
uls := make([]database.UserLink, 0)
2862+
for _, ul := range q.userLinks {
2863+
if ul.UserID == userID {
2864+
uls = append(uls, ul)
2865+
}
2866+
}
2867+
return uls, nil
2868+
}
2869+
28352870
func (q *FakeQuerier) GetUsers(_ context.Context, params database.GetUsersParams) ([]database.GetUsersRow, error) {
28362871
if err := validateDatabaseType(params); err != nil {
28372872
return nil, err
@@ -4791,6 +4826,13 @@ func (q *FakeQuerier) RegisterWorkspaceProxy(_ context.Context, arg database.Reg
47914826
return database.WorkspaceProxy{}, sql.ErrNoRows
47924827
}
47934828

4829+
func (q *FakeQuerier) SetDBCryptSentinelValue(_ context.Context, value string) error {
4830+
q.mutex.Lock()
4831+
defer q.mutex.Unlock()
4832+
q.dbcryptSentinelValue = &value
4833+
return nil
4834+
}
4835+
47944836
func (*FakeQuerier) TryAcquireLock(_ context.Context, _ int64) (bool, error) {
47954837
return false, xerrors.New("TryAcquireLock must only be called within a transaction")
47964838
}

coderd/database/dbgen/dbgen.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ func UserLink(t testing.TB, db database.Store, orig database.UserLink) database.
473473
LoginType: takeFirst(orig.LoginType, database.LoginTypeGithub),
474474
LinkedID: takeFirst(orig.LinkedID),
475475
OAuthAccessToken: takeFirst(orig.OAuthAccessToken, uuid.NewString()),
476-
OAuthRefreshToken: takeFirst(orig.OAuthAccessToken, uuid.NewString()),
476+
OAuthRefreshToken: takeFirst(orig.OAuthRefreshToken, uuid.NewString()),
477477
OAuthExpiry: takeFirst(orig.OAuthExpiry, database.Now().Add(time.Hour*24)),
478478
})
479479

@@ -486,7 +486,7 @@ func GitAuthLink(t testing.TB, db database.Store, orig database.GitAuthLink) dat
486486
ProviderID: takeFirst(orig.ProviderID, uuid.New().String()),
487487
UserID: takeFirst(orig.UserID, uuid.New()),
488488
OAuthAccessToken: takeFirst(orig.OAuthAccessToken, uuid.NewString()),
489-
OAuthRefreshToken: takeFirst(orig.OAuthAccessToken, uuid.NewString()),
489+
OAuthRefreshToken: takeFirst(orig.OAuthRefreshToken, uuid.NewString()),
490490
OAuthExpiry: takeFirst(orig.OAuthExpiry, database.Now().Add(time.Hour*24)),
491491
CreatedAt: takeFirst(orig.CreatedAt, database.Now()),
492492
UpdatedAt: takeFirst(orig.UpdatedAt, database.Now()),

coderd/database/dbmetrics/dbmetrics.go

+28
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dbmock/dbmock.go

+59
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dump.sql

+14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE IF EXISTS dbcrypt_sentinel;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CREATE TABLE IF NOT EXISTS dbcrypt_sentinel (
2+
only_one integer GENERATED ALWAYS AS (1) STORED UNIQUE,
3+
val text NOT NULL DEFAULT ''::text
4+
);
5+
6+
COMMENT ON TABLE dbcrypt_sentinel IS 'A table used to determine if the database is encrypted';
7+
COMMENT ON COLUMN dbcrypt_sentinel.only_one IS 'Ensures that only one row exists in the table.';
8+
COMMENT ON COLUMN dbcrypt_sentinel.val IS 'Used to determine if the database is encrypted.';

coderd/database/migrations/migrate_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ func TestMigrateUpWithFixtures(t *testing.T) {
266266
"template_version_parameters",
267267
"workspace_build_parameters",
268268
"template_version_variables",
269+
"dbcrypt_sentinel", // having zero rows is a valid state for this table
269270
}
270271
s := &tableStats{s: make(map[string]int)}
271272

coderd/database/models.go

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/querier.go

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)