diff --git a/pg_tde--1.0.sql b/pg_tde--1.0.sql index 727fac5a..70169b10 100644 --- a/pg_tde--1.0.sql +++ b/pg_tde--1.0.sql @@ -194,6 +194,11 @@ RETURNS boolean AS 'MODULE_PATHNAME' LANGUAGE C; +CREATE FUNCTION pg_tde_alter_principal_key_keyring(new_provider_name VARCHAR(255)) +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C; + CREATE FUNCTION pg_tde_extension_initialize() RETURNS VOID AS 'MODULE_PATHNAME' @@ -342,6 +347,7 @@ BEGIN PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_vault_v2', 'varchar, JSON, JSON,JSON,JSON'); PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_set_principal_key', 'varchar, varchar, BOOLEAN'); + PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_alter_principal_key_keyring', 'varchar'); PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_rotate_principal_key', 'pg_tde_global, varchar, varchar'); PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_rotate_principal_key', 'varchar, varchar'); @@ -410,6 +416,7 @@ BEGIN PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_vault_v2', 'varchar, JSON, JSON,JSON,JSON'); PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_set_principal_key', 'varchar, varchar, BOOLEAN'); + PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_alter_principal_key_keyring', 'varchar'); PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_rotate_principal_key', 'pg_tde_global, varchar, varchar'); PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_rotate_principal_key', 'varchar, varchar'); diff --git a/src/access/pg_tde_tdemap.c b/src/access/pg_tde_tdemap.c index 6809d23b..e6711f7c 100644 --- a/src/access/pg_tde_tdemap.c +++ b/src/access/pg_tde_tdemap.c @@ -115,7 +115,7 @@ static int pg_tde_open_file_basic(char *tde_filename, int fileFlags, bool ignore static int pg_tde_file_header_read(char *tde_filename, int fd, TDEFileHeader *fheader, bool *is_new_file, off_t *bytes_read); static bool pg_tde_read_one_map_entry(int fd, const RelFileLocator *rlocator, int flags, TDEMapEntry *map_entry, off_t *offset); static RelKeyData *pg_tde_read_one_keydata(int keydata_fd, int32 key_index, TDEPrincipalKey *principal_key); -static int pg_tde_open_file(char *tde_filename, TDEPrincipalKeyInfo *principal_key_info, bool should_fill_info, int fileFlags, bool *is_new_file, off_t *offset); +static int pg_tde_open_file(char *tde_filename, TDEPrincipalKeyInfo *principal_key_info, bool update_header, int fileFlags, bool *is_new_file, off_t *curr_pos); static RelKeyData *pg_tde_get_key_from_cache(RelFileNumber rel_number, uint32 key_type); #ifndef FRONTEND @@ -279,7 +279,7 @@ pg_tde_delete_tde_files(Oid dbOid) * The caller must have an EXCLUSIVE LOCK on the files before calling this function. */ bool -pg_tde_save_principal_key(TDEPrincipalKeyInfo *principal_key_info) +pg_tde_save_principal_key(TDEPrincipalKeyInfo *principal_key_info, bool truncate_existing, bool update_header) { int map_fd = -1; int keydata_fd = -1; @@ -288,16 +288,23 @@ pg_tde_save_principal_key(TDEPrincipalKeyInfo *principal_key_info) bool is_new_key_data = false; char db_map_path[MAXPGPATH] = {0}; char db_keydata_path[MAXPGPATH] = {0}; + int file_flags = O_RDWR | O_CREAT; /* Set the file paths */ pg_tde_set_db_file_paths(principal_key_info->databaseId, db_map_path, db_keydata_path); - ereport(LOG, (errmsg("pg_tde_save_principal_key"))); + ereport(DEBUG2, + (errmsg("pg_tde_save_principal_key"), + errdetail("truncate_existing:%s update_header:%s", truncate_existing?"YES":"NO", update_header?"YES":"NO"))); + /* + * Create or truncate these map and keydata files. + */ + if (truncate_existing) + file_flags |= O_TRUNC; - /* Create or truncate these map and keydata files. */ - map_fd = pg_tde_open_file(db_map_path, principal_key_info, false, O_RDWR | O_CREAT | O_TRUNC, &is_new_map, &curr_pos); - keydata_fd = pg_tde_open_file(db_keydata_path, principal_key_info, false, O_RDWR | O_CREAT | O_TRUNC, &is_new_key_data, &curr_pos); + map_fd = pg_tde_open_file(db_map_path, principal_key_info, update_header, file_flags, &is_new_map, &curr_pos); + keydata_fd = pg_tde_open_file(db_keydata_path, principal_key_info, update_header, file_flags, &is_new_key_data, &curr_pos); /* Closing files. */ close(map_fd); @@ -341,6 +348,8 @@ pg_tde_file_header_write(char *tde_filename, int fd, TDEPrincipalKeyInfo *princi (errcode_for_file_access(), errmsg("could not fsync file \"%s\": %m", tde_filename))); } + ereport(DEBUG2, + (errmsg("Wrote the header to %s", tde_filename))); return fd; } @@ -1124,7 +1133,7 @@ tde_decrypt_rel_key(TDEPrincipalKey *principal_key, RelKeyData *enc_rel_key_data * or an error is thrown if the file does not exist. */ static int -pg_tde_open_file(char *tde_filename, TDEPrincipalKeyInfo *principal_key_info, bool should_fill_info, int fileFlags, bool *is_new_file, off_t *curr_pos) +pg_tde_open_file(char *tde_filename, TDEPrincipalKeyInfo *principal_key_info, bool update_header, int fileFlags, bool *is_new_file, off_t *curr_pos) { int fd = -1; TDEFileHeader fheader; @@ -1141,7 +1150,7 @@ pg_tde_open_file(char *tde_filename, TDEPrincipalKeyInfo *principal_key_info, bo #ifndef FRONTEND /* In case it's a new file, let's add the header now. */ - if (*is_new_file && principal_key_info) + if ((*is_new_file || update_header) && principal_key_info) pg_tde_file_header_write(tde_filename, fd, principal_key_info, &bytes_written); #endif /* FRONTEND */ diff --git a/src/access/pg_tde_xlog.c b/src/access/pg_tde_xlog.c index f7583da0..0654d8fb 100644 --- a/src/access/pg_tde_xlog.c +++ b/src/access/pg_tde_xlog.c @@ -46,12 +46,16 @@ tdeheap_rmgr_redo(XLogReaderState *record) pg_tde_write_key_map_entry(&xlrec->rlocator, &xlrec->relKey, pk); LWLockRelease(tde_lwlock_enc_keys()); } - else if (info == XLOG_TDE_ADD_PRINCIPAL_KEY) + else if (info == XLOG_TDE_ADD_PRINCIPAL_KEY || info == XLOG_TDE_UPDATE_PRINCIPAL_KEY) { TDEPrincipalKeyInfo *mkey = (TDEPrincipalKeyInfo *) XLogRecGetData(record); LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE); - save_principal_key_info(mkey); + if (info == XLOG_TDE_ADD_PRINCIPAL_KEY) + save_principal_key_info(mkey); + else + update_principal_key_info(mkey); + LWLockRelease(tde_lwlock_enc_keys()); } else if (info == XLOG_TDE_EXTENSION_INSTALL_KEY) @@ -109,6 +113,12 @@ tdeheap_rmgr_desc(StringInfo buf, XLogReaderState *record) appendStringInfo(buf, "add tde principal key for db %u", xlrec->databaseId); } + if (info == XLOG_TDE_UPDATE_PRINCIPAL_KEY) + { + TDEPrincipalKeyInfo *xlrec = (TDEPrincipalKeyInfo *) XLogRecGetData(record); + + appendStringInfo(buf, "Alter key provider to:%d for tde principal key for db %u", xlrec->keyringId, xlrec->databaseId); + } if (info == XLOG_TDE_EXTENSION_INSTALL_KEY) { XLogExtensionInstall *xlrec = (XLogExtensionInstall *) XLogRecGetData(record); @@ -138,6 +148,9 @@ tdeheap_rmgr_identify(uint8 info) if ((info & ~XLR_INFO_MASK) == XLOG_TDE_ADD_PRINCIPAL_KEY) return "XLOG_TDE_ADD_PRINCIPAL_KEY"; + if ((info & ~XLR_INFO_MASK) == XLOG_TDE_UPDATE_PRINCIPAL_KEY) + return "XLOG_TDE_UPDATE_PRINCIPAL_KEY"; + if ((info & ~XLR_INFO_MASK) == XLOG_TDE_EXTENSION_INSTALL_KEY) return "XLOG_TDE_EXTENSION_INSTALL_KEY"; diff --git a/src/catalog/tde_principal_key.c b/src/catalog/tde_principal_key.c index 8d805382..53b55f5e 100644 --- a/src/catalog/tde_principal_key.c +++ b/src/catalog/tde_principal_key.c @@ -86,6 +86,7 @@ static TDEPrincipalKey *set_principal_key_with_keyring(const char *key_name, GenericKeyring *keyring, Oid dbOid, bool ensure_new_key); +static TDEPrincipalKey *alter_keyprovider_for_principal_key(GenericKeyring *newKeyring,Oid dbOid); static const TDEShmemSetupRoutine principal_key_info_shmem_routine = { .init_shared_state = initialize_shared_state, @@ -213,7 +214,14 @@ save_principal_key_info(TDEPrincipalKeyInfo *principal_key_info) { Assert(principal_key_info != NULL); - return pg_tde_save_principal_key(principal_key_info); + return pg_tde_save_principal_key(principal_key_info, true, true); +} + +bool +update_principal_key_info(TDEPrincipalKeyInfo *principal_key_info) +{ + Assert(principal_key_info != NULL); + return pg_tde_save_principal_key(principal_key_info, false, true); } /* @@ -296,6 +304,60 @@ set_principal_key_with_keyring(const char *key_name, GenericKeyring *keyring, return principalKey; } +/* + * alter_keyprovider_for_principal_key: + */ +TDEPrincipalKey * +alter_keyprovider_for_principal_key(GenericKeyring *newKeyring, Oid dbOid) +{ + TDEPrincipalKeyInfo *principalKeyInfo = NULL; + TDEPrincipalKey *principal_key = NULL; + + LWLock *lock_files = tde_lwlock_enc_keys(); + + Assert(newKeyring != NULL); + LWLockAcquire(lock_files, LW_EXCLUSIVE); + + principalKeyInfo = pg_tde_get_principal_key_info(dbOid); + + if (principalKeyInfo == NULL) + { + LWLockRelease(lock_files); + ereport(ERROR, + (errmsg("Principal key not set for the database"), + errhint("Use set_principal_key interface to set the principal key"))); + } + + if (newKeyring->key_id == principalKeyInfo->keyringId) + { + LWLockRelease(lock_files); + ereport(ERROR, + (errmsg("New key provider is same as the current key provider"))); + } + /* update the key provider in principal key info */ + + ereport(DEBUG2, + (errmsg("Changing keyprovider ID from :%d to %d", principalKeyInfo->keyringId, newKeyring->key_id))); + + principalKeyInfo->keyringId = newKeyring->key_id; + + update_principal_key_info(principalKeyInfo); + + /* XLog the new key*/ + XLogBeginInsert(); + XLogRegisterData((char *)principalKeyInfo, sizeof(TDEPrincipalKeyInfo)); + XLogInsert(RM_TDERMGR_ID, XLOG_TDE_UPDATE_PRINCIPAL_KEY); + + /* clear the cache as well */ + clear_principal_key_cache(dbOid); + + principal_key = GetPrincipalKey(dbOid, LW_EXCLUSIVE); + + LWLockRelease(lock_files); + + return principal_key; +} + bool SetPrincipalKey(const char *key_name, const char *provider_name, bool ensure_new_key) { @@ -307,6 +369,15 @@ SetPrincipalKey(const char *key_name, const char *provider_name, bool ensure_new return (principal_key != NULL); } +bool +AlterPrincipalKeyKeyring(const char *provider_name) +{ + TDEPrincipalKey *principal_key = alter_keyprovider_for_principal_key(GetKeyProviderByName(provider_name, MyDatabaseId), + MyDatabaseId); + + return (principal_key != NULL); +} + bool RotatePrincipalKey(TDEPrincipalKey *current_key, const char *new_key_name, const char *new_provider_name, bool ensure_new_key) { @@ -629,6 +700,19 @@ pg_tde_set_principal_key(PG_FUNCTION_ARGS) PG_RETURN_BOOL(ret); } +PG_FUNCTION_INFO_V1(pg_tde_alter_principal_key_keyring); +Datum pg_tde_alter_principal_key_keyring(PG_FUNCTION_ARGS); + +Datum pg_tde_alter_principal_key_keyring(PG_FUNCTION_ARGS) +{ + char *provider_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + bool ret; + + ereport(LOG, (errmsg("Altering principal key provider to \"%s\" for the database", provider_name))); + ret = AlterPrincipalKeyKeyring(provider_name); + PG_RETURN_BOOL(ret); +} + /* * SQL interface for key rotation */ diff --git a/src/include/access/pg_tde_tdemap.h b/src/include/access/pg_tde_tdemap.h index 9ab39578..88185c0f 100644 --- a/src/include/access/pg_tde_tdemap.h +++ b/src/include/access/pg_tde_tdemap.h @@ -65,7 +65,7 @@ extern RelKeyData *GetTdeGlobaleRelationKey(RelFileLocator rel); extern void pg_tde_delete_tde_files(Oid dbOid); extern TDEPrincipalKeyInfo *pg_tde_get_principal_key_info(Oid dbOid); -extern bool pg_tde_save_principal_key(TDEPrincipalKeyInfo *principal_key_info); +extern bool pg_tde_save_principal_key(TDEPrincipalKeyInfo *principal_key_info, bool truncate_existing, bool update_header); extern bool pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_principal_key); extern bool pg_tde_write_map_keydata_files(off_t map_size, char *m_file_data, off_t keydata_size, char *k_file_data); extern RelKeyData *tde_create_rel_key(RelFileNumber rel_num, InternalKey *key, TDEPrincipalKeyInfo *principal_key_info); diff --git a/src/include/access/pg_tde_xlog.h b/src/include/access/pg_tde_xlog.h index 48ae4c80..c064e017 100644 --- a/src/include/access/pg_tde_xlog.h +++ b/src/include/access/pg_tde_xlog.h @@ -22,6 +22,7 @@ #define XLOG_TDE_ROTATE_KEY 0x30 #define XLOG_TDE_ADD_KEY_PROVIDER_KEY 0x40 #define XLOG_TDE_FREE_MAP_ENTRY 0x50 +#define XLOG_TDE_UPDATE_PRINCIPAL_KEY 0x60 /* ID 140 is registered for Percona TDE extension: https://wiki.postgresql.org/wiki/CustomWALResourceManagers */ #define RM_TDERMGR_ID 140 diff --git a/src/include/catalog/tde_principal_key.h b/src/include/catalog/tde_principal_key.h index 5ba90646..bc8239d6 100644 --- a/src/include/catalog/tde_principal_key.h +++ b/src/include/catalog/tde_principal_key.h @@ -67,9 +67,11 @@ extern TDEPrincipalKey *GetPrincipalKey(Oid dbOid, void *lockMode); #endif extern bool save_principal_key_info(TDEPrincipalKeyInfo *principalKeyInfo); +extern bool update_principal_key_info(TDEPrincipalKeyInfo *principal_key_info); extern Oid GetPrincipalKeyProviderId(void); extern bool SetPrincipalKey(const char *key_name, const char *provider_name, bool ensure_new_key); +extern bool AlterPrincipalKeyKeyring(const char *provider_name); extern bool RotatePrincipalKey(TDEPrincipalKey *current_key, const char *new_key_name, const char *new_provider_name, bool ensure_new_key); extern bool xl_tde_perform_rotate_key(XLogPrincipalKeyRotate *xlrec); diff --git a/t/010_alter_keyring.pl b/t/010_alter_keyring.pl new file mode 100644 index 00000000..a6e8ea55 --- /dev/null +++ b/t/010_alter_keyring.pl @@ -0,0 +1,107 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use File::Basename; +use File::Compare; +use File::Copy; +use Test::More; +use lib 't'; +use pgtde; + +# Get file name and CREATE out file name and dirs WHERE requried +PGTDE::setup_files_dir(basename($0)); + +# CREATE new PostgreSQL node and do initdb +my $node = PGTDE->pgtde_init_pg(); +my $pgdata = $node->data_dir; + +# UPDATE postgresql.conf to include/load pg_tde library +open my $conf, '>>', "$pgdata/postgresql.conf"; +print $conf "shared_preload_libraries = 'pg_tde'\n"; +close $conf; + +# Start server +my $rt_value = $node->start; +ok($rt_value == 1, "Start Server"); + +# CREATE EXTENSION and change out file permissions +my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION pg_tde;', extra_params => ['-a']); +ok($cmdret == 0, "CREATE PGTDE EXTENSION"); +PGTDE::append_to_file($stdout); + +# Restart the server +PGTDE::append_to_file("-- server restart"); +$node->stop(); + +$rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); + +$rt_value = $node->psql('postgres', "SELECT pg_tde_add_key_provider_file('file-vault-1','/tmp/pg_tde_keyring_1.per');", extra_params => ['-a']); +$rt_value = $node->psql('postgres', "SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault-1');", extra_params => ['-a']); + +$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap_basic;', extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +$stdout = $node->safe_psql('postgres', 'INSERT INTO test_enc (k) VALUES (5),(6);', extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +#rename the keyring file to make it inaccessible +PGTDE::append_to_file("--moving keyring file--"); +move("/tmp/pg_tde_keyring_1.per", "/tmp/pg_tde_keyring_2.per") + or die "move failed: $!"; + +#restart the server and now the table should become inaccessible +PGTDE::append_to_file("-- server restart"); +$node->stop(); + +$rt_value = $node->start(); +ok($rt_value == 1, "Restart Server"); + +# this should fail +($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT * FROM test_enc ORDER BY id ASC;", extra_params => ['-a']); +PGTDE::append_to_file($stderr); + +#$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); +#PGTDE::append_to_file($stdout); + + +#create a new key provider pointing to the moved keyring file +PGTDE::append_to_file("-- creating new key provider pointing to the moved file --"); +$rt_value = $node->psql('postgres', "SELECT pg_tde_add_key_provider_file('file-vault-2','/tmp/pg_tde_keyring_2.per');", extra_params => ['-a']); +#update principal key to use the new key provider +PGTDE::append_to_file("-- Alter principal key to use new provider --"); +$rt_value = $node->psql('postgres', "SELECT pg_tde_alter_principal_key_keyring('file-vault-2');", extra_params => ['-a']); + +# this should work now +$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); +PGTDE::append_to_file($stdout); + + +# Restart the server +PGTDE::append_to_file("-- server restart"); +$rt_value = $node->stop(); +$rt_value = $node->start(); + +$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_enc ORDER BY id ASC;', extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +$stdout = $node->safe_psql('postgres', 'DROP TABLE test_enc;', extra_params => ['-a']); +PGTDE::append_to_file($stdout); + +# DROP EXTENSION +$stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde CASCADE;', extra_params => ['-a']); +# Stop the server +$node->stop(); + +# compare the expected and out file +my $compare = PGTDE->compare_results(); + +# Test/check if expected and result/out file match. If Yes, test passes. +is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files."); + +# Done testing for this testcase file. +done_testing(); diff --git a/t/expected/010_alter_keyring.out b/t/expected/010_alter_keyring.out new file mode 100644 index 00000000..0f6c37e0 --- /dev/null +++ b/t/expected/010_alter_keyring.out @@ -0,0 +1,20 @@ +CREATE EXTENSION pg_tde; +-- server restart +CREATE TABLE test_enc(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap_basic; +INSERT INTO test_enc (k) VALUES (5),(6); +SELECT * FROM test_enc ORDER BY id ASC; +1|5 +2|6 +--moving keyring file-- +-- server restart +psql::1: ERROR: failed to retrieve principal key. Create one using pg_tde_set_principal_key before using encrypted tables. +-- creating new key provider pointing to the moved file -- +-- Alter principal key to use new provider -- +SELECT * FROM test_enc ORDER BY id ASC; +1|5 +2|6 +-- server restart +SELECT * FROM test_enc ORDER BY id ASC; +1|5 +2|6 +DROP TABLE test_enc;