-
Notifications
You must be signed in to change notification settings - Fork 886
feat(coderd): add dbcrypt package #9421
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
Conversation
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>
} | ||
|
||
func (*DecryptFailedError) Unwrap() error { | ||
return sql.ErrNoRows |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the logic behind making failed decryption look like sql.ErrNoRows
?
I feel like it will mask operational issues (like having the wrong key), making it take longer for ops to find and fix the problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a good answer for that apart from "it felt like the right thing to do"?
But you're correct, making it unwrap to ErrNoRows does mask the case where we can't decrypt a value. In the case of an oauth token, it just means that the user has to re-authenticate. But for other data, we could end up in a weird situation where we select, allegedly get no rows, insert, and hit a duplicate key constraint -- which would be a really annoying problem to figure out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did this initially, and it's a dev-ux problem. I was actually considering changing the way we do this in entirety - not making it an abstraction layer of database.Store
.
It's always problematic in our code to get ErrNoRows
then hit a constraint on insert. We'd have to make everything an upsert, which sounds like a weird constraint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kylecarbs what were you considering instead? Adding the Cipher
as a field accessible in the enterprise coderd API struct, and calling it at the handler level? Or something else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed with Kyle -- while I agree in principle that this could reduce the overall cognitive overhead, I think this might be better done as a refactor in a follow-up PR instead.
This whole thing feels a little fragile, partially on account of attempting to keep it minimally invasive on the DB structure. In particular, it's not easy to tell that an encryption key is definitely no longer in use. This is very important to security people, especially when you are rotating a key that you think might have been compromised. The Sentinel is a nice idea, but its current powers are limited. It just makes sure that you have the old key before starting to encrypt with a new key, and it only affects things on startup. So, if you have multiple coderds working, and update some but not all of them with a new key, you might not immediately notice problems. The old coderds keep working, and when they encounter data they can't decrypt, they make the user re-authenticate and encrypt with the old key. The new coderds don't notice this at all --- they can decrypt data from the old key. Then, you go and run Instead of a singleton sentinel, consider a table of key identities CREATE TABLE dbcrypt_keys (
number int NOT NULL PRIMARY KEY,
active_key_id text,
revoked_key_id text,
test text
); Active keys have the For encrypted colums, we break them into two columns: a xxxxxxx_key_id column that has a foreign key relationship to To rotate a key, you do a transaction that reads the dbcrypt_keys table, verifies you have all the active keys, and then inserts a new key with the next number. The number is a primary key for the table, so multiple coderds can't insert different successor keys. Then, you migrate all the data off they key you want to revoke, and update the row to NULL |
I was initially avoiding making too many schema changes, but this has a number of nice properties:
It also has some cons:
Overall it definitely seems like a useful and worthwhile change 👍 |
Closing in favour of #9522 |
See #9522 instead.
This commit builds upon the previous work in #7959:
NOTE: This is part 1 of a 2-part PR. This PR focuses mainly on the dbcrypt and database packages. #9433 adds the required plumbing to integrate this into enterprise/coderd properly.
The original working branch
cj/dbcrypt
can be viewed here: #9339