Skip to content
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
retry insert active key on tx serialization failure
  • Loading branch information
johnstcn committed Sep 5, 2023
commit 381f0783d533e6a8dfd745c24c122e700ce7f0f1
34 changes: 20 additions & 14 deletions enterprise/dbcrypt/dbcrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ func New(ctx context.Context, db database.Store, ciphers ...Cipher) (database.St
Store: db,
}
// nolint: gocritic // This is allowed.
if err := dbc.ensureEncrypted(dbauthz.AsSystemRestricted(ctx)); err != nil {
authCtx := dbauthz.AsSystemRestricted(ctx)
if err := dbc.ensureEncryptedWithRetry(authCtx); err != nil {
return nil, xerrors.Errorf("ensure encrypted database fields: %w", err)
}
return dbc, nil
Expand Down Expand Up @@ -334,6 +335,22 @@ func (db *dbCrypt) decryptField(field *string, digest sql.NullString) error {
return nil
}

func (db *dbCrypt) ensureEncryptedWithRetry(ctx context.Context) error {
err := db.ensureEncrypted(ctx)
if err == nil {
return nil
}
// If we get a serialization error, then we need to retry.
var pqerr *pq.Error
if !xerrors.As(err, &pqerr) {
return err
}
if pqerr.Code != "40001" { // serialization_failure
return err
}
return db.ensureEncrypted(ctx)
}

func (db *dbCrypt) ensureEncrypted(ctx context.Context) error {
return db.InTx(func(s database.Store) error {
// Attempt to read the encrypted test fields of the currently active keys.
Expand Down Expand Up @@ -364,21 +381,10 @@ func (db *dbCrypt) ensureEncrypted(ctx context.Context) error {
}

// If we get here, then we have a new key that we need to insert.
// If this conflicts with another transaction, we do not need to retry as
// the other transaction will have inserted the key for us.
if err := db.InsertDBCryptKey(ctx, database.InsertDBCryptKeyParams{
return db.InsertDBCryptKey(ctx, database.InsertDBCryptKeyParams{
Number: highestNumber + 1,
ActiveKeyDigest: db.primaryCipherDigest,
Test: testValue,
}); err != nil {
var pqErr *pq.Error
if xerrors.As(err, &pqErr) && pqErr.Code == "23505" {
// Unique constraint violation -> another transaction has inserted the key for us.
return nil
}
return err
}

return nil
})
}, &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
}
51 changes: 51 additions & 0 deletions enterprise/dbcrypt/dbcrypt_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import (
"io"
"testing"

"github.com/golang/mock/gomock"
"github.com/lib/pq"
"github.com/stretchr/testify/require"

"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbgen"
"github.com/coder/coder/v2/coderd/database/dbmock"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
)

Expand Down Expand Up @@ -470,6 +473,46 @@ func TestNew(t *testing.T) {
require.Error(t, err)
require.ErrorContains(t, err, "has been revoked")
})

t.Run("Retry", func(t *testing.T) {
t.Parallel()
// Given: a cipher is loaded
cipher := initCipher(t)
ctx, cancel := context.WithCancel(context.Background())
testVal, err := cipher.Encrypt([]byte("coder"))
key := database.DBCryptKey{
Number: 1,
ActiveKeyDigest: sql.NullString{String: cipher.HexDigest(), Valid: true},
Test: b64encode(testVal),
}
require.NoError(t, err)
t.Cleanup(cancel)

// And: a database that returns an error once when we try to serialize a key
ctrl := gomock.NewController(t)
mockDB := dbmock.NewMockStore(ctrl)

gomock.InOrder(
// First try: we get a serialization error.
expectTx(mockDB),
mockDB.EXPECT().GetDBCryptKeys(gomock.Any()).Times(1).Return([]database.DBCryptKey{}, nil),
mockDB.EXPECT().InsertDBCryptKey(gomock.Any(), gomock.Any()).Times(1).Return(&pq.Error{Code: "40001"}),
// Second try: we get the key we wanted to insert initially.
expectTx(mockDB),
mockDB.EXPECT().GetDBCryptKeys(gomock.Any()).Times(1).Return([]database.DBCryptKey{key}, nil),
)

_, err = New(ctx, mockDB, cipher)
require.NoError(t, err)
})
}

func expectTx(mdb *dbmock.MockStore) *gomock.Call {
return mdb.EXPECT().InTx(gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
func(f func(store database.Store) error, _ *sql.TxOptions) error {
return f(mdb)
},
)
}

func requireEncryptedEquals(t *testing.T, c Cipher, value, expected string) {
Expand Down Expand Up @@ -511,3 +554,11 @@ func fakeBase64RandomData(t *testing.T, n int) string {
require.NoError(t, err)
return base64.StdEncoding.EncodeToString(b)
}

func withInTx(mTx *dbmock.MockStore) {
mTx.EXPECT().InTx(gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
func(f func(store database.Store) error, _ *sql.TxOptions) error {
return f(mTx)
},
)
}