Verify provider have in-use keys before change

Prevent the change if they do not. In the future we might want to add a
"force" parameter, but for now the command line utility can be used
while the cluster is offline if that is necessary.
pull/230/head
Anders Åstrand 4 months ago committed by AndersAstrand
parent 19b69aa0ba
commit 15ab9fa8a3
  1. 2
      contrib/pg_tde/expected/create_database.out
  2. 22
      contrib/pg_tde/expected/default_principal_key.out
  3. 77
      contrib/pg_tde/expected/key_provider.out
  4. 30
      contrib/pg_tde/sql/key_provider.sql
  5. 12
      contrib/pg_tde/src/catalog/tde_keyring.c
  6. 84
      contrib/pg_tde/src/catalog/tde_principal_key.c
  7. 1
      contrib/pg_tde/src/include/catalog/tde_principal_key.h
  8. 76
      contrib/pg_tde/t/010_change_key_provider.pl
  9. 139
      contrib/pg_tde/t/expected/010_change_key_provider.out

@ -26,7 +26,7 @@ INSERT INTO test_plain (x) VALUES (30), (40);
SELECT pg_tde_add_global_key_provider_file('global-file-vault','/tmp/template_provider_global.per');
pg_tde_add_global_key_provider_file
-------------------------------------
-4
-6
(1 row)
SELECT pg_tde_set_default_key_using_global_key_provider('default-key', 'global-file-vault');

@ -3,7 +3,7 @@ CREATE EXTENSION IF NOT EXISTS pg_buffercache;
SELECT pg_tde_add_global_key_provider_file('file-provider','/tmp/pg_tde_regression_default_key.per');
pg_tde_add_global_key_provider_file
-------------------------------------
-3
-5
(1 row)
-- Should fail: no default principal key for the server yet
@ -30,18 +30,20 @@ SELECT key_provider_id, key_provider_name, key_name
FROM pg_tde_default_key_info();
key_provider_id | key_provider_name | key_name
-----------------+-------------------+-------------
-3 | file-provider | default-key
-5 | file-provider | default-key
(1 row)
-- fails
SELECT pg_tde_delete_global_key_provider('file-provider');
ERROR: Can't delete a provider which is currently in use
SELECT id, provider_name FROM pg_tde_list_all_global_key_providers();
id | provider_name
----+---------------
id | provider_name
----+------------------
-1 | file-keyring
-3 | file-provider
(2 rows)
-3 | global-provider
-4 | global-provider2
-5 | file-provider
(4 rows)
-- Should fail: no principal key for the database yet
SELECT key_provider_id, key_provider_name, key_name
@ -60,7 +62,7 @@ SELECT key_provider_id, key_provider_name, key_name
FROM pg_tde_key_info();
key_provider_id | key_provider_name | key_name
-----------------+-------------------+-------------
-3 | file-provider | default-key
-5 | file-provider | default-key
(1 row)
SELECT current_database() AS regress_database
@ -86,7 +88,7 @@ SELECT key_provider_id, key_provider_name, key_name
FROM pg_tde_key_info();
key_provider_id | key_provider_name | key_name
-----------------+-------------------+-------------
-3 | file-provider | default-key
-5 | file-provider | default-key
(1 row)
\c :regress_database
@ -101,7 +103,7 @@ SELECT key_provider_id, key_provider_name, key_name
FROM pg_tde_key_info();
key_provider_id | key_provider_name | key_name
-----------------+-------------------+-----------------
-3 | file-provider | new-default-key
-5 | file-provider | new-default-key
(1 row)
\c regress_pg_tde_other
@ -109,7 +111,7 @@ SELECT key_provider_id, key_provider_name, key_name
FROM pg_tde_key_info();
key_provider_id | key_provider_name | key_name
-----------------+-------------------+-----------------
-3 | file-provider | new-default-key
-5 | file-provider | new-default-key
(1 row)
SELECT pg_buffercache_evict(bufferid) FROM pg_buffercache WHERE relfilenode = (SELECT relfilenode FROM pg_class WHERE oid = 'test_enc'::regclass);

