-
Notifications
You must be signed in to change notification settings - Fork 19
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
Keyrotation and key name versioning implementation #144
Conversation
xlog functionality. This patch includes some minor refactoring as well as changes the return type of the set key function which now returns a boolean.
versioned_name is added. Co-authored-by: Muhammad Usama <[email protected]>
feature complete and dev-tested. As part of this commit, XLog for key rotation is implemented and verified that during streaming replication, when master key is rotated, the key map and the data file on primary and standby are binary equivalent when the Xlog record is replayed on the standby.
src/catalog/tde_master_key.c
Outdated
while (true) | ||
{ | ||
keyInfo = KeyringGetKey(keyring, new_master_key.keyInfo.keyId.versioned_name, false, &keyring_ret); | ||
if (keyInfo == NULL || ensure_new_key == false) |
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.
ensure_new_key
is always true
, do we need || ensure_new_key == false
here?
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.
We will add a SQL interface ( A new argument to set key function) after this PR gets in. So for now it is set to always true.
src/catalog/tde_master_key.c
Outdated
while (true) | ||
{ | ||
keyInfo = KeyringGetKey(keyring, new_master_key.keyInfo.keyId.versioned_name, false, &keyring_ret); | ||
if (keyInfo == NULL || ensure_new_key == false) |
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.
The code in L422-447 looks like it is generalized to be shared with set_master_key_with_keyring()
. But the code is duplicated in both funcions and it is much more complicated than it has to be. For example here in addition to redundant check || ensure_new_key == false
we also don't need L440 check if (keyInfo == NULL)
as this is the only way to reach the code after the loop.
In set_master_key_with_keyring()
the loop is redundant by it self.
As for me either code in these function should be simplified or this common code has to be moved to the separate function and reused. Otherwise it is really confusing.
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.
BTW, should we always generate a new key during rotation? Can't a user set a key with the "new name" which is already exists in a keyring and we use it? And generate a new key only if a provided new name doesn't exists or the name is the same as the name of the current key (is such a case we generate a new version of the key). I'm just asking because I'm not sure what is the supposed logic in that regard.
Can we have a comment w/ these rules?
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.
We should have functionality to use an existing key, for example when multiple databases use the same master key. One rotation should generate a new version, the others just should use the latest.
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.
Basically, the loop appears redundant because of the missing SQL interface. The idea is that if users want to use the existing key ( for whatever reason), they call the set key function with mastrer-key name (using ensure_new_key=false) that already exists in the key provider. Secondly, if the user intends to use the latest version of the key when the key name already exists in the key provider, then they can do so by passing ensure_new_key=true.
This PR just adds the infrastructure for enabling the above.
*/ | ||
bool | ||
pg_tde_perform_rotate_key(const char *new_master_key_name) | ||
pg_tde_perform_rotate_key(TDEMasterKey *master_key, TDEMasterKey *new_master_key) |
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.
Do we need locks in this function? Or should the caller have locks?
I imagine it can be call by different processes which can rewrite/unlink files at the same time.
src/catalog/tde_master_key.c
Outdated
|
||
masterKey->keyInfo.keyId.version++; | ||
|
||
snprintf(masterKey->keyInfo.keyId.versioned_name, TDE_KEY_NAME_LEN, "%s_%d", key_name, masterKey->keyInfo.keyId.version); |
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.
Setting this variable here seems out of place - shouldn't it be part of keyring internals?
src/catalog/tde_master_key.c
Outdated
while (true) | ||
{ | ||
keyInfo = KeyringGetKey(keyring, masterKey->keyInfo.keyId.versioned_name, false, &keyring_ret); | ||
if (keyInfo == NULL || ensure_new_key == false) |
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.
This condition seems strange to me - if ensure_new_key is false we always return the first version of the key, not the last?
src/catalog/tde_master_key.c
Outdated
while (true) | ||
{ | ||
keyInfo = KeyringGetKey(keyring, new_master_key.keyInfo.keyId.versioned_name, false, &keyring_ret); | ||
if (keyInfo == NULL || ensure_new_key == false) |
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.
We should have functionality to use an existing key, for example when multiple databases use the same master key. One rotation should generate a new version, the others just should use the latest.
src/catalog/tde_master_key.c
Outdated
/* We need to get the key from keyring */ | ||
while (true) | ||
{ | ||
keyInfo = KeyringGetKey(keyring, masterKey->keyInfo.keyId.versioned_name, false, &keyring_ret); |
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 see now why you introduced versioned_name, but this is a hack and not a good solution. Instead of this, KeyringGetKey should receive keyInfo as a parameter, and deal with the name construction internally.
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.
IMHO the key version is the function of master_key, not the keyring. And keyring should only be concerned about key-name and if that key-name contains the version number appended to it so be it. Also managing the version as part of master key naming allows the keyring to get-set master key without constructing the key name every time and that is good for performance as well.
pg_tde--1.0.sql
Outdated
@@ -81,13 +81,13 @@ RETURNS boolean | |||
AS $$ SELECT amname = 'pg_tde' FROM pg_class INNER JOIN pg_am ON pg_am.oid = pg_class.relam WHERE relname = table_name $$ | |||
LANGUAGE SQL; | |||
|
|||
CREATE FUNCTION pg_tde_rotate_key(key_name VARCHAR) | |||
CREATE FUNCTION pg_tde_rotate_key(new_master_key_name VARCHAR(255), new_provider_name VARCHAR(255)) |
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.
99% of the time, users just want to rotate the key, and that's it. We should have an overload without any arguments.
We also need a function that just updates the file to the latest already existing key, instead of creating a new one.
The commit contains the following changes. Moved the logic for getting the latest available master key name to a separate function. Changed the base name for the master key. Now it always contains the version number postfix. Added a SQL interface to allow users to specify if they need to ensure the key during set and rotate master key operations. Also making a few adjustments in file keyring file handling.
vault-v2 returns 404 (KEYRING_CODE_RESOURCE_NOT_AVAILABLE) when key is not found so it should not be treated as an error
Commit also adds a key rotation tap test case
As agreed during the weekly meeting, the discussion points are now pretty much resolved by the latest commits from @codeforall and @EngineeredVirus, barring the locking issue, which will come as a separate PR for the whole structure, not just key rotation. |
The initial version of the key rotation functionality without the xlog functionality.
This patch includes some minor refactoring as well as changes to the return type of the set key function, which now returns a boolean.
Master key versioning implemented by Usama. A new field of versioned_name is added.
Co-authored-by: Muhammad Usama [email protected]