Skip to content

Commit 3f7806a

Browse files
committed
PG-1447 Assert there are no encrypted relations when using FILE_COPY
The FILE_COPY strategy of CREATE DATABASE does not use the SMGR so it cannot properly copy and re-encrypt encrypted relations. So we check for such relations and error out if any are found. We look for encrypted relations simply by counting the number of keys in the key map file of the database. This simple approach is fine even with the risk of a left-over key from a crash or a bug due to the FILE_COPY method being rare and that we therefore want to keep this code minimal.
1 parent c7f0a81 commit 3f7806a

File tree

7 files changed

+237
-8
lines changed

7 files changed

+237
-8
lines changed

contrib/pg_tde/expected/create_database.out

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ SELECT pg_tde_is_encrypted('test_plain_id_seq');
9292
(1 row)
9393

9494
\c :regress_database
95+
CREATE DATABASE new_db_file_copy TEMPLATE template_db STRATEGY FILE_COPY;
96+
ERROR: The FILE_COPY strategy cannot be used when there are encrypted objects in the template database: 3 objects found
97+
HINT: Use the WAL_LOG strategy instead.
98+
\c template_db
99+
DROP TABLE test_enc;
100+
\c :regress_database
101+
CREATE DATABASE new_db_file_copy TEMPLATE template_db STRATEGY FILE_COPY;
102+
DROP DATABASE new_db_file_copy;
95103
DROP DATABASE new_db;
96104
DROP DATABASE template_db;
97105
DROP EXTENSION pg_tde;

contrib/pg_tde/sql/create_database.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,17 @@ SELECT pg_tde_is_encrypted('test_plain_id_seq');
4545

4646
\c :regress_database
4747

48+
CREATE DATABASE new_db_file_copy TEMPLATE template_db STRATEGY FILE_COPY;
49+
50+
\c template_db
51+
52+
DROP TABLE test_enc;
53+
54+
\c :regress_database
55+
56+
CREATE DATABASE new_db_file_copy TEMPLATE template_db STRATEGY FILE_COPY;
57+
58+
DROP DATABASE new_db_file_copy;
4859
DROP DATABASE new_db;
4960
DROP DATABASE template_db;
5061

contrib/pg_tde/src/access/pg_tde_tdemap.c

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ static int pg_tde_open_file_basic(const char *tde_filename, int fileFlags, bool
115115
static void pg_tde_file_header_read(const char *tde_filename, int fd, TDEFileHeader *fheader, off_t *bytes_read);
116116
static bool pg_tde_read_one_map_entry(int fd, TDEMapEntry *map_entry, off_t *offset);
117117
static void pg_tde_read_one_map_entry2(int keydata_fd, int32 key_index, TDEMapEntry *map_entry, Oid databaseId);
118-
static int pg_tde_open_file_read(const char *tde_filename, off_t *curr_pos);
118+
static int pg_tde_open_file_read(const char *tde_filename, bool ignore_missing, off_t *curr_pos);
119119
static InternalKey *pg_tde_get_key_from_cache(const RelFileLocator *rlocator, uint32 key_type);
120120
static WALKeyCacheRec *pg_tde_add_wal_key_to_cache(InternalKey *cached_key, XLogRecPtr start_lsn);
121121
static InternalKey *pg_tde_put_key_into_cache(const RelFileLocator *locator, InternalKey *key);
@@ -599,7 +599,7 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p
599599

600600
pg_tde_set_db_file_path(principal_key->keyInfo.databaseId, old_path);
601601

602-
old_fd = pg_tde_open_file_read(old_path, &old_curr_pos);
602+
old_fd = pg_tde_open_file_read(old_path, false, &old_curr_pos);
603603
new_fd = keyrotation_init_file(&new_signed_key_info, new_path, old_path, &new_curr_pos);
604604

605605
/* Read all entries until EOF */
@@ -831,7 +831,7 @@ pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *db_
831831

832832
Assert(rlocator != NULL);
833833

834-
map_fd = pg_tde_open_file_read(db_map_path, &curr_pos);
834+
map_fd = pg_tde_open_file_read(db_map_path, false, &curr_pos);
835835

836836
while (pg_tde_read_one_map_entry(map_fd, map_entry, &curr_pos))
837837
{
@@ -847,6 +847,47 @@ pg_tde_find_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *db_
847847
return found;
848848
}
849849

850+
/*
851+
* Counts number of encrypted objects in a database.
852+
*
853+
* Does not check if objects actually exist but just that they have keys in
854+
* the map file. For the only current caller, checking if we can use
855+
* FILE_COPY, this is good enough but for other workloads where a false
856+
* positive is more harmful this might not be.
857+
*
858+
* Works even if the database has no map file.
859+
*/
860+
int
861+
pg_tde_count_relations(Oid dbOid)
862+
{
863+
char db_map_path[MAXPGPATH];
864+
LWLock *lock_pk = tde_lwlock_enc_keys();
865+
File map_fd;
866+
off_t curr_pos = 0;
867+
TDEMapEntry map_entry;
868+
int count = 0;
869+
870+
pg_tde_set_db_file_path(dbOid, db_map_path);
871+
872+
LWLockAcquire(lock_pk, LW_SHARED);
873+
874+
map_fd = pg_tde_open_file_read(db_map_path, true, &curr_pos);
875+
if (map_fd < 0)
876+
return count;
877+
878+
while (pg_tde_read_one_map_entry(map_fd, &map_entry, &curr_pos))
879+
{
880+
if (map_entry.flags & TDE_KEY_TYPE_SMGR)
881+
count++;
882+
}
883+
884+
close(map_fd);
885+
886+
LWLockRelease(lock_pk);
887+
888+
return count;
889+
}
890+
850891
bool
851892
pg_tde_verify_principal_key_info(TDESignedPrincipalKeyInfo *signed_key_info, const TDEPrincipalKey *principal_key)
852893
{
@@ -883,15 +924,17 @@ tde_decrypt_rel_key(TDEPrincipalKey *principal_key, TDEMapEntry *map_entry)
883924
* is raised.
884925
*/
885926
static int
886-
pg_tde_open_file_read(const char *tde_filename, off_t *curr_pos)
927+
pg_tde_open_file_read(const char *tde_filename, bool ignore_missing, off_t *curr_pos)
887928
{
888929
int fd;
889930
TDEFileHeader fheader;
890931
off_t bytes_read = 0;
891932

892933
Assert(LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_SHARED) || LWLockHeldByMeInMode(tde_lwlock_enc_keys(), LW_EXCLUSIVE));
893934

894-
fd = pg_tde_open_file_basic(tde_filename, O_RDONLY | PG_BINARY, false);
935+
fd = pg_tde_open_file_basic(tde_filename, O_RDONLY | PG_BINARY, ignore_missing);
936+
if (ignore_missing && fd < 0)
937+
return fd;
895938

896939
pg_tde_file_header_read(tde_filename, fd, &fheader, &bytes_read);
897940
*curr_pos = bytes_read;
@@ -1144,7 +1187,7 @@ pg_tde_read_last_wal_key(void)
11441187
}
11451188
pg_tde_set_db_file_path(rlocator.dbOid, db_map_path);
11461189

1147-
fd = pg_tde_open_file_read(db_map_path, &read_pos);
1190+
fd = pg_tde_open_file_read(db_map_path, false, &read_pos);
11481191
fsize = lseek(fd, 0, SEEK_END);
11491192
/* No keys */
11501193
if (fsize == TDE_FILE_HEADER_SIZE)
@@ -1187,7 +1230,7 @@ pg_tde_fetch_wal_keys(XLogRecPtr start_lsn)
11871230

11881231
pg_tde_set_db_file_path(rlocator.dbOid, db_map_path);
11891232

1190-
fd = pg_tde_open_file_read(db_map_path, &read_pos);
1233+
fd = pg_tde_open_file_read(db_map_path, false, &read_pos);
11911234

11921235
keys_count = (lseek(fd, 0, SEEK_END) - TDE_FILE_HEADER_SIZE) / MAP_ENTRY_SIZE;
11931236

contrib/pg_tde/src/include/access/pg_tde_tdemap.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ pg_tde_set_db_file_path(Oid dbOid, char *path)
111111
}
112112

113113
extern InternalKey *GetSMGRRelationKey(RelFileLocatorBackend rel);
114+
extern int pg_tde_count_relations(Oid dbOid);
114115

115116
extern void pg_tde_delete_tde_files(Oid dbOid);
116117

contrib/pg_tde/src/include/pg_tde_event_capture.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ typedef struct TdeCreateEvent
2626
* based on earlier encryption status. */
2727
} TdeCreateEvent;
2828

29+
extern void TdeEventCaptureInit(void);
2930
extern TdeCreateEvent *GetCurrentTdeCreateEvent(void);
30-
3131
extern void validateCurrentEventTriggerState(bool mightStartTransaction);
3232

3333
#endif

contrib/pg_tde/src/pg_tde.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "utils/builtins.h"
3434
#include "smgr/pg_tde_smgr.h"
3535
#include "catalog/tde_global_space.h"
36+
#include "pg_tde_event_capture.h"
3637
#include "utils/percona.h"
3738
#include "pg_tde_guc.h"
3839
#include "access/tableam.h"
@@ -111,6 +112,7 @@ _PG_init(void)
111112
check_percona_api_version();
112113

113114
TdeGucInit();
115+
TdeEventCaptureInit();
114116

115117
InitializePrincipalKeyInfo();
116118
InitializeKeyProviderInfo();

contrib/pg_tde/src/pg_tde_event_capture.c

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "utils/builtins.h"
1717
#include "utils/lsyscache.h"
1818
#include "catalog/pg_class.h"
19+
#include "catalog/pg_database.h"
1920
#include "commands/defrem.h"
2021
#include "commands/sequence.h"
2122
#include "access/table.h"
@@ -24,8 +25,13 @@
2425
#include "catalog/namespace.h"
2526
#include "commands/event_trigger.h"
2627
#include "common/pg_tde_utils.h"
28+
#include "storage/lmgr.h"
29+
#include "tcop/utility.h"
30+
#include "utils/fmgroids.h"
31+
#include "utils/syscache.h"
2732
#include "pg_tde_event_capture.h"
2833
#include "pg_tde_guc.h"
34+
#include "access/pg_tde_tdemap.h"
2935
#include "catalog/tde_principal_key.h"
3036
#include "miscadmin.h"
3137
#include "access/tableam.h"
@@ -34,6 +40,7 @@
3440
/* Global variable that gets set at ddl start and cleard out at ddl end*/
3541
static TdeCreateEvent tdeCurrentCreateEvent = {.tid = {.value = 0}};
3642

43+
static Oid get_db_oid(const char *name);
3744
static void reset_current_tde_create_event(void);
3845
static Oid get_tde_table_am_oid(void);
3946

@@ -354,3 +361,160 @@ get_tde_table_am_oid(void)
354361
{
355362
return get_table_am_oid("tde_heap", false);
356363
}
364+
365+
static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
366+
367+
/*
368+
* Handles utility commands which we cannot handle in the event trigger.
369+
*/
370+
static void
371+
pg_tde_proccess_utility(PlannedStmt *pstmt,
372+
const char *queryString,
373+
bool readOnlyTree,
374+
ProcessUtilityContext context,
375+
ParamListInfo params,
376+
QueryEnvironment *queryEnv,
377+
DestReceiver *dest,
378+
QueryCompletion *qc)
379+
{
380+
Node *parsetree = pstmt->utilityStmt;
381+
382+
switch (nodeTag(parsetree))
383+
{
384+
case T_CreatedbStmt:
385+
{
386+
CreatedbStmt *stmt = castNode(CreatedbStmt, parsetree);
387+
ListCell *option;
388+
char *dbtemplate = "template1";
389+
char *strategy = "wal_log";
390+
391+
foreach(option, stmt->options)
392+
{
393+
DefElem *defel = (DefElem *) lfirst(option);
394+
395+
if (strcmp(defel->defname, "template") == 0)
396+
dbtemplate = defGetString(defel);
397+
else if (strcmp(defel->defname, "strategy") == 0)
398+
strategy = defGetString(defel);
399+
}
400+
401+
if (pg_strcasecmp(strategy, "file_copy") == 0)
402+
{
403+
Oid dbOid = get_db_oid(dbtemplate);
404+
405+
if (dbOid != InvalidOid)
406+
{
407+
int count = pg_tde_count_relations(dbOid);
408+
409+
if (count > 0)
410+
ereport(ERROR,
411+
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
412+
errmsg("The FILE_COPY strategy cannot be used when there are encrypted objects in the template database: %d objects found", count),
413+
errhint("Use the WAL_LOG strategy instead."));
414+
}
415+
}
416+
}
417+
break;
418+
default:
419+
break;
420+
}
421+
422+
if (next_ProcessUtility_hook)
423+
(*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
424+
context, params, queryEnv,
425+
dest, qc);
426+
else
427+
standard_ProcessUtility(pstmt, queryString, readOnlyTree,
428+
context, params, queryEnv,
429+
dest, qc);
430+
}
431+
432+
void
433+
TdeEventCaptureInit(void)
434+
{
435+
next_ProcessUtility_hook = ProcessUtility_hook;
436+
ProcessUtility_hook = pg_tde_proccess_utility;
437+
}
438+
439+
/*
440+
* A stripped down version of get_db_info() from src/backend/commands/dbcommands.c
441+
*/
442+
static Oid
443+
get_db_oid(const char *name)
444+
{
445+
Oid resDbOid = InvalidOid;
446+
Relation relation;
447+
448+
Assert(name);
449+
450+
relation = table_open(DatabaseRelationId, AccessShareLock);
451+
452+
/*
453+
* Loop covers the rare case where the database is renamed before we can
454+
* lock it. We try again just in case we can find a new one of the same
455+
* name.
456+
*/
457+
for (;;)
458+
{
459+
ScanKeyData scanKey;
460+
SysScanDesc scan;
461+
HeapTuple tuple;
462+
Oid dbOid;
463+
464+
/*
465+
* there's no syscache for database-indexed-by-name, so must do it the
466+
* hard way
467+
*/
468+
ScanKeyInit(&scanKey,
469+
Anum_pg_database_datname,
470+
BTEqualStrategyNumber, F_NAMEEQ,
471+
CStringGetDatum(name));
472+
473+
scan = systable_beginscan(relation, DatabaseNameIndexId, true,
474+
NULL, 1, &scanKey);
475+
476+
tuple = systable_getnext(scan);
477+
478+
if (!HeapTupleIsValid(tuple))
479+
{
480+
/* definitely no database of that name */
481+
systable_endscan(scan);
482+
break;
483+
}
484+
485+
dbOid = ((Form_pg_database) GETSTRUCT(tuple))->oid;
486+
487+
systable_endscan(scan);
488+
489+
/*
490+
* Now that we have a database OID, we can try to lock the DB.
491+
*/
492+
LockSharedObject(DatabaseRelationId, dbOid, 0, AccessExclusiveLock);
493+
494+
/*
495+
* And now, re-fetch the tuple by OID. If it's still there and still
496+
* the same name, we win; else, drop the lock and loop back to try
497+
* again.
498+
*/
499+
tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(dbOid));
500+
if (HeapTupleIsValid(tuple))
501+
{
502+
Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple);
503+
504+
if (strcmp(name, NameStr(dbform->datname)) == 0)
505+
{
506+
resDbOid = dbOid;
507+
ReleaseSysCache(tuple);
508+
break;
509+
}
510+
/* can only get here if it was just renamed */
511+
ReleaseSysCache(tuple);
512+
}
513+
514+
UnlockSharedObject(DatabaseRelationId, dbOid, 0, AccessExclusiveLock);
515+
}
516+
517+
table_close(relation, AccessShareLock);
518+
519+
return resDbOid;
520+
}

0 commit comments

Comments
 (0)