# PostgreSQL Event Trigger 應用之一 前幾天介紹了 event trigger,現在來介紹一下應用. ## aduit, log audit 或是 log 是常見的應用,在網路上也能找到一些例子. 上次的介紹中,使用列印出訊息的方式,現在改用table儲存. ### 建立table 與 function 及 event trigger ```sql= create table ddl_history ( id int generated always as identity primary key , ts timestamptz , tag text , obj text , cmd text ); create or replace function f_ddl_end() returns event_trigger language plpgsql as $code$ declare event_tuple record; begin for event_tuple in select command_tag, object_identity from pg_event_trigger_ddl_commands() loop insert into ddl_history(ts, tag, obj, cmd) values (statement_timestamp(), event_tuple.command_tag , event_tuple.object_identity, current_query()); end loop; end; $code$; -- create or replace function f_drop_info() returns event_trigger language plpgsql as $code$ declare event_tuple record; begin for event_tuple in select object_type, object_identity from pg_event_trigger_dropped_objects() loop insert into ddl_history(ts, tag, obj, cmd) values (statement_timestamp(), tg_tag || ' / ' || event_tuple.object_type , event_tuple.object_identity, current_query()); end loop; end; $code$; -- create event trigger tr_ddl_end on ddl_command_end execute function f_ddl_end(); create event trigger tr_drop on sql_drop execute function f_drop_info(); ``` ### 測試 ```sql= create table bar ( id int ); alter table bar add column bname text; alter table bar add column j text; alter table bar alter column j type int using(j::int); alter table bar alter column id type text; drop table bar; commit; -------- select * from ddl_history; id | ts | tag | obj | cmd ----+-------------------------------+--------------------------+--------------------------------+---------------------------------------- 1 | 2021-05-19 14:35:29.047706+08 | CREATE TABLE | s12.bar | create table bar ( + | | | | id int + | | | | ); 2 | 2021-05-19 14:35:35.123379+08 | ALTER TABLE | s12.bar | alter table bar + | | | | add column bname text; 3 | 2021-05-19 14:35:40.758333+08 | ALTER TABLE | s12.bar | alter table bar + | | | | add column j text; 4 | 2021-05-19 14:35:47.137456+08 | ALTER TABLE | s12.bar | alter table bar + | | | | alter column j type int using(j::int); 5 | 2021-05-19 14:35:54.399101+08 | ALTER TABLE | s12.bar | alter table bar + | | | | alter column id type text; 6 | 2021-05-19 14:36:00.47093+08 | DROP TABLE / table | s12.bar | drop table bar; 7 | 2021-05-19 14:36:00.47093+08 | DROP TABLE / type | s12.bar | drop table bar; 8 | 2021-05-19 14:36:00.47093+08 | DROP TABLE / type | s12.bar[] | drop table bar; 9 | 2021-05-19 14:36:00.47093+08 | DROP TABLE / toast table | pg_toast.pg_toast_818036 | drop table bar; 10 | 2021-05-19 14:36:00.47093+08 | DROP TABLE / type | pg_toast.pg_toast_818036 | drop table bar; 11 | 2021-05-19 14:36:00.47093+08 | DROP TABLE / index | pg_toast.pg_toast_818036_index | drop table bar; (11 rows) ---------- --stemp 2: create or replace procedure someddl() language sql as $code$ create table baz ( id int ); alter table baz add column bname text; alter table baz add column j text; alter table baz alter column j type int using(j::int); alter table baz alter column id type text; drop table baz; $code$; commit; call someddl(); commit; ---------- select * from ddl_history; .... 13 | 2021-05-19 14:40:42.710363+08 | CREATE TABLE | s12.baz | call someddl(); 14 | 2021-05-19 14:40:42.710363+08 | ALTER TABLE | s12.baz | call someddl(); 15 | 2021-05-19 14:40:42.710363+08 | ALTER TABLE | s12.baz | call someddl(); 16 | 2021-05-19 14:40:42.710363+08 | ALTER TABLE | s12.baz | call someddl(); 17 | 2021-05-19 14:40:42.710363+08 | ALTER TABLE | s12.baz | call someddl(); 18 | 2021-05-19 14:40:42.710363+08 | DROP TABLE / table | s12.baz | call someddl(); 19 | 2021-05-19 14:40:42.710363+08 | DROP TABLE / type | s12.baz | call someddl(); 20 | 2021-05-19 14:40:42.710363+08 | DROP TABLE / type | s12.baz[] | call someddl(); 21 | 2021-05-19 14:40:42.710363+08 | DROP TABLE / toast table | pg_toast.pg_toast_818055 | call someddl(); 22 | 2021-05-19 14:40:42.710363+08 | DROP TABLE / type | pg_toast.pg_toast_818055 | call someddl(); 23 | 2021-05-19 14:40:42.710363+08 | DROP TABLE / index | pg_toast.pg_toast_818055_index | call someddl(); -- alter event trigger tr_drop disable; alter event trigger tr_ddl_end disable; ``` ### 測試結果討論 可以觀察到drop 會產生連鎖動作,所以使用了單獨的function及event trigger,將其資訊細化. 另外使用了 procedure 來觀察,可以看到是紀錄了 procedure name. ## 限制DDL 第二種應用是限制DDL. ### 例如我們想要限制create view ```sql= create or replace function f_abort_tag() returns event_trigger language plpgsql as $code$ begin raise exception '禁止執行 %', tg_tag; end; $code$; create event trigger abort_view on ddl_command_start when tag in ('CREATE VIEW') execute function f_abort_tag(); -- 測試: create view v_ddl_hist as select tag,obj from ddl_history; ERROR: P0001: 禁止執行 CREATE VIEW --會阻擋執行 create view. --將這個 event trigger abort_view 先 drop 掉. drop event trigger abort_view; commit; ``` 這樣雖然有功能,但是不夠細緻,例如想要指名特定的物件. ### 例如我們想保護 ddl_history , 不能被 alter. ```sql= create or replace function f_prot_tbl() returns event_trigger language plpgsql as $code$ declare event_tuple record; begin for event_tuple in select command_tag, object_identity from pg_event_trigger_ddl_commands() loop if event_tuple.command_tag = 'ALTER TABLE' and event_tuple.object_identity = 's12.ddl_history' then raise exception '結界保護 %', 's12.ddl_history'; end if; end loop; end; $code$; create event trigger tr_prot_tbl on ddl_command_end when tag in ('ALTER TABLE') execute function f_prot_tbl(); -- 測試: alter table ddl_history add column j int; ERROR: P0001: 結界保護 s12.ddl_history --- \dy tr_prot_tbl List of event triggers Name | Event | Owner | Enabled | Function | Tags -------------+-----------------+--------+---------+------------+------------- tr_prot_tbl | ddl_command_end | pagila | enabled | f_prot_tbl | ALTER TABLE alter event trigger tr_prot_tbl disable; ``` ### 我全都要!! 若建立 event trigger on ddl_command_start 沒有指定 tag, 使用f_abort_tag(). 而 f_abort_tag() 裡面也沒有細化判斷,也就是說所有能攔截到的 ddl command 在 start 時,就被 exception 中斷了. ```sql= create event trigger abort_ddl on ddl_command_start execute function f_abort_tag(); -- 測試: create table foz (id int); ERROR: P0001: 禁止執行 CREATE TABLE ``` ### 這樣是否就全部被封印了? 在這情形下, Event and Command Tag Matrix 裡的 ddl 都被禁止了. 但是 event trigger 的 相關是不被攔截的,所以我們是可以控制. ```sql= alter event trigger abort_ddl disable; create table foz (id int); \d foz Table "s12.foz" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- id | integer | | | ``` 這樣就復元了! PostgreSQL 的 event trigger 是不攔截 event trigger 相關的,這樣避免作繭自縛. ### 驗證其他ddl ```sql= --驗證 drop 是否被攔截 alter event trigger abort_ddl enable; drop table foz; ERROR: P0001: 禁止執行 DROP TABLE --驗證是否能夠 CREATE TEMPORARY TABLE create temporary table foy (id int); ERROR: P0001: 禁止執行 CREATE TABLE alter event trigger abort_ddl disable; ``` ### 全部封印的探討 這樣 database 就進入保護模式, 結構不會被改變,在一些情境下有其用處. 例如測試時,為了減少不確定因素;或是資安政策,需要對資料庫的結構有版本管理等等. 但是上面的 f_abort_tag(), 裡面很簡單,就是全攔截了,不夠細化. 在實務應用上,可能還會有 create temporary table 或是 create unlogged table 的需要. ### 開放部分功能的方式 ```sql= create or replace function f_abort_tag2() returns event_trigger language plpgsql as $code$ declare event_tuple record; v_reltype char; begin if tg_tag = 'CREATE TABLE' then for event_tuple in select objid from pg_event_trigger_ddl_commands() loop select relpersistence into v_reltype from pg_class where oid = event_tuple.objid; if v_reltype = 'p' then raise exception '禁止建立普通的 table'; end if; end loop; else raise exception '禁止執行 %', tg_tag; end if; end; $code$; -- 接著建立 on ddl_command_end 的 trigger create event trigger freeze_ddl on ddl_command_end execute function f_abort_tag2(); --- -- 驗證是否能夠 CREATE TEMPORARY TABLE create temporary table foy (id int); \d foy Table "pg_temp_3.foy" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- id | integer | | | -- 驗證是否能夠 CREATE UNLOGGED TABLE create unlogged table fot (id int); \d fot Unlogged table "s12.fot" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- id | integer | | | create table fos (id int); ERROR: P0001: 禁止建立普通的 table create view v_test as select * from test; ERROR: P0001: 禁止執行 CREATE VIEW alter event trigger freeze_ddl disable; ``` ## 結論 今天探討了兩種應用的方式.在資料庫的應用方面,有許多深入的細節,是一般較少討論或是被忽略了. 最近資安各種議題頗為熱門,event trigger 除了 aduit 以外尚有限制保護的功能,在實務應用上可以適當搭配提高開發效率及資安保護層級.