العربيةworlber.sa
Flag of Saudi ArabiaSaudi company · Riyadh

Security

PGEE maintenance-window permissions: schema changes only when approved

CAB tickets don't revoke GRANTs. How PGEE time-related maintenance permissions stop mid-day ALTER TABLE, and why event-trigger DIY falls short.

6 Nov 2026 · 8 min read

PGEE maintenance-window permissions: schema changes only when approved

Your change-advisory board approved the alter for Sunday 02:00. The ticket is green. At 14:17 on a trading day someone with a standing DBA role runs ALTER TABLE … ADD COLUMN on production anyway, because a release is stuck and "it's a tiny change." The lock waits pile up. Or worse: the change lands cleanly, nobody notices until the audit sample asks for the window that never opened.

The process worked. The database didn't enforce it. Community PostgreSQL will happily accept a GRANT that lasts forever and an ALTER that has no concept of "maintenance window." CYBERTEC PostgreSQL Enterprise Edition (PGEE) lists a feature for exactly this gap: time-related / rule-based maintenance permissions. They associate permissions with maintenance windows so data-structure changes are prevented outside the window and allowed inside it. Below: the standing-privilege problem, the community DIY teams try first (and why it is incomplete), what the public PGEE docs actually say, and how to pair the feature with audit and declarative permission diffs.

The standing privilege is the real risk

In a regulated shop the CAB already exists. The gap is enforcement at the object layer:

  • A GRANT CREATE, ALTER (or a role that effectively has them via membership) is permanent until someone remembers REVOKE. Tickets close. Privileges do not.

  • Application-release pressure turns "wait for the window" into "just this once." The person who can ship is often the person who can DDL.

  • Audit samples look for evidence that schema changes happened inside an approved window. A ServiceNow record that says Sunday, and a database catalog that says Thursday 14:17, is a finding, even when nobody meant harm.

Least privilege for DML is a solved conversation in most banks. Least privilege for DDL, *and* least privilege that varies with the clock, is not. That is the ICP for this feature: DBA leads and platform/security architects who already have a CAB and still see mid-day schema changes.

What community PostgreSQL lets you build (and what it misses)

PostgreSQL event triggers are the usual DIY. The official documentation even ships a table_rewrite example that encodes a maintenance window with current_time:

CREATE OR REPLACE FUNCTION no_rewrite()
RETURNS event_trigger
LANGUAGE plpgsql AS
$$
DECLARE
table_oid oid := pg_event_trigger_table_rewrite_oid();
current_hour integer := extract('hour' from current_time);
pages integer;
max_pages integer := 100;
BEGIN
IF pg_event_trigger_table_rewrite_oid() = 'public.foo'::regclass
THEN
RAISE EXCEPTION 'you''re not allowed to rewrite the table %',
table_oid::regclass;
END IF;

SELECT INTO pages relpages FROM pg_class WHERE oid = table_oid;
IF pages > max_pages
THEN
RAISE EXCEPTION 'rewrites only allowed for table with less than % pages',
max_pages;
END IF;

IF current_hour NOT BETWEEN 1 AND 6
THEN
RAISE EXCEPTION 'rewrites only allowed between 1am and 6am';
END IF;
END;
$$;

CREATE EVENT TRIGGER no_rewrite_allowed
ON table_rewrite
EXECUTE FUNCTION no_rewrite();

That example is real and useful as a teaching tool. It is also incomplete as a production change-control mechanism. Read the same docs carefully:

  1. table_rewrite is narrow. It fires just before a table is rewritten by some actions of ALTER TABLE and ALTER TYPE. The documentation states explicitly that CLUSTER and VACUUM do not fire table_rewrite, even though they can rewrite a table.

  2. ddl_command_start is broader, still not total. It fires just before CREATE, ALTER, DROP, COMMENT, GRANT, IMPORT FOREIGN SCHEMA, REINDEX, REFRESH MATERIALIZED VIEW, REVOKE, and SECURITY LABEL (and SELECT INTO). It does not fire for DDL on shared objects: databases, roles (definitions and memberships), tablespaces, parameter privileges, ALTER SYSTEM. It also does not fire for commands that target event triggers themselves.

  3. A superuser can remove the guard. DROP EVENT TRIGGER no_rewrite_allowed; ends the policy. If your emergency break-glass role is also your daily DBA login, the window is optional.

  4. current_time follows the session TimeZone. Two sessions with different TimeZone settings see different "hours." Clock sync across app servers, jump hosts, and the database host is ordinary ops hygiene; treating session-local time as a security boundary is not.

  5. Who owns the trigger function matters. A SECURITY DEFINER function running as a powerful owner is a classic trap if the search path or the exception path is wrong. A SECURITY INVOKER function that the DBA role can replace is a different trap.

You can combine ddl_command_start with a WHEN TAG IN (...) filter and a central allow-list table. Many teams do. What you still do not get from community PostgreSQL is a first-class notion that *permissions themselves* are only valid inside a window. You get a procedural gate you maintain, that privileged roles can dismantle, that misses shared-object DDL by design.

What PGEE's public docs actually say

CYBERTEC's feature comparison lists "Time related permissions" as: Community PostgreSQL: not available natively. PGEE: included.

The Enterprise PostgreSQL Security PDF (v1.1, 2025-04-11), in the section "RULE BASED MAINTENANCE PERMISSIONS," states the product intent in prose:

> PGEE allows you to associate permissions with maintenance windows. You can prevent changes to your data structure during certain periods while allowing them during a maintenance window.

That is the claim we rely on. Read the limits as carefully as the promise:

  • Product feature. It ties permissions to windows, aimed at data-structure change control. Our working inference, labeled as such, is that "data structure" here means schema/DDL-class changes (tables, indexes, and similar), which is the failure mode in the CAB story above. Confirm the exact object scope for your PGEE version during evaluation.

  • No published public SQL API in the materials we cite. This article deliberately invents neither GRANT … DURING WINDOW syntax, nor ServiceNow auto-open hooks, nor an SLA around window switching. If your evaluation build exposes a specific window-definition interface, use that. Do not assume one from a blog post.

  • No compliance certification. Associating permissions with windows helps you *implement* the change-control and least-privilege practices auditors ask about. On its own it does not "satisfy" any named framework.

The same security materials frame an accompanying capability under integrated / declarative permission management: see all permissions, define a desired state, calculate desired versus real, and fix automatically. In the open-source world that companion is CYBERTEC's pg_permissions extension (permission_target, permission_diffs(), views whose granted column can be updated to drive GRANT/REVOKE). PGEE's product framing of "integrated security auditing" sits next to that idea; be precise about which SQL you are running. The open-source extension's API is public. Treat PGEE's packaging of the broader story as product scope to confirm in evaluation, not as a second invented dialect.

Why the product feature exists if DIY "works"

Because the failure mode is social and permanent. Syntax is not the hard part.

A DIY event trigger answers "block this command right now if the hour is wrong." A standing GRANT answers "this role may change the schema any day of the week." Those are different questions. The CAB process tries to answer the second with tickets. The database answers the second with privileges. PGEE's stated feature is about making the privilege itself time-scoped for data-structure changes. The mid-day ALTER TABLE fails because the permission is not in force. That is a different control from a trigger function you wrote that remembers to check extract(hour …).

That matters when:

  • The person under release pressure is a superuser, who can drop your DIY trigger.

  • The change is not a table rewrite (so table_rewrite never fires).

  • The change is GRANT/REVOKE on an object, or a DROP INDEX, or something your WHEN TAG list forgot.

  • Last quarter's DIY trigger was disabled "temporarily" during a migration and never re-enabled.

None of that is a knock on event triggers. Use them. Know their holes. Do not mistake them for time-related permissions.

Pair the window with audit and with desired-state diffs

A blocked attempt that nobody can see is half a control.

  • Audit. Object-level and DDL-aware auditing is how you show that outside-window attempts happened, who made them, and whether break-glass was used. See our earlier post on tamper-resistant audit logging with PGEE.

  • Declarative diffs. After the window closes, permission_diffs()-style desired-versus-real checks catch privileges that were raised for the change and never lowered. A maintenance window that leaves CREATE hanging on a human DBA role is how next Thursday's incident starts.

CREATE EXTENSION pg_permissions SCHEMA public;

-- Desired state: app role may DML on appschema tables, not DDL.
INSERT INTO public.permission_target
(role_name, permissions, object_type, schema_name)
VALUES
('appuser', '{SELECT,INSERT,UPDATE,DELETE}', 'TABLE', 'appschema');

SELECT * FROM public.permission_diffs();

The snippet above is the open-source pg_permissions API. Use it as the reviewable desired-state layer. Do not read it as the PGEE window-definition API.

A staging checklist you can run this week

  1. List every role that can run DDL on production schemas today (has_schema_privilege, role membership, and any BYPASSRLS / superuser accounts). Assume anything that can DDL on Tuesday afternoon will eventually do so.

  2. Reproduce the official table_rewrite example in a non-production cluster. Confirm it blocks a rewriting ALTER TABLE outside hours 1-6, then confirm a CLUSTER still proceeds. That is the documented hole.

  3. Add a ddl_command_start trigger with a tight WHEN TAG IN (...) list. Attempt CREATE ROLE / ALTER SYSTEM and record what is *not* blocked.

  4. As a break-glass exercise, drop the event trigger with a superuser and note how much of your "window" disappeared. Decide whether that role should exist as a daily login.

  5. During PGEE evaluation, ask specifically: how are maintenance windows defined in *your* build, which object classes they cover, how break-glass works, whether superusers are bound by the window, and how window transitions appear in the audit trail. Take the PDF prose as intent; take the evaluation cluster as ground truth.

Talk to Worlber

Worlber is the authorised CYBERTEC reseller in Saudi Arabia. We deploy and support PGEE from Riyadh, including change-control design around maintenance-window permissions, audit configuration, and declarative permission reviews with pg_permissions.

Sources

  • CYBERTEC, Enterprise PostgreSQL Security PDF v1.1 (2025-04-11), "RULE BASED MAINTENANCE PERMISSIONS": https://www.cybertec-postgresql.com/wp-content/uploads/2025/02/Enterprise-PostgreSQL-Security.pdf

  • CYBERTEC PGEE feature comparison ("Time related permissions"): https://www.cybertec-postgresql.com/en/cybertec-pgee-feature-comparison/

  • CYBERTEC PGEE product page: https://www.cybertec-postgresql.com/en/products/cybertec-postgresql-enterprise-edition/

  • PostgreSQL docs, Overview of Event Trigger Behavior (ddl_command_start, table_rewrite scope): https://www.postgresql.org/docs/current/event-trigger-definition.html

  • PostgreSQL / CYBERTEC mirror, A Table Rewrite Event Trigger Example: https://repository.cybertec.at/doc/current/event-trigger-table-rewrite-example.html

  • CYBERTEC, pg_permissions (open source): https://github.com/cybertec-postgresql/pg_permissions

  • Worlber, PostgreSQL audit logging with PGEE: https://blog.worlber.sa/posts/postgresql-audit-logging-tamper-resistant-pgee

Next step

Bring us your database challenge

Send us your environment and goal. We will tell you what fits, what does not, and the safest next step.

contactus@worlber.com

PGEE maintenance-window permissions: schema changes only when approved | Worlber Insights