Skip to content

feat(coderd): connect dbcrypt package implementation #9523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 38 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
fb953e4
feat(coderd): add dbcrypt package
johnstcn Sep 4, 2023
3b8140b
feat(coderd): plumb through dbcrypt package
johnstcn Sep 4, 2023
55b93e7
fix indentation
johnstcn Sep 5, 2023
f340cba
fixup! fix indentation
johnstcn Sep 5, 2023
feae634
check for primary key revocation on startup
johnstcn Sep 5, 2023
381f078
retry insert active key on tx serialization failure
johnstcn Sep 5, 2023
c42e6a6
fixup! retry insert active key on tx serialization failure
johnstcn Sep 5, 2023
6a50a43
use database.IsSerializedError
johnstcn Sep 5, 2023
46b1ff4
encryptFields: check for nil field or digest
johnstcn Sep 5, 2023
9c18168
rm insertDBCryptKeyNoLock
johnstcn Sep 5, 2023
6c28ce5
Merge branch 'cj/dbcrypt_redux_1' into cj/dbcrypt_redux_2
johnstcn Sep 5, 2023
c54b64a
Update enterprise/cli/dbcrypt_rotate.go
johnstcn Sep 5, 2023
5959b34
Update enterprise/coderd/coderd.go
johnstcn Sep 5, 2023
b1546b1
add unit test for ExtractAPIKeyMW
johnstcn Sep 5, 2023
3859e03
add unit test for cli.ConnectToPostgres
johnstcn Sep 5, 2023
a4f93c5
Merge remote-tracking branch 'origin/main' into cj/dbcrypt_redux_2
johnstcn Sep 6, 2023
55a0fd0
DON'T PANIC
johnstcn Sep 6, 2023
cce0244
debug log user_ids
johnstcn Sep 6, 2023
d51ec66
dbcrypt-rotate -> server dbcrypt rotate
johnstcn Sep 6, 2023
aa39fcc
refactor: move rotate logic into dbcrypt
johnstcn Sep 6, 2023
e69e3ef
add decrypt/delete commands
johnstcn Sep 6, 2023
ebf4eef
fixup! add decrypt/delete commands
johnstcn Sep 6, 2023
2de6cc3
beef up unit tests, refactor cli
johnstcn Sep 6, 2023
7774811
update golden files
johnstcn Sep 6, 2023
35ca78f
Update codersdk/deployment.go
johnstcn Sep 6, 2023
3a92a7d
Merge remote-tracking branch 'origin/main' into cj/dbcrypt_redux_2
johnstcn Sep 7, 2023
8b1f43c
revoke all active keys on dbcrypt delete
johnstcn Sep 7, 2023
270cdc1
fixup! Merge remote-tracking branch 'origin/main' into cj/dbcrypt_red…
johnstcn Sep 7, 2023
2514ffe
update docs
johnstcn Sep 7, 2023
cd351af
fixup! update docs
johnstcn Sep 7, 2023
2f5c112
fixup! update docs
johnstcn Sep 7, 2023
2450d13
soft-enforce dbcrypt in license
johnstcn Sep 7, 2023
e56b639
do not add external token encryption keys by default (as it will alwa…
johnstcn Sep 7, 2023
441fcbf
update golden files
johnstcn Sep 7, 2023
ba14128
log encryption status on startup
johnstcn Sep 7, 2023
2ae45c6
modify CLI output
johnstcn Sep 7, 2023
13451f0
Merge remote-tracking branch 'origin/main' into cj/dbcrypt_redux_2
johnstcn Sep 7, 2023
b3ff024
rm unused golden file
johnstcn Sep 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor: move rotate logic into dbcrypt
  • Loading branch information
johnstcn committed Sep 6, 2023
commit aa39fcc262af7e26efaa9c8b7f7a9f07d7636c6e
66 changes: 2 additions & 64 deletions enterprise/cli/server_dbcrypt_rotate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ package cli
import (
"bytes"
"context"
"database/sql"
"encoding/base64"

"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
"github.com/coder/coder/v2/cli"
"github.com/coder/coder/v2/cli/clibase"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/enterprise/dbcrypt"

Expand Down Expand Up @@ -95,70 +93,10 @@ func (*RootCmd) dbcryptRotateCmd() *clibase.Cmd {
_ = sqlDB.Close()
}()
logger.Info(ctx, "connected to postgres")

db := database.New(sqlDB)

cryptDB, err := dbcrypt.New(ctx, db, ciphers...)
if err != nil {
return xerrors.Errorf("create cryptdb: %w", err)
}

users, err := cryptDB.GetUsers(ctx, database.GetUsersParams{})
if err != nil {
return xerrors.Errorf("get users: %w", err)
}
logger.Info(ctx, "encrypting user tokens", slog.F("user_count", len(users)))
for idx, usr := range users {
err := cryptDB.InTx(func(tx database.Store) error {
userLinks, err := tx.GetUserLinksByUserID(ctx, usr.ID)
if err != nil {
return xerrors.Errorf("get user links for user: %w", err)
}
for _, userLink := range userLinks {
if _, err := tx.UpdateUserLink(ctx, database.UpdateUserLinkParams{
OAuthAccessToken: userLink.OAuthAccessToken,
OAuthRefreshToken: userLink.OAuthRefreshToken,
OAuthExpiry: userLink.OAuthExpiry,
UserID: usr.ID,
LoginType: usr.LoginType,
}); err != nil {
return xerrors.Errorf("update user link user_id=%s linked_id=%s: %w", userLink.UserID, userLink.LinkedID, err)
}
}
gitAuthLinks, err := tx.GetGitAuthLinksByUserID(ctx, usr.ID)
if err != nil {
return xerrors.Errorf("get git auth links for user: %w", err)
}
for _, gitAuthLink := range gitAuthLinks {
if _, err := tx.UpdateGitAuthLink(ctx, database.UpdateGitAuthLinkParams{
ProviderID: gitAuthLink.ProviderID,
UserID: usr.ID,
UpdatedAt: gitAuthLink.UpdatedAt,
OAuthAccessToken: gitAuthLink.OAuthAccessToken,
OAuthRefreshToken: gitAuthLink.OAuthRefreshToken,
OAuthExpiry: gitAuthLink.OAuthExpiry,
}); err != nil {
return xerrors.Errorf("update git auth link user_id=%s provider_id=%s: %w", gitAuthLink.UserID, gitAuthLink.ProviderID, err)
}
}
return nil
}, &sql.TxOptions{
Isolation: sql.LevelRepeatableRead,
})
if err != nil {
return xerrors.Errorf("update user links: %w", err)
}
logger.Debug(ctx, "encrypted user tokens", slog.F("user_id", usr.ID), slog.F("current", idx+1), slog.F("cipher", ciphers[0].HexDigest()))
if err := dbcrypt.Rotate(ctx, logger, sqlDB, ciphers); err != nil {
return xerrors.Errorf("rotate ciphers: %w", err)
}
logger.Info(ctx, "operation completed successfully")

// Revoke old keys
for _, c := range ciphers[1:] {
if err := db.RevokeDBCryptKey(ctx, c.HexDigest()); err != nil {
return xerrors.Errorf("revoke key: %w", err)
}
logger.Info(ctx, "revoked unused key", slog.F("digest", c.HexDigest()))
}
return nil
},
}
Expand Down
88 changes: 88 additions & 0 deletions enterprise/dbcrypt/rotate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package dbcrypt

import (
"context"
"database/sql"

"golang.org/x/xerrors"

"cdr.dev/slog"
"github.com/coder/coder/v2/coderd/database"
)

// Rotate rotates the database encryption keys by re-encrypting all user tokens
// with the first cipher and revoking all other ciphers.
func Rotate(ctx context.Context, log slog.Logger, sqlDB *sql.DB, ciphers []Cipher) error {
db := database.New(sqlDB)
cryptDB, err := New(ctx, db, ciphers...)
if err != nil {
return xerrors.Errorf("create cryptdb: %w", err)
}

users, err := cryptDB.GetUsers(ctx, database.GetUsersParams{})
if err != nil {
return xerrors.Errorf("get users: %w", err)
}
log.Info(ctx, "encrypting user tokens", slog.F("user_count", len(users)))
for idx, usr := range users {
err := cryptDB.InTx(func(tx database.Store) error {
userLinks, err := tx.GetUserLinksByUserID(ctx, usr.ID)
if err != nil {
return xerrors.Errorf("get user links for user: %w", err)
}
for _, userLink := range userLinks {
if userLink.OAuthAccessTokenKeyID.String == ciphers[0].HexDigest() && userLink.OAuthRefreshTokenKeyID.String == ciphers[0].HexDigest() {
log.Debug(ctx, "skipping user link", slog.F("user_id", usr.ID), slog.F("current", idx+1), slog.F("cipher", ciphers[0].HexDigest()))
continue
}
if _, err := tx.UpdateUserLink(ctx, database.UpdateUserLinkParams{
OAuthAccessToken: userLink.OAuthAccessToken,
OAuthRefreshToken: userLink.OAuthRefreshToken,
OAuthExpiry: userLink.OAuthExpiry,
UserID: usr.ID,
LoginType: usr.LoginType,
}); err != nil {
return xerrors.Errorf("update user link user_id=%s linked_id=%s: %w", userLink.UserID, userLink.LinkedID, err)
}
}

gitAuthLinks, err := tx.GetGitAuthLinksByUserID(ctx, usr.ID)
if err != nil {
return xerrors.Errorf("get git auth links for user: %w", err)
}
for _, gitAuthLink := range gitAuthLinks {
if gitAuthLink.OAuthAccessTokenKeyID.String == ciphers[0].HexDigest() && gitAuthLink.OAuthRefreshTokenKeyID.String == ciphers[0].HexDigest() {
log.Debug(ctx, "skipping git auth link", slog.F("user_id", usr.ID), slog.F("current", idx+1), slog.F("cipher", ciphers[0].HexDigest()))
continue
}
if _, err := tx.UpdateGitAuthLink(ctx, database.UpdateGitAuthLinkParams{
ProviderID: gitAuthLink.ProviderID,
UserID: usr.ID,
UpdatedAt: gitAuthLink.UpdatedAt,
OAuthAccessToken: gitAuthLink.OAuthAccessToken,
OAuthRefreshToken: gitAuthLink.OAuthRefreshToken,
OAuthExpiry: gitAuthLink.OAuthExpiry,
}); err != nil {
return xerrors.Errorf("update git auth link user_id=%s provider_id=%s: %w", gitAuthLink.UserID, gitAuthLink.ProviderID, err)
}
}
return nil
}, &sql.TxOptions{
Isolation: sql.LevelRepeatableRead,
})
if err != nil {
return xerrors.Errorf("update user links: %w", err)
}
log.Debug(ctx, "encrypted user tokens", slog.F("user_id", usr.ID), slog.F("current", idx+1), slog.F("cipher", ciphers[0].HexDigest()))
}

// Revoke old keys
for _, c := range ciphers[1:] {
if err := db.RevokeDBCryptKey(ctx, c.HexDigest()); err != nil {
return xerrors.Errorf("revoke key: %w", err)
}
log.Info(ctx, "revoked unused key", slog.F("digest", c.HexDigest()))
}

return nil
}