@ -57,27 +57,12 @@ SELECT * FROM pg_tde_list_all_database_key_providers();
2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"}
(2 rows)
SELECT pg_tde_change_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_other.per');
pg_tde_change_database_key_provider_file
------------------------------------------
1
(1 row)
SELECT * FROM pg_tde_list_all_database_key_providers();
id | provider_name | provider_type | options
----+----------------+---------------+-------------------------------------------------
1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring_other.per"}
2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"}
(2 rows)
SELECT pg_tde_verify_key();
ERROR: failed to retrieve principal key test-db-key from keyring with ID 1
SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per'));
ERROR: unexpected field "foo" for external value "path"
SELECT * FROM pg_tde_list_all_database_key_providers();
id | provider_name | provider_type | options
----+----------------+---------------+-------------------------------------------------
1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring_other.per"}
id | provider_name | provider_type | options
----+----------------+---------------+--------------------------------------------
1 | file-provider | file | {"path" : "/tmp/pg_tde_test_keyring.per"}
2 | file-provider2 | file | {"path" : "/tmp/pg_tde_test_keyring2.per"}
(2 rows)
@ -282,6 +267,62 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"
ERROR: unexpected boolean in field "path"
SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": true}}');
ERROR: unexpected boolean in field "path"
-- Modifying key providers fails if new settings can't fetch existing server key
SELECT pg_tde_add_global_key_provider_file('global-provider', '/tmp/global-provider-file-1');
pg_tde_add_global_key_provider_file
-------------------------------------
-3
(1 row)
SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'global-provider');
pg_tde_set_server_key_using_global_key_provider
-------------------------------------------------
(1 row)
SELECT pg_tde_change_global_key_provider_file('global-provider','/tmp/global-provider-file-2');
ERROR: could not fetch key "server-key" used as server key from modified key provider "global-provider": 0
-- Modifying key providers fails if new settings can't fetch existing database key
SELECT pg_tde_add_global_key_provider_file('global-provider2', '/tmp/global-provider-file-1');
pg_tde_add_global_key_provider_file
-------------------------------------
-4
(1 row)
SELECT current_database() AS regress_database
\gset
CREATE DATABASE db_using_global_provider;
\c db_using_global_provider;
CREATE EXTENSION pg_tde;
SELECT pg_tde_set_key_using_global_key_provider('database-key', 'global-provider2');
pg_tde_set_key_using_global_key_provider
------------------------------------------
(1 row)
\c :regress_database
SELECT pg_tde_change_global_key_provider_file('global-provider2', '/tmp/global-provider-file-2');
ERROR: could not fetch key "database-key" used by database "db_using_global_provider" from modified key provider "global-provider2": 0
DROP DATABASE db_using_global_provider;
CREATE DATABASE db_using_database_provider;
\c db_using_database_provider;
CREATE EXTENSION pg_tde;
SELECT pg_tde_add_database_key_provider_file('db-provider', '/tmp/db-provider-file');
pg_tde_add_database_key_provider_file
---------------------------------------
1
(1 row)
SELECT pg_tde_set_key_using_database_key_provider('database-key', 'db-provider');
pg_tde_set_key_using_database_key_provider
--------------------------------------------
(1 row)
SELECT pg_tde_change_database_key_provider_file('db-provider', '/tmp/db-provider-file-2');
ERROR: could not fetch key "database-key" used by database "db_using_database_provider" from modified key provider "db-provider": 0
\c :regress_database
DROP DATABASE db_using_database_provider;
-- Deleting key providers fails if key name is NULL
SELECT pg_tde_delete_database_key_provider(NULL);
ERROR: provider_name cannot be null

@ -19,10 +19,6 @@ SELECT pg_tde_verify_key();
SELECT pg_tde_change_database_key_provider_file('not-existent-provider','/tmp/pg_tde_test_keyring.per');
SELECT * FROM pg_tde_list_all_database_key_providers();
SELECT pg_tde_change_database_key_provider_file('file-provider','/tmp/pg_tde_test_keyring_other.per');
SELECT * FROM pg_tde_list_all_database_key_providers();
SELECT pg_tde_verify_key();
SELECT pg_tde_change_database_key_provider_file('file-provider', json_object('foo' VALUE '/tmp/pg_tde_test_keyring.per'));
SELECT * FROM pg_tde_list_all_database_key_providers();
@ -135,6 +131,31 @@ SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": tr
SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": true}}');
SELECT pg_tde_change_database_key_provider('file', 'file-provider', '{"path": {"type": "file", "path": true}}');
-- Modifying key providers fails if new settings can't fetch existing server key
SELECT pg_tde_add_global_key_provider_file('global-provider', '/tmp/global-provider-file-1');
SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'global-provider');
SELECT pg_tde_change_global_key_provider_file('global-provider','/tmp/global-provider-file-2');
-- Modifying key providers fails if new settings can't fetch existing database key
SELECT pg_tde_add_global_key_provider_file('global-provider2', '/tmp/global-provider-file-1');
SELECT current_database() AS regress_database
\gset
CREATE DATABASE db_using_global_provider;
\c db_using_global_provider;
CREATE EXTENSION pg_tde;
SELECT pg_tde_set_key_using_global_key_provider('database-key', 'global-provider2');
\c :regress_database
SELECT pg_tde_change_global_key_provider_file('global-provider2', '/tmp/global-provider-file-2');
DROP DATABASE db_using_global_provider;
CREATE DATABASE db_using_database_provider;
\c db_using_database_provider;
CREATE EXTENSION pg_tde;
SELECT pg_tde_add_database_key_provider_file('db-provider', '/tmp/db-provider-file');
SELECT pg_tde_set_key_using_database_key_provider('database-key', 'db-provider');
SELECT pg_tde_change_database_key_provider_file('db-provider', '/tmp/db-provider-file-2');
\c :regress_database
DROP DATABASE db_using_database_provider;
-- Deleting key providers fails if key name is NULL
SELECT pg_tde_delete_database_key_provider(NULL);
SELECT pg_tde_delete_global_key_provider(NULL);
@ -157,4 +178,5 @@ SELECT pg_tde_set_key_using_database_key_provider(repeat('K', 256), 'file-provid
SELECT pg_tde_set_key_using_global_key_provider(repeat('K', 256), 'file-keyring');
SELECT pg_tde_set_server_key_using_global_key_provider(repeat('K', 256), 'file-keyring');
DROP EXTENSION pg_tde;

@ -511,6 +511,18 @@ check_provider_record(KeyringProviderRecord *provider_record)
KeyringValidate(provider);
#ifndef FRONTEND /* We can't scan the pg_database catalog from
* frontend. */
if (provider->keyring_id != 0)
{
/*
* If we are modifying an existing provider, verify that all of the
* keys already in use are the same.
*/
pg_tde_verify_provider_keys_in_use(provider);
}
#endif
pfree(provider);
}

@ -942,6 +942,90 @@ pg_tde_is_provider_used(Oid databaseOid, Oid providerId)
}
}
/*
* Verifies that all keys that are currently in use matches the keys available
* at the provided key provider. This is meant to be used before modifying an
* existing provider to ensure the new settings will provide the same keys as
* those that are already in use.
*/
void
pg_tde_verify_provider_keys_in_use(GenericKeyring *modified_provider)
{
TDEPrincipalKey *existing_principal_key;
HeapTuple tuple;
SysScanDesc scan;
Relation rel;
Assert(modified_provider);
Assert(modified_provider->keyring_id);
LWLockAcquire(tde_lwlock_enc_keys(), LW_EXCLUSIVE);
/* Check the server key that is used for WAL encryption */
existing_principal_key = GetPrincipalKeyNoDefault(GLOBAL_DATA_TDE_OID, LW_EXCLUSIVE);
if (existing_principal_key != NULL &&
existing_principal_key->keyInfo.keyringId == modified_provider->keyring_id)
{
char *key_name = existing_principal_key->keyInfo.name;
KeyringReturnCodes return_code;
KeyInfo *proposed_key;
proposed_key = KeyringGetKey(modified_provider, key_name, &return_code);
if (!proposed_key)
{
ereport(ERROR,
errmsg("could not fetch key \"%s\" used as server key from modified key provider \"%s\": %d",
key_name, modified_provider->provider_name, return_code));
}
if (proposed_key->data.len != existing_principal_key->keyLength ||
memcmp(proposed_key->data.data, existing_principal_key->keyData, proposed_key->data.len) != 0)
{
ereport(ERROR,
errmsg("key \"%s\" from modified key provider \"%s\" does not match existing server key",
key_name, modified_provider->provider_name));
}
}
/* Check all databases for usage of keys from this key provider. */
rel = table_open(DatabaseRelationId, AccessShareLock);
scan = systable_beginscan(rel, 0, false, NULL, 0, NULL);
while (HeapTupleIsValid(tuple = systable_getnext(scan)))
{
Form_pg_database database = (Form_pg_database) GETSTRUCT(tuple);
existing_principal_key = GetPrincipalKeyNoDefault(database->oid, LW_EXCLUSIVE);
if (existing_principal_key != NULL &&
existing_principal_key->keyInfo.keyringId == modified_provider->keyring_id)
{
char *key_name = existing_principal_key->keyInfo.name;
KeyringReturnCodes return_code;
KeyInfo *proposed_key;
proposed_key = KeyringGetKey(modified_provider, key_name, &return_code);
if (!proposed_key)
{
ereport(ERROR,
errmsg("could not fetch key \"%s\" used by database \"%s\" from modified key provider \"%s\": %d",
key_name, database->datname.data, modified_provider->provider_name, return_code));
}
if (proposed_key->data.len != existing_principal_key->keyLength ||
memcmp(proposed_key->data.data, existing_principal_key->keyData, proposed_key->data.len) != 0)
{
ereport(ERROR,
errmsg("key \"%s\" from modified key provider \"%s\" does not match existing key used by database \"%s\"",
key_name, modified_provider->provider_name, database->datname.data));
}
}
}
systable_endscan(scan);
table_close(rel, AccessShareLock);
LWLockRelease(tde_lwlock_enc_keys());
}
static bool
pg_tde_is_same_principal_key(TDEPrincipalKey *a, TDEPrincipalKey *b)
{

@ -55,5 +55,6 @@ extern TDEPrincipalKey *GetPrincipalKey(Oid dbOid, void *lockMode);
extern void xl_tde_perform_rotate_key(XLogPrincipalKeyRotate *xlrec);
extern bool pg_tde_is_provider_used(Oid databaseOid, Oid providerId);
extern void pg_tde_verify_provider_keys_in_use(GenericKeyring *proposed_provider);
#endif /* PG_TDE_PRINCIPAL_KEY_H */

@ -62,82 +62,6 @@ PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();");
PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');");
PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;');
# Change provider and do not move file
PGTDE::psql($node, 'postgres',
"SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');"
);
PGTDE::psql($node, 'postgres',
"SELECT pg_tde_list_all_database_key_providers();");
PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();");
PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');");
PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;');
PGTDE::append_to_result_file("-- server restart");
$node->restart;
# Verify
PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();");
PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');");
PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;');
PGTDE::append_to_result_file(
"-- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_3.per");
move('/tmp/change_key_provider_2.per', '/tmp/change_key_provider_3.per');
PGTDE::append_to_result_file("-- server restart");
$node->restart;
# Verify
PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();");
PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');");
PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;');
PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde CASCADE;');
PGTDE::psql($node, 'postgres', 'CREATE EXTENSION IF NOT EXISTS pg_tde;');
# Change provider and generate a new principal key
PGTDE::psql($node, 'postgres',
"SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');"
);
PGTDE::psql($node, 'postgres',
"SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');"
);
PGTDE::psql($node, 'postgres',
'CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;'
);
PGTDE::psql($node, 'postgres', 'INSERT INTO test_enc (k) VALUES (5), (6);');
PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();");
PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');");
PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;');
PGTDE::psql($node, 'postgres',
"SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');"
);
PGTDE::append_to_result_file("-- server restart");
$node->restart;
# Verify
PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();");
PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');");
PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;');
PGTDE::psql($node, 'postgres',
'CREATE TABLE test_enc2 (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;'
);
PGTDE::psql($node, 'postgres',
"SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');"
);
# Verify
PGTDE::psql($node, 'postgres', "SELECT pg_tde_verify_key();");
PGTDE::psql($node, 'postgres', "SELECT pg_tde_is_encrypted('test_enc');");
PGTDE::psql($node, 'postgres', 'SELECT * FROM test_enc ORDER BY id;');
PGTDE::psql($node, 'postgres', 'DROP EXTENSION pg_tde CASCADE;');
$node->stop;

@ -90,144 +90,5 @@ SELECT * FROM test_enc ORDER BY id;
2 | 6
(2 rows)
SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');
pg_tde_change_database_key_provider_file
------------------------------------------
1
(1 row)
SELECT pg_tde_list_all_database_key_providers();
pg_tde_list_all_database_key_providers
-----------------------------------------------------------------------
(1,file-vault,file,"{""path"" : ""/tmp/change_key_provider_3.per""}")
(1 row)
SELECT pg_tde_verify_key();
psql:<stdin>:1: ERROR: failed to retrieve principal key test-key from keyring with ID 1
SELECT pg_tde_is_encrypted('test_enc');
pg_tde_is_encrypted
---------------------
t
(1 row)
SELECT * FROM test_enc ORDER BY id;
id | k
----+---
1 | 5
2 | 6
(2 rows)
-- server restart
SELECT pg_tde_verify_key();
psql:<stdin>:1: ERROR: failed to retrieve principal key test-key from keyring with ID 1
SELECT pg_tde_is_encrypted('test_enc');
pg_tde_is_encrypted
---------------------
t
(1 row)
SELECT * FROM test_enc ORDER BY id;
psql:<stdin>:1: ERROR: failed to retrieve principal key test-key from keyring with ID 1
-- mv /tmp/change_key_provider_2.per /tmp/change_key_provider_3.per
-- server restart
SELECT pg_tde_verify_key();
pg_tde_verify_key
-------------------
(1 row)
SELECT pg_tde_is_encrypted('test_enc');
pg_tde_is_encrypted
---------------------
t
(1 row)
SELECT * FROM test_enc ORDER BY id;
id | k
----+---
1 | 5
2 | 6
(2 rows)
DROP EXTENSION pg_tde CASCADE;
psql:<stdin>:1: NOTICE: drop cascades to table test_enc
CREATE EXTENSION IF NOT EXISTS pg_tde;
SELECT pg_tde_add_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');
pg_tde_add_database_key_provider_file
---------------------------------------
1
(1 row)
SELECT pg_tde_set_key_using_database_key_provider('test-key', 'file-vault');
pg_tde_set_key_using_database_key_provider
--------------------------------------------
(1 row)
CREATE TABLE test_enc (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;
INSERT INTO test_enc (k) VALUES (5), (6);
SELECT pg_tde_verify_key();
pg_tde_verify_key
-------------------
(1 row)
SELECT pg_tde_is_encrypted('test_enc');
pg_tde_is_encrypted
---------------------
t
(1 row)
SELECT * FROM test_enc ORDER BY id;
id | k
----+---
1 | 5
2 | 6
(2 rows)
SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_3.per');
pg_tde_change_database_key_provider_file
------------------------------------------
1
(1 row)
-- server restart
SELECT pg_tde_verify_key();
psql:<stdin>:1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file
SELECT pg_tde_is_encrypted('test_enc');
pg_tde_is_encrypted
---------------------
t
(1 row)
SELECT * FROM test_enc ORDER BY id;
psql:<stdin>:1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file
CREATE TABLE test_enc2 (id serial, k integer, PRIMARY KEY (id)) USING tde_heap;
psql:<stdin>:1: ERROR: Failed to verify principal key header for key test-key, incorrect principal key or corrupted key file
SELECT pg_tde_change_database_key_provider_file('file-vault', '/tmp/change_key_provider_4.per');
pg_tde_change_database_key_provider_file
------------------------------------------
1
(1 row)
SELECT pg_tde_verify_key();
pg_tde_verify_key
-------------------
(1 row)
SELECT pg_tde_is_encrypted('test_enc');
pg_tde_is_encrypted
---------------------
t
(1 row)
SELECT * FROM test_enc ORDER BY id;
id | k
----+---
1 | 5
2 | 6
(2 rows)
DROP EXTENSION pg_tde CASCADE;
psql:<stdin>:1: NOTICE: drop cascades to table test_enc

Loading…
Cancel
Save