diff --git a/contrib/pg_tde/src/include/pg_tde_event_capture.h b/contrib/pg_tde/src/include/pg_tde_event_capture.h index d1cccf2a38f..7c1e3fdd2a4 100644 --- a/contrib/pg_tde/src/include/pg_tde_event_capture.h +++ b/contrib/pg_tde/src/include/pg_tde_event_capture.h @@ -11,23 +11,14 @@ #include "nodes/parsenodes.h" #include "access/transam.h" -typedef struct TdeCreateEvent +typedef enum { - FullTransactionId tid; /* transaction id of the last event trigger, - * or 0 */ - bool encryptMode; /* true when the table uses encryption */ - Oid baseTableOid; /* Oid of table on which index is being - * created on. For create table statement this - * contains InvalidOid */ - RangeVar *relation; /* Reference to the parsed relation from - * create statement */ - bool alterAccessMethodMode; /* during ALTER ... SET ACCESS METHOD, - * new file permissions shouldn't be - * based on earlier encryption status. */ -} TdeCreateEvent; + TDE_ENCRYPT_MODE_RETAIN = 0, + TDE_ENCRYPT_MODE_ENCRYPT, + TDE_ENCRYPT_MODE_PLAIN, +} TDEEncryptMode; extern void TdeEventCaptureInit(void); -extern TdeCreateEvent *GetCurrentTdeCreateEvent(void); -extern void validateCurrentEventTriggerState(bool mightStartTransaction); +extern TDEEncryptMode currentTdeEncryptModeValidated(void); #endif diff --git a/contrib/pg_tde/src/pg_tde_event_capture.c b/contrib/pg_tde/src/pg_tde_event_capture.c index a75a3f462a7..da349043c8f 100644 --- a/contrib/pg_tde/src/pg_tde_event_capture.c +++ b/contrib/pg_tde/src/pg_tde_event_capture.c @@ -37,20 +37,43 @@ #include "access/tableam.h" #include "catalog/tde_global_space.h" -/* Global variable that gets set at ddl start and cleard out at ddl end*/ -static TdeCreateEvent tdeCurrentCreateEvent = {.tid = {.value = 0}}; +typedef struct +{ + Node *parsetree; + TDEEncryptMode encryptMode; + Oid rebuildSequencesFor; +} TdeDdlEvent; + +static FullTransactionId ddlEventStackTid = {}; +static List *ddlEventStack = NIL; static Oid get_db_oid(const char *name); -static void reset_current_tde_create_event(void); static Oid get_tde_table_am_oid(void); PG_FUNCTION_INFO_V1(pg_tde_ddl_command_start_capture); PG_FUNCTION_INFO_V1(pg_tde_ddl_command_end_capture); -TdeCreateEvent * -GetCurrentTdeCreateEvent(void) +static TDEEncryptMode +currentTdeEncryptMode(void) +{ + if (ddlEventStack == NIL) + return TDE_ENCRYPT_MODE_RETAIN; + else + return ((TdeDdlEvent *) llast(ddlEventStack))->encryptMode; +} + +/* + * Make sure that even if a statement failed, and an event trigger end + * trigger didn't fire, we don't accidentaly create encrypted files when + * we don't have to. + */ +TDEEncryptMode +currentTdeEncryptModeValidated(void) { - return &tdeCurrentCreateEvent; + if (!FullTransactionIdEquals(ddlEventStackTid, GetCurrentFullTransactionIdIfAny())) + return TDE_ENCRYPT_MODE_RETAIN; + + return currentTdeEncryptMode(); } static bool @@ -74,7 +97,7 @@ checkPrincipalKeyConfigured(void) static void checkEncryptionStatus(void) { - if (tdeCurrentCreateEvent.encryptMode) + if (currentTdeEncryptMode() == TDE_ENCRYPT_MODE_ENCRYPT) { checkPrincipalKeyConfigured(); } @@ -85,22 +108,38 @@ checkEncryptionStatus(void) } } -void -validateCurrentEventTriggerState(bool mightStartTransaction) +static void +verify_event_stack(void) { - FullTransactionId tid = mightStartTransaction ? GetCurrentFullTransactionId() : GetCurrentFullTransactionIdIfAny(); + FullTransactionId tid = GetCurrentFullTransactionId(); - if (RecoveryInProgress()) + if (!FullTransactionIdEquals(ddlEventStackTid, tid)) { - reset_current_tde_create_event(); - } - else if (tdeCurrentCreateEvent.tid.value != InvalidFullTransactionId.value && tid.value != tdeCurrentCreateEvent.tid.value) - { - /* There was a failed query, end event trigger didn't execute */ - reset_current_tde_create_event(); + ListCell *lc; + + foreach(lc, ddlEventStack) + pfree(lfirst(lc)); + + ddlEventStack = NIL; + ddlEventStackTid = tid; } } +static void +push_event_stack(const TdeDdlEvent *event) +{ + MemoryContext oldCtx; + TdeDdlEvent *e; + + verify_event_stack(); + + oldCtx = MemoryContextSwitchTo(TopMemoryContext); + e = palloc_object(TdeDdlEvent); + *e = *event; + ddlEventStack = lappend(ddlEventStack, e); + MemoryContextSwitchTo(oldCtx); +} + /* * pg_tde_ddl_command_start_capture is an event trigger function triggered * at the start of any DDL command execution. @@ -131,45 +170,36 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) { IndexStmt *stmt = castNode(IndexStmt, parsetree); Relation rel; - - validateCurrentEventTriggerState(true); - tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); + TdeDdlEvent event = {.parsetree = parsetree}; rel = table_openrv(stmt->relation, AccessShareLock); - tdeCurrentCreateEvent.baseTableOid = rel->rd_id; - if (rel->rd_rel->relam == get_tde_table_am_oid()) { - /* - * We are creating an index on an encrypted table so set the - * global state. - */ - tdeCurrentCreateEvent.encryptMode = true; + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + checkPrincipalKeyConfigured(); } + else + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; /* Hold on to lock until end of transaction */ table_close(rel, NoLock); - if (tdeCurrentCreateEvent.encryptMode) - checkPrincipalKeyConfigured(); + push_event_stack(&event); } else if (IsA(parsetree, CreateStmt)) { CreateStmt *stmt = castNode(CreateStmt, parsetree); bool foundAccessMethod = false; - - validateCurrentEventTriggerState(true); - tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); - + TdeDdlEvent event = {.parsetree = parsetree}; if (stmt->accessMethod) { foundAccessMethod = true; if (strcmp(stmt->accessMethod, "tde_heap") == 0) - { - tdeCurrentCreateEvent.encryptMode = true; - } + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + else + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; } else if (stmt->partbound) { @@ -193,99 +223,90 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS) parentAmOid = get_rel_relam(parentOid); foundAccessMethod = parentAmOid != InvalidOid; - if (foundAccessMethod && parentAmOid == get_tde_table_am_oid()) + if (foundAccessMethod) { - tdeCurrentCreateEvent.encryptMode = true; + if (parentAmOid == get_tde_table_am_oid()) + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + else + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; } } - if (!foundAccessMethod - && strcmp(default_table_access_method, "tde_heap") == 0) + if (!foundAccessMethod) { - tdeCurrentCreateEvent.encryptMode = true; + if (strcmp(default_table_access_method, "tde_heap") == 0) + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + else + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; } + push_event_stack(&event); checkEncryptionStatus(); } else if (IsA(parsetree, CreateTableAsStmt)) { CreateTableAsStmt *stmt = castNode(CreateTableAsStmt, parsetree); - - validateCurrentEventTriggerState(true); - tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); + TdeDdlEvent event = {.parsetree = parsetree}; if (shouldEncryptTable(stmt->into->accessMethod)) - tdeCurrentCreateEvent.encryptMode = true; + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + else + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; + push_event_stack(&event); checkEncryptionStatus(); } else if (IsA(parsetree, AlterTableStmt)) { AlterTableStmt *stmt = castNode(AlterTableStmt, parsetree); ListCell *lcmd; - Oid relationId = RangeVarGetRelid(stmt->relation, AccessShareLock, true); AlterTableCmd *setAccessMethod = NULL; + TdeDdlEvent event = {.parsetree = parsetree}; + Oid relid = RangeVarGetRelid(stmt->relation, AccessShareLock, true); - validateCurrentEventTriggerState(true); - tdeCurrentCreateEvent.tid = GetCurrentFullTransactionId(); - - foreach(lcmd, stmt->cmds) + if (relid != InvalidOid) { - AlterTableCmd *cmd = castNode(AlterTableCmd, lfirst(lcmd)); - - if (cmd->subtype == AT_SetAccessMethod) - setAccessMethod = cmd; - } - - tdeCurrentCreateEvent.baseTableOid = relationId; + foreach(lcmd, stmt->cmds) + { + AlterTableCmd *cmd = castNode(AlterTableCmd, lfirst(lcmd)); - /* - * With a SET ACCESS METHOD clause, use that as the basis for - * decisions. But if it's not present, look up encryption status of - * the table. - */ - if (setAccessMethod) - { - if (shouldEncryptTable(setAccessMethod->name)) - tdeCurrentCreateEvent.encryptMode = true; + if (cmd->subtype == AT_SetAccessMethod) + setAccessMethod = cmd; + } - checkEncryptionStatus(); + /* + * With a SET ACCESS METHOD clause, use that as the basis for + * decisions. But if it's not present, look up encryption status + * of the table. + */ + if (setAccessMethod) + { + event.rebuildSequencesFor = relid; - tdeCurrentCreateEvent.alterAccessMethodMode = true; - } - else - { - if (relationId != InvalidOid) + if (shouldEncryptTable(setAccessMethod->name)) + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + else + event.encryptMode = TDE_ENCRYPT_MODE_PLAIN; + } + else { - Relation rel = relation_open(relationId, NoLock); + Relation rel = relation_open(relid, AccessShareLock); if (rel->rd_rel->relam == get_tde_table_am_oid()) { /* * We are altering an encrypted table ALTER TABLE can - * create possibly new files set the global state + * create possibly new files set the global state. */ - tdeCurrentCreateEvent.encryptMode = true; + event.encryptMode = TDE_ENCRYPT_MODE_ENCRYPT; + checkPrincipalKeyConfigured(); } relation_close(rel, NoLock); - - if (tdeCurrentCreateEvent.encryptMode) - checkPrincipalKeyConfigured(); } - } - } - else - { - if (!tdeCurrentCreateEvent.alterAccessMethodMode) - { - /* - * Any other type of statement doesn't need TDE mode, except - * during alter access method. To make sure that we have no - * leftover setting from a previous error or something, we just - * reset the status here. - */ - reset_current_tde_create_event(); + + push_event_stack(&event); + checkEncryptionStatus(); } } @@ -301,6 +322,7 @@ pg_tde_ddl_command_end_capture(PG_FUNCTION_ARGS) { EventTriggerData *trigdata; Node *parsetree; + TdeDdlEvent *event; /* Ensure this function is being called as an event trigger */ if (!CALLED_AS_EVENT_TRIGGER(fcinfo)) /* internal error */ @@ -310,15 +332,20 @@ pg_tde_ddl_command_end_capture(PG_FUNCTION_ARGS) trigdata = castNode(EventTriggerData, fcinfo->context); parsetree = trigdata->parsetree; - if (IsA(parsetree, AlterTableStmt) && tdeCurrentCreateEvent.alterAccessMethodMode) + if (ddlEventStack == NIL || ((TdeDdlEvent *) llast(ddlEventStack))->parsetree != parsetree) + PG_RETURN_VOID(); + + event = (TdeDdlEvent *) llast(ddlEventStack); + + if (event->rebuildSequencesFor != InvalidOid) { /* * sequences are not updated automatically so force rewrite by * updating their persistence to be the same as before. */ - List *seqlist = getOwnedSequences(tdeCurrentCreateEvent.baseTableOid); + List *seqlist = getOwnedSequences(event->rebuildSequencesFor); ListCell *lc; - Relation rel = relation_open(tdeCurrentCreateEvent.baseTableOid, NoLock); + Relation rel = relation_open(event->rebuildSequencesFor, NoLock); char persistence = rel->rd_rel->relpersistence; relation_close(rel, NoLock); @@ -329,33 +356,14 @@ pg_tde_ddl_command_end_capture(PG_FUNCTION_ARGS) SequenceChangePersistence(seq_relid, persistence); } - - tdeCurrentCreateEvent.alterAccessMethodMode = false; } - /* - * All we need to do is to clear the event state. Except when we are in - * alter access method mode, because during that, we have multiple nested - * event trigger running. Reset should only be called in the end, when it - * is set to false. - */ - if (!tdeCurrentCreateEvent.alterAccessMethodMode) - { - reset_current_tde_create_event(); - } + ddlEventStack = list_delete_last(ddlEventStack); + pfree(event); PG_RETURN_VOID(); } -static void -reset_current_tde_create_event(void) -{ - tdeCurrentCreateEvent.encryptMode = false; - tdeCurrentCreateEvent.baseTableOid = InvalidOid; - tdeCurrentCreateEvent.tid = InvalidFullTransactionId; - tdeCurrentCreateEvent.alterAccessMethodMode = false; -} - static Oid get_tde_table_am_oid(void) { diff --git a/contrib/pg_tde/src/smgr/pg_tde_smgr.c b/contrib/pg_tde/src/smgr/pg_tde_smgr.c index a14a868a215..d863a497c80 100644 --- a/contrib/pg_tde/src/smgr/pg_tde_smgr.c +++ b/contrib/pg_tde/src/smgr/pg_tde_smgr.c @@ -41,40 +41,26 @@ tde_smgr_get_key(const RelFileLocatorBackend *smgr_rlocator) static bool tde_smgr_should_encrypt(const RelFileLocatorBackend *smgr_rlocator, RelFileLocator *old_locator) { - TdeCreateEvent *event; - /* Do not try to encrypt/decrypt catalog tables */ if (IsCatalogRelationOid(smgr_rlocator->locator.relNumber)) return false; - /* - * Make sure that even if a statement failed, and an event trigger end - * trigger didn't fire, we don't accidentaly create encrypted files when - * we don't have to. - */ - validateCurrentEventTriggerState(false); - - event = GetCurrentTdeCreateEvent(); - - /* - * Can be many things, such as: CREATE TABLE ALTER TABLE SET ACCESS METHOD - * ALTER TABLE something else on an encrypted table CREATE INDEX ... - * - * Every file has its own key, that makes logistics easier. - */ - if (event->encryptMode) - return true; - - /* check if we had a key for the old locator, if there's one */ - if (!event->alterAccessMethodMode && old_locator) + switch (currentTdeEncryptModeValidated()) { - RelFileLocatorBackend old_smgr_locator = { - .locator = *old_locator, - .backend = smgr_rlocator->backend, - }; - - if (GetSMGRRelationKey(old_smgr_locator)) + case TDE_ENCRYPT_MODE_PLAIN: + return false; + case TDE_ENCRYPT_MODE_ENCRYPT: return true; + case TDE_ENCRYPT_MODE_RETAIN: + if (old_locator) + { + RelFileLocatorBackend old_smgr_locator = { + .locator = *old_locator, + .backend = smgr_rlocator->backend, + }; + + return GetSMGRRelationKey(old_smgr_locator) != 0; + } } return false;