@@ -3,9 +3,12 @@ package dbcrypt
3
3
import (
4
4
"context"
5
5
"database/sql"
6
+ "encoding/base64"
7
+ "runtime"
6
8
"strings"
7
9
"sync/atomic"
8
10
11
+ "cdr.dev/slog"
9
12
"golang.org/x/xerrors"
10
13
11
14
"github.com/coder/coder/coderd/database"
@@ -22,6 +25,7 @@ type Options struct {
22
25
// to encrypt/decrypt user link and git auth link tokens. If this is nil,
23
26
// then no encryption/decryption will be performed.
24
27
ExternalTokenCipher * atomic.Pointer [cryptorand.Cipher ]
28
+ Logger slog.Logger
25
29
}
26
30
27
31
// New creates a database.Store wrapper that encrypts/decrypts values
@@ -128,19 +132,25 @@ func (db *dbCrypt) encryptFields(fields ...*string) error {
128
132
if err != nil {
129
133
return err
130
134
}
131
- * field = MagicPrefix + string (encrypted )
135
+ // Base64 is used to support UTF-8 encoding in PostgreSQL.
136
+ * field = MagicPrefix + base64 .StdEncoding .EncodeToString (encrypted )
132
137
}
133
138
return nil
134
139
}
135
140
136
141
// decryptFields decrypts the given fields in place.
137
142
// If the value fails to decrypt, sql.ErrNoRows will be returned.
138
143
func (db * dbCrypt ) decryptFields (deleteFn func () error , fields ... * string ) error {
139
- delete := func () error {
144
+ delete := func (reason string ) error {
140
145
err := deleteFn ()
141
146
if err != nil {
142
147
return xerrors .Errorf ("delete encrypted row: %w" , err )
143
148
}
149
+ pc , _ , _ , ok := runtime .Caller (2 )
150
+ details := runtime .FuncForPC (pc )
151
+ if ok && details != nil {
152
+ db .Logger .Debug (context .Background (), "deleted row" , slog .F ("reason" , reason ), slog .F ("caller" , details .Name ()))
153
+ }
144
154
return sql .ErrNoRows
145
155
}
146
156
@@ -154,7 +164,7 @@ func (db *dbCrypt) decryptFields(deleteFn func() error, fields ...*string) error
154
164
if strings .HasPrefix (* field , MagicPrefix ) {
155
165
// If we have a magic prefix but encryption is disabled,
156
166
// we should delete the row.
157
- return delete ()
167
+ return delete ("encryption disabled" )
158
168
}
159
169
}
160
170
return nil
@@ -166,13 +176,19 @@ func (db *dbCrypt) decryptFields(deleteFn func() error, fields ...*string) error
166
176
continue
167
177
}
168
178
if len (* field ) < len (MagicPrefix ) || ! strings .HasPrefix (* field , MagicPrefix ) {
179
+ // We do not force encryption of unencrypted rows. This could be damaging
180
+ // to the deployment, and admins can always manually purge data.
169
181
continue
170
182
}
171
-
172
- decrypted , err := cipher .Decrypt ([]byte ((* field )[len (MagicPrefix ):]))
183
+ data , err := base64 .StdEncoding .DecodeString ((* field )[len (MagicPrefix ):])
184
+ if err != nil {
185
+ // If it's not base64 with the prefix, we should delete the row.
186
+ return delete ("stored value was not base64 encoded" )
187
+ }
188
+ decrypted , err := cipher .Decrypt (data )
173
189
if err != nil {
174
190
// If the encryption key changed, we should delete the row.
175
- return delete ()
191
+ return delete ("encryption key changed" )
176
192
}
177
193
* field = string (decrypted )
178
194
}
0 commit comments