diff --git a/Makefile b/Makefile index c973530..f27fa8d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # Makefile MODULES = ddl_guard EXTENSION = ddl_guard -DATA = ddl_guard--1.0.1.sql +DATA = ddl_guard--1.0.1.sql ddl_guard--1.0.2.sql ddl_guard--1.0.1--1.0.2.sql INPUTDIR ?= test/regular TESTS = $(wildcard $(INPUTDIR)/sql/*.sql) diff --git a/README.md b/README.md index ab4fc95..1dd30ad 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,12 @@ CREATE EXTENSION ddl_guard; ALTER SYSTEM SET ddl_guard.enabled = on; ``` +As of 1.0.2, DDL enforcement runs in the ProcessUtility hook and no event trigger is created. If you're upgrading from earlier versions, run: + +```sql +ALTER EXTENSION ddl_guard UPDATE TO '1.0.2'; +``` + Note: `ddl_guard` creates the `ddl_guard` schema; ensure it does not already exist before running `CREATE EXTENSION`. Now, only superusers can run DDL commands. diff --git a/ddl_guard--1.0.1--1.0.2.sql b/ddl_guard--1.0.1--1.0.2.sql new file mode 100644 index 0000000..add4b59 --- /dev/null +++ b/ddl_guard--1.0.1--1.0.2.sql @@ -0,0 +1,2 @@ +-- ddl_guard--1.0.1--1.0.2.sql +DROP EVENT TRIGGER IF EXISTS ddl_guard_trigger; diff --git a/ddl_guard--1.0.2.sql b/ddl_guard--1.0.2.sql new file mode 100644 index 0000000..11b94af --- /dev/null +++ b/ddl_guard--1.0.2.sql @@ -0,0 +1,15 @@ +-- ddl_guard--1.0.2.sql +CREATE SCHEMA ddl_guard; +CREATE TABLE ddl_guard.sentinel_log ( + logged_at timestamptz NOT NULL DEFAULT now(), + operation text NOT NULL +); +GRANT USAGE ON SCHEMA ddl_guard TO PUBLIC; +GRANT SELECT ON ddl_guard.sentinel_log TO PUBLIC; + +CREATE OR REPLACE FUNCTION ddl_guard_check() + RETURNS event_trigger + SECURITY INVOKER + SET search_path = 'pg_catalog, pg_temp' + LANGUAGE C + AS 'MODULE_PATHNAME', 'ddl_guard_check'; diff --git a/ddl_guard.c b/ddl_guard.c index 2e97efb..31fcb1c 100644 --- a/ddl_guard.c +++ b/ddl_guard.c @@ -27,7 +27,7 @@ PG_MODULE_MAGIC; void _PG_init(void); void _PG_fini(void); -void log_sentinel_event(const char *volatile operation); +bool log_sentinel_event(const char *volatile operation); Datum ddl_guard_check(PG_FUNCTION_ARGS); static void lob_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, int subId, void *arg); static bool set_lobject_funcs(void); @@ -43,6 +43,7 @@ static void ddl_guard_process_utility(PlannedStmt *pstmt, const char *queryStrin QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *qc); #endif static bool is_guarded_dcl_statement(Node *parsetree, CommandTag *commandTag); +static bool is_guarded_ddl_statement(Node *parsetree, CommandTag *commandTag); static bool ddl_guard_enabled = false; static bool ddl_guard_ddl_sentinel = false; @@ -177,6 +178,22 @@ lookup_lobject_log_name(Oid funcid) return NULL; } +static bool +is_guarded_ddl_statement(Node *parsetree, CommandTag *commandTag) +{ + CommandTag tag; + + if (parsetree == NULL) + return false; + + tag = CreateCommandTag(parsetree); + if (!command_tag_event_trigger_ok(tag)) + return false; + if (commandTag != NULL) + *commandTag = tag; + return true; +} + static bool is_guarded_dcl_statement(Node *parsetree, CommandTag *commandTag) { @@ -197,7 +214,7 @@ is_guarded_dcl_statement(Node *parsetree, CommandTag *commandTag) case T_GrantStmt: case T_AlterDefaultPrivilegesStmt: tag = CreateCommandTag(parsetree); - /* Event-triggered commands are handled by ddl_guard_check. */ + /* Event-triggered commands are handled by the ProcessUtility hook. */ if (command_tag_event_trigger_ok(tag)) return false; if (commandTag != NULL) @@ -243,7 +260,7 @@ get_sentinel_log_owner(void) return relowner; } -void +bool log_sentinel_event(const char *volatile operation) { int ret; @@ -258,9 +275,7 @@ log_sentinel_event(const char *volatile operation) log_owner = get_sentinel_log_owner(); if (!OidIsValid(log_owner)) - ereport(ERROR, - (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("ddl_guard.sentinel_log not found"))); + return false; GetUserIdAndSecContext(&saved_userid, &saved_sec_context); new_sec_context = saved_sec_context | SECURITY_LOCAL_USERID_CHANGE; @@ -308,6 +323,7 @@ log_sentinel_event(const char *volatile operation) PG_END_TRY(); SetUserIdAndSecContext(saved_userid, saved_sec_context); + return true; } static void @@ -336,8 +352,10 @@ lob_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, int s log_name = lookup_lobject_log_name(objectId); if (log_name != NULL) { - log_sentinel_event(log_name); - ereport(WARNING, errmsg("lo_guard: lobject \"%s\" function call, sentinel entry written", log_name)); + if (log_sentinel_event(log_name)) + ereport(WARNING, errmsg("lo_guard: lobject \"%s\" function call, sentinel entry written", log_name)); + else + ereport(WARNING, errmsg("lo_guard: lobject \"%s\" function call, sentinel log unavailable", log_name)); } break; default: @@ -361,13 +379,34 @@ ddl_guard_process_utility(PlannedStmt *pstmt, const char *queryString, #endif { CommandTag commandTag; + bool guard_context; - if (ddl_guard_enabled && is_guarded_dcl_statement(pstmt->utilityStmt, &commandTag)) + /* Skip internal subcommands to avoid duplicate sentinel entries. */ + guard_context = (context != PROCESS_UTILITY_SUBCOMMAND); + + if (guard_context && ddl_guard_enabled && is_guarded_ddl_statement(pstmt->utilityStmt, &commandTag)) + { + if (ddl_guard_ddl_sentinel) + { + if (log_sentinel_event(GetCommandTagName(commandTag))) + ereport(WARNING, (errmsg("ddl_guard: ddl detected, sentinel entry written"))); + else + ereport(WARNING, (errmsg("ddl_guard: ddl detected, sentinel log unavailable"))); + } + else if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("Non-superusers are not allowed to execute DDL statements"), + errhint("ddl_guard.enabled is set."))); + } + else if (guard_context && ddl_guard_enabled && is_guarded_dcl_statement(pstmt->utilityStmt, &commandTag)) { if (ddl_guard_dcl_sentinel) { - log_sentinel_event(GetCommandTagName(commandTag)); - ereport(WARNING, (errmsg("ddl_guard: dcl detected, sentinel entry written"))); + if (log_sentinel_event(GetCommandTagName(commandTag))) + ereport(WARNING, (errmsg("ddl_guard: dcl detected, sentinel entry written"))); + else + ereport(WARNING, (errmsg("ddl_guard: dcl detected, sentinel log unavailable"))); } else if (!superuser()) ereport(ERROR, @@ -406,23 +445,6 @@ ddl_guard_check(PG_FUNCTION_ARGS) (errcode(ERRCODE_INTERNAL_ERROR), errmsg("ddl_guard_check: not fired by event trigger manager"))); - if (ddl_guard_enabled) - { - if (ddl_guard_ddl_sentinel) - { - EventTriggerData *trigdata = (EventTriggerData *) fcinfo->context; - - log_sentinel_event(GetCommandTagName(trigdata->tag)); - ereport(WARNING, (errmsg("ddl_guard: ddl detected, sentinel entry written"))); - PG_RETURN_VOID(); - } - if (!superuser()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("Non-superusers are not allowed to execute DDL statements"), - errhint("ddl_guard.enabled is set."))); - } - PG_RETURN_VOID(); } diff --git a/ddl_guard.control b/ddl_guard.control index fbc3ea6..7c1472b 100644 --- a/ddl_guard.control +++ b/ddl_guard.control @@ -1,5 +1,5 @@ # ddl_guard.control -default_version = '1.0.1' +default_version = '1.0.2' comment = 'Prevents DDL execution by non-superusers when a specific GUC is set' relocatable = false module_pathname = '$libdir/ddl_guard' diff --git a/test/regular/expected/sentinel.out b/test/regular/expected/sentinel.out index 5ccc985..7597066 100644 --- a/test/regular/expected/sentinel.out +++ b/test/regular/expected/sentinel.out @@ -1,10 +1,4 @@ -SELECT count(*) AS before_count FROM ddl_guard.sentinel_log; - before_count --------------- - 149 -(1 row) - -\gset +SELECT count(*) AS before_count FROM ddl_guard.sentinel_log \gset CREATE TABLE ddl_guard_sentinel_test (id int); WARNING: ddl_guard: ddl detected, sentinel entry written INSERT INTO ddl_guard.sentinel_log (operation) VALUES ('spoof'); diff --git a/test/regular/sql/sentinel.sql b/test/regular/sql/sentinel.sql index 55c580a..7238bd4 100644 --- a/test/regular/sql/sentinel.sql +++ b/test/regular/sql/sentinel.sql @@ -1,5 +1,4 @@ -SELECT count(*) AS before_count FROM ddl_guard.sentinel_log; -\gset +SELECT count(*) AS before_count FROM ddl_guard.sentinel_log \gset CREATE TABLE ddl_guard_sentinel_test (id int); INSERT INTO ddl_guard.sentinel_log (operation) VALUES ('spoof'); SELECT count(*) >= :before_count::int + 1 AS logged_after_create FROM ddl_guard.sentinel_log;