ข้าม​ไป​ยัง​เนื้อหา

policy หนึ่ง​ใบ​ผูก​อยู่​กับ​ตาราง เขียน​ถูก​ทุก​ตัว​อักษร และ​เจ้าของ​นับ​ได้​หก​แถว

ตาราง​ของ​คลินิก​มี​หก​แถว สาม​แถว​เป็น​ของ​ผู้​เช่า clinic-a และ​อีก​สาม​แถว​เป็น​ของ clinic-b เจ้าของ​ตาราง​เปิด row-level securityrow-level security (RLS)กลไก​ของ PostgreSQL ที่​ให้​นิพจน์​ตัดสิน​เป็น​ราย​แถว​ว่า​คำ​สั่ง​หนึ่ง​เห็น​หรือ​แตะ​แถว​ใด​ได้​บ้าง · เอกสาร​ของ PostgreSQL 18 แยก​มัน​เป็น​ธง​สอง​ใบ​ใน catalog คือ relrowsecurity กับ relforcerowsecurity และ​การ​เปิด​ใบ​แรก​ไม่​ได้​ทำให้​ใบ​ที่​สอง​เปิด​ตาม · สิ่ง​ที่ probe วัด​คือ​ผล​ของ​การ​แยก​นั้น​บน​ตาราง t_owner ไม่ใช่​ค่า​ของ​ธง​บน​ตาราง​ที่​ยกไว้​แค่​ใบ​แรก ไว้​แล้ว และ​ผูก policy ไว้​หนึ่ง​ใบ นิพจน์​ของ​ใบ​นั้น​เทียบ tenant_id กับ​ค่า​ตั้ง​ของ session ตรง​ไป​ตรง​มา

แล้ว​เจ้าของ​ตาราง​สั่ง​นับ​แถว​ของ​ตัว​เอง ค่าที่​วัด​ได้​คือ 6

หก​คือ​ทั้ง​ตาราง จำนวน​แถว​ที่ policy ใบ​นั้น​คัด​ออก​ไป​จึง​เป็น​ศูนย์ บท​นี้​ตอบ​ว่า​ทำไม แล้ว​เปลี่ยน​มัน​เป็น 0 ด้วย​คำ​สั่ง​เดียว ที่​ไม่​แตะ SQL ของ policy สัก​ตัว​อักษร

📦 clinic — ซี​รีส์​เดิม แต่​หกบท​แรก​เป็น SQL ล้วน

คอร์ส​นี้​อยู่​ใน​ซี​รีส์​คลินิก​เดียว​กับ คอร์ส​คัด​กรอง​ผู้​เรียก และ คอร์ส CORS กับ CSRF แต่​หกบท​แรก​ไม่มี​แอป​มา​เกี่ยวข้อง มี​แต่​ตาราง policy และ role

ตัวเลข​ทุก​ตัว​ใน​บท​นี้​คุณ​รัน​เอง​ได้ สั่ง scripts/clinic-rls/up.sh ซึ่ง​ยก container ชื่อ kaen-rls-pg ขึ้น​ที่ port 55438 แล้ว​สั่ง python3 scripts/clinic-rls/spike.py เก็บ​กวาด​ด้วย docker rm -f kaen-rls-pg

ทั้ง​บท​ตั้ง​อยู่​บน DDL ก้อน​เดียว และ​ก้อน​นี้​ไม่​ถูก​แก้​อีก​เลย​จน​จบ​บท

ทรง​ตั้ง​ต้น · สั่ง​โดย role ที่​เป็น​เจ้าของ​ตาราง
create table t_owner (
id bigserial primary key,
tenant_id text not null,
patient text not null,
archived boolean not null default false);
insert into t_owner (tenant_id, patient, archived) values
('clinic-a','Ann',false),('clinic-a','Ben',false),('clinic-a','Cid',true),
('clinic-b','Dan',false),('clinic-b','Eve',false),('clinic-b','Fay',true);
grant select, insert, update, delete on t_owner to kaen_rls_app, kaen_rls_bypass;
grant usage, select on sequence t_owner_id_seq to kaen_rls_app, kaen_rls_bypass;
alter table t_owner enable row level security;
create policy p on t_owner for all using (tenant_id = current_setting('app.tenant', true));

บรรทัด​รอง​สุดท้าย​คือ ENABLE ROW LEVEL SECURITYALTER TABLE … ENABLE ROW LEVEL SECURITYคำ​สั่ง​ที่​ยก​ธง​ใบ​แรก​ให้​ตาราง · ธง​ใบ​นี้​ไม่​ครอบ​เจ้าของ​ตาราง ทรง​ที่​เจ้าของ​อ่าน​ตาราง t_owner หก​แถว​ของ​ตัว​เอง​ใต้ ENABLE ล้วน วัด​ได้ 6 แถว (ownerEnableNoGuc) คือ​ทั้ง​ตาราง จำนวน​แถว​ที่ policy คัด​ออก​ไป​จึง​เป็น​ศูนย์ ซึ่ง​เป็น​คำ​สั่ง​ที่​คน​ส่วน​ใหญ่​จำ​ได้​ว่า เป็น​สวิตช์​เปิด​กลไก​นี้ บรรทัด​สุดท้าย​คือ CREATE POLICYCREATE POLICYคำ​สั่ง​ที่​ผูก​นิพจน์​หนึ่ง​เข้า​กับ​ตาราง​หนึ่ง · ตาราง t_app ของ probe มี policy ผูก​อยู่ 1 ใบ (appPolicyCount) และ​เขียน​ถูก​ทุก​ตัว​อักษร แต่​จำนวน policy ที่​ผูก​อยู่​ไม่ใช่​หลัก​ฐาน​ว่า​มัน​เคย​ถูก​เรียก​ใช้ ซึ่ง​ผูก​นิพจน์​หนึ่ง​เข้า​กับ​ตาราง

for all แปล​ว่า​ใบ​นี้​ครอบ​ทั้ง SELECT INSERT UPDATE และ DELETE ส่วน current_setting('app.tenant', true) อ่าน​ค่า​ตั้ง​ของ session ที่​ชื่อ app.tenant อาร์กิวเมนต์​ตัว​ที่​สอง​สั่ง​ไม่​ให้​ยก​ข้อ​ผิด​พลาด เมื่อ​ยัง​ไม่มี​ใคร​ตั้ง​ค่า​นั้น​ใน session นี้ บท​ที่ 2 วัด​ว่า​ค่าที่​ได้​ใน​กรณี​นั้น​คือ​อะไร

probe อ่าน​ผลก​ลับ​มา​ด้วย​การ​ต่อ 'KAEN|<ชื่อคีย์>|' ไว้​หน้า​ค่า​ทุก​บรรทัด ชื่อ​คีย์​ที่​คุณ​เห็น​ใน​ทุก fence หลัง​จาก​นี้ คือ​ชื่อ​ที่​ผล​ของ spike.py ใช้​เก็บ​ค่า​นั้น​ไว้ ตัวเลข​ใน​บท​นี้​จึง​อ้างอิง​เป็น​คู่​ของ​ทรง​กับ​คีย์​เสมอ

เจ้าของ​ตาราง​นับ​แถว​ของ​ตัว​เอง แล้ว​ได้​ทั้ง​ตาราง

หัวข้อ​ที่​มีชื่อ​ว่า “เจ้าของ​ตาราง​นับ​แถว​ของ​ตัว​เอง แล้ว​ได้​ทั้ง​ตาราง”

policy ผูก​อยู่​แล้ว RLS เปิด​อยู่​แล้ว และ​ค่าที่​ได้​เท่ากับ​จำนวน​แถว​ทั้ง​ตาราง

คำ​สั่ง​เดียว · session ที่​ยัง​ไม่มี​ใคร​ตั้ง app.tenant
select 'KAEN|ownerEnableNoGuc|'||count(*) from t_owner where true;

ownerEnableNoGuc = 6

ค่า​นี้​ไม่ใช่​ความ​ผิด​พลาด​ของ​นิพจน์ ถ้า​นิพจน์​ถูก​เรียก​ใช้​จริง มัน​จะ​เทียบ tenant_id ของ​ทุก​แถว​กับ​ค่าที่​ยัง​ไม่มี​ใคร​ตั้ง แล้ว​ไม่มี​แถว​ไหน​ผ่าน หัวข้อ​ถัด​ไป​วัด​ข้อ​นี้​ออก​มา​เป็น​ตัวเลข

สิ่ง​ที่​เกิด​ขึ้น​คือ​นิพจน์​ไม่​เคย​ถูก​เรียก​ใช้​เลย เจ้าของ​ตาราง​ไม่​ได้​อยู่​ใต้ policy ของ​ตัว​เอง ด้วย​คำ​สั่ง ENABLE เพียง​คำ​สั่ง​เดียว

คำ​สั่ง​ที่​เพิ่ม​เข้า​มา​คือ ALTER TABLE ไม่ใช่ CREATE POLICY และ​ตัวเลข​ขยับ​สี่​ครั้ง​ใน​ลำดับ​เดียว

ลำดับ​เดียว · session เดียว · ตาราง​ใบ​เดิม
alter table t_owner force row level security;
select 'KAEN|ownerForceNoGuc|'||count(*) from t_owner where true;
set app.tenant = 'clinic-a';
select 'KAEN|ownerForceGucA|'||count(*) from t_owner where true;
set app.tenant = 'clinic-b';
select 'KAEN|ownerForceGucB|'||count(*) from t_owner where true;
alter table t_owner no force row level security;
select 'KAEN|ownerNoForceGucB|'||count(*) from t_owner where true;
สิ่ง​ที่​เพิ่ง​สั่ง​ไป​ก่อน​หน้าคีย์แถว​ที่​นับ​ได้
force row level security · ยัง​ไม่​ตั้ง app.tenantownerForceNoGuc0
set app.tenant = 'clinic-a'ownerForceGucA3
set app.tenant = 'clinic-b'ownerForceGucB3
no force row level security · app.tenant ยัง​เป็น clinic-bownerNoForceGucB6

แถว​แรก​ปิด​คำถาม​ของ​หัวข้อ​ก่อน​หน้า นิพจน์​ใบ​เดิม​ใน​ตาราง​ใบ​เดิม​ให้ 0 เมื่อ​มัน​ถูก​เรียก​ใช้​จริง ส่วน​ที่​ให้ 6 ก่อน​หน้า​นั้น​คือ​ทรง​ที่​มัน​ไม่​ถูก​เรียก​ใช้

FORCE ROW LEVEL SECURITYALTER TABLE … FORCE ROW LEVEL SECURITYคำ​สั่ง​ที่​ยก​ธง​ใบ​ที่​สอง ซึ่ง​พา​เจ้าของ​ตาราง​เข้า​มา​อยู่​ใต้ policy ด้วย · ทุก​ค่า​ต่อ​จาก​นี้​วัด​บน​ตาราง t_owner ที่​เจ้าของ​อ่าน​เอง · เติม​แล้ว​เจ้าของ​ที่​ไม่​ได้​ตั้ง GUC ได้ 0 แถว ตั้ง​เป็น​ผู้​เช่า a ได้ 3 แถว ตั้ง​เป็น b ได้ 3 แถว และ​สั่ง NO FORCE ทั้ง​ที่ GUC ยัง​ตั้ง​อยู่ กลับ​ไป​ได้ 6 แถว โดย SQL ของ policy ไม่​เปลี่ยน​สัก​ตัว​อักษร​ใน​ทุก​ทรง คือ​คำ​สั่ง​ที่​พา​เจ้าของ​ตาราง​เข้า​มา​อยู่​ใต้ policy ด้วย สอง​แถว​กลาง​แสดง​ว่า​นิพจน์​ทำงาน​ตาม​ที่​เขียน​ไว้ ผู้​เช่า​ไหน​ถูก​ตั้ง​ไว้ ก็​เห็น​สาม​แถว​ของ​ผู้​เช่า​นั้น

แถว​สุดท้าย​คือ​แถว​ที่​อ่าน​ง่าย​ที่สุด​ใน​ตาราง app.tenant ยัง​เป็น clinic-b อยู่ ไม่มี​ใคร​แตะ policy และ​ค่า​กลับ​ไป​เป็น 6 สิ่ง​ที่​ยก​ขึ้น​แล้ว​ลด​ลง​จึง​ไม่ใช่​นิพจน์ แต่​เป็น​สถานะ​ของ​ตาราง

ENABLE กับ FORCE ยก​ธง​คนละ​ใบ​ใน catalog และ​ใบ​แรก​ไม่​ได้​ยก​ใบ​ที่​สอง​ให้

เอกสาร​ของ PostgreSQL 18 อธิบาย​ไว้​ว่า​ตาราง​หนึ่ง​ถือ​ธง​สอง​ใบ​ใน pg_class คือ relrowsecurity ที่ ENABLE ยก และ relforcerowsecurity ที่ FORCE ยก ข้อ​นี้​เป็น​ข้อ​อ้าง​เรื่อง​กลไก จึง​อ้าง​เอกสาร ไม่​ได้​อ้าง probe

เอกสาร​หน้า​เดียวกัน​ระบุ​ว่า​เจ้าของ​ตาราง​ถูก​ยกเว้น​จาก policy ตาม​ค่า​ปริยาย และ​ใบ​ที่​สอง​คือ​ใบ​ที่​ถอน​การ​ยกเว้น​นั้น สี่​แถว​ใน​ตาราง​ข้าง​บน​คือ​ค่าที่​วัด​ได้​ของ​กฎ​ข้อ​นี้

การ​แยก​สอง​ข้อ​นี้​ออก​จาก​กัน​สำคัญ​กว่า​ที่​เห็น ตัวเลข 6 กับ 0 เป็น​สิ่ง​ที่ probe วัด ส่วน​คำ​อธิบาย​ว่า​ธง​ใบ​ไหน​ทำ​อะไร เป็น​สิ่ง​ที่​เอกสาร​กำหนด บท​นี้​ไม่​ได้​พิสูจน์​ข้อ​หลัง มัน​เพียง​แสดง​ผล​ที่​สอดคล้อง​กับ​ข้อ​หลัง

ตาราง​ของ superuser ธง​เป็น true ทั้ง​สอง​ใบ แล้ว​ยัง​ได้​หก

หัวข้อ​ที่​มีชื่อ​ว่า “ตาราง​ของ superuser ธง​เป็น true ทั้ง​สอง​ใบ แล้ว​ยัง​ได้​หก”

ทรง​นี้​ยก​ธง​ครบ​ทั้ง​สอง​ใบ อ่าน​ค่า​ธง​ออก​มา​จาก catalog จริง แล้ว​จำนวน​แถว​ยัง​ไม่​ขยับ

ทรง​ที่​สอง​สร้าง​ตาราง t_super ด้วย DDL ก้อน​เดียว​กับ​ข้าง​บน​ทุก​บรรทัด แล้ว​สั่ง force row level security ต่อ​ท้าย ต่าง​กัน​ที่​ผู้​สั่ง​เป็น postgres ซึ่ง​เป็น superuser ตาราง​ใบ​นี้​จึง​มี​ทั้ง policy ทั้ง ENABLE และ​ทั้ง FORCE

ทรง​ที่​สอง · ผู้​อ่าน​เป็น superuser และ​เป็น​เจ้าของ​ตาราง
select 'KAEN|superIsSuper|'||rolsuper from pg_roles where rolname = current_user;
select 'KAEN|superRlsEnabled|'||relrowsecurity from pg_class where oid='t_super'::regclass;
select 'KAEN|superRlsForced|'||relforcerowsecurity from pg_class where oid='t_super'::regclass;
select 'KAEN|superOwnerForceNoGuc|'||count(*) from t_super where true;
set app.tenant = 'clinic-a';
select 'KAEN|superOwnerForceGucA|'||count(*) from t_super where true;
คีย์ค่าที่​วัด​ได้
superIsSupertrue
superRlsEnabledtrue
superRlsForcedtrue
superOwnerForceNoGuc6
superOwnerForceGucA6

สาม​แถว​บน​คือ​ธง​ที่​อ่าน​ออก​มา​จาก catalog จริง ไม่ใช่​สิ่ง​ที่​บท​นี้​เดา​จาก​คำ​สั่ง​ที่​สั่ง​ไป สอง​แถว​ล่าง​คือ​จำนวน​แถว​ที่​ผู้​อ่าน​คน​นั้น​ได้​กลับ​ไป ทั้ง​ตอน​ไม่​ตั้ง​ค่า​และ​ตอน​ตั้ง​เป็น clinic-a

ทรง​นี้​คือ​ทรง​ที่​หลอก​ได้​มาก​ที่สุด​ใน​บท ตาราง​มี policy มี​ธง​ครบ​ทั้ง​สอง​ใบ และ​ค่าที่​อ่าน​ออก​มา​จาก catalog ยืนยัน​ว่า​ครบ​จริง แต่​ตัวเลข​ยัง​เป็น​หก superuser ข้าม policy โดย​ไม่มี​เงื่อนไข ซึ่ง​เป็น​ข้อ​ที่​เอกสาร​กำหนด ไม่ใช่​ข้อ​ที่​ธง​สอง​ใบ​นี้​บอก

ทาง​ที่​สาม ไม่​ได้​อยู่​ที่​ตาราง แต่​อยู่​ที่​ตัว role ของ​ผู้​อ่าน

ทรง​ที่​สาม​สร้าง​ตาราง t_app ด้วย DDL ก้อน​เดิม​และ policy ใบ​เดิม probe นับ policy ที่​ผูก​อยู่​กับ​ตาราง​ใบ​นี้​จาก pg_policy ได้ appPolicyCount = 1 ผู้​อ่าน​ใน​ทรง​นี้​คือ role คนละ​ใบ​กับ​เจ้าของ​ตาราง และ​ถือ​ธง BYPASSRLSBYPASSRLSธง​ของ role ที่​ให้​ข้าม policy ทุก​ใบ · role ที่​มี​ธง​นี้​อ่าน​ตาราง t_app ซึ่ง​เปิด RLS แล้ว​และ​มี policy ผูก​อยู่​หนึ่ง​ใบ ได้ 6 แถว​ทั้ง​ตอน​ตั้ง GUC และ​ตอน​ไม่​ตั้ง ขณะ​ที่ role ที่​ไม่มี​ธง​บน​ตาราง​เดียวกัน​ได้ 3 แถว​และ 0 แถว · superuser ข้าม​การ​ตรวจ​สิทธิ์​ทุก​ชนิด​อยู่​แล้ว ซึ่ง​เป็น​คนละ​เงื่อนไข​กับ​ธง​ใบ​นี้ ไม่ใช่​การ​ไม่มี​ธง · probe จึง​ยืนยัน​ก่อน​วัด​ว่า​ทุก role ที่​มัน​สร้าง​ไม่ใช่ superuser (rolesAreSuperuser = 0)

ทรง​ที่​สาม · ผู้​อ่าน​ไม่ใช่​เจ้าของ​ตาราง และ​ไม่ใช่ superuser
select 'KAEN|bypassNoGuc|'||count(*) from t_app where true;
set app.tenant = 'clinic-a';
select 'KAEN|bypassGucA|'||count(*) from t_app where true;

bypassNoGuc = 6 และ bypassGucA = 6

สอง​ค่า​นี้​เท่า​กัน ซึ่ง​เป็น​สิ่ง​ที่​ต้อง​อ่าน​ให้​ครบ การ​ตั้ง app.tenant เป็น clinic-a ไม่​ได้​ทำให้​ตัวเลข​ขยับ​แม้แต่​แถว​เดียว ค่า​ตั้ง​ที่ policy อ่าน​จึง​ไม่มี​ผล​กับ role ใบ​นี้​เลย เพราะ​นิพจน์​ไม่​ได้​ถูก​เรียก​ใช้​ตั้งแต่​ต้น

เทียบ​กับ​ทรง​แรก นิพจน์​รูป​เดียวกัน​ที่​ถูก​เรียก​ใช้​จริง ให้ ownerForceGucA = 3 เมื่อ app.tenant เป็น clinic-a ธง​ใบ​นี้​จึง​ไม่​ได้​เปลี่ยน​ผล​ของ​นิพจน์ มัน​เอา​นิพจน์​ออก​จาก​ทาง​เดิน​ของ​คำ​สั่ง

ธง​ของ role กับ​ธง​ของ​ตาราง อยู่​คนละ​ที่​และ​แก้​คนละ​คน

BYPASSRLS ผูก​กับ role ไม่​ได้​ผูก​กับ​ตาราง คน​ที่​ตรวจ​ตาราง​ของ​ตัว​เอง​ว่า เปิด​ครบ​ทั้ง​สอง​ใบ​แล้ว ยัง​ตอบ​ไม่​ได้​ว่า​มี​ใคร​อ่าน​ตาราง​นั้น​แบบ​ข้าม policy อยู่​หรือ​ไม่

superIsSuper เป็น true และ bypassNoGuc เป็น 6 เป็น​สอง​ข้อ​ที่​แยก​กัน ข้อ​แรก​คือ superuser ซึ่ง​เอกสาร​จัด​เป็น​เงื่อนไข​คนละ​ข้อ​กับ​ธง​นี้ ข้อ​ที่​สอง​คือ role ที่ probe ยืนยัน​ก่อน​วัด​แล้ว​ว่า​ไม่ใช่ superuser และ​ถือ​ธง​นี้​ติดตัว​มา

ข้อ​เดียว​ที่​ทำให้​ตัวเลข​ที่​เหลือ​ทั้ง​คอร์ส​มี​ความ​หมาย

หัวข้อ​ที่​มีชื่อ​ว่า “ข้อ​เดียว​ที่​ทำให้​ตัวเลข​ที่​เหลือ​ทั้ง​คอร์ส​มี​ความ​หมาย”

ถ้า role ที่ probe ใช้​วัด​เป็น superuser มัน​จะ​พิมพ์​ตัวเลข​ออก​มา​ครบ โดย​ไม่​ได้​วัด​อะไร​เลย

หัวข้อ​ก่อน​หน้า​เพิ่ง​แสดง​ว่า superuser ข้าม policy โดย​ไม่มี​เงื่อนไข ข้อ​นั้น​ทำให้ probe ที่​ต่อ​ฐาน​ข้อมูล​ด้วย role ผิด​ใบ กลาย​เป็น probe ที่​รายงาน​หก​แถว​ทุก​ทรง แล้ว​อ่าน​เหมือน​การ​ค้น​พบ ทั้ง​ที่​มัน​คือ​การ​ไม่​ได้​วัด

role ทั้ง​สาม​ใบ​ที่​ทุก​ทรง​ใน​คอร์ส​นี้​ใช้
create role kaen_rls_owner login nosuperuser nocreatedb nocreaterole;
create role kaen_rls_app login nosuperuser nocreatedb nocreaterole;
create role kaen_rls_bypass login nosuperuser nocreatedb nocreaterole bypassrls;

spike.py ตรวจ role ทั้ง​สาม​ใบ​ทันที​ที่​สร้าง​เสร็จ ก่อน​ที่​ทรง​ไหน​จะ​ได้​วัด​สัก​ทรง ถ้า​มี​ใบ​ไหน​เป็น superuser มัน​หยุด​ด้วย​รหัส​ออก 2 ซึ่ง​แปล​ว่า​วัด​ไม่​ได้ ไม่ใช่​ข้อ​อ้าง​ผิด จำนวน​ที่​นับ​ได้​ถูก​เก็บ​เป็น​ค่าที่​วัด​ได้​ตัว​หนึ่ง​ด้วย คือ rolesAreSuperuser = 0

ธง bypassrls บน​บรรทัด​ที่​สาม​คือ​ธง​ที่​ตั้งใจ​ใส่​ให้ role ใบ​เดียว และ probe ยืนยัน​ก่อน​วัด​ด้วย​ว่า​อีก​สอง​ใบ​ไม่มี​ธง​นี้​ติด​มา

ทาง​แรก​อยู่​ที่​ตาราง ทาง​ที่​สอง​อยู่​ที่​ชนิด​ของ role และ​ทาง​ที่​สาม​อยู่​ที่​ธง​บน role

flowchart TB
  A1["ทรงที่ 1 · เจ้าของตาราง อ่าน t_owner ของตัวเอง"] --> A2{"สั่ง FORCE ให้ t_owner แล้วหรือยัง"}
  A2 -->|"ยัง · มีแค่ ENABLE"| A3["6 แถว · ownerEnableNoGuc และ ownerNoForceGucB"]
  A2 -->|"สั่งแล้ว"| A4{"app.tenant ถูกตั้งไว้หรือยัง"}
  A4 -->|"ยังไม่ตั้ง"| A5["0 แถว · ownerForceNoGuc"]
  A4 -->|"clinic-a หรือ clinic-b"| A6["3 แถว · ownerForceGucA และ ownerForceGucB"]
  B1["ทรงที่ 2 · superuser อ่าน t_super ของตัวเอง · ธงใน catalog เป็น true ทั้งสองใบ"] --> B2["6 แถว ทั้งตอนตั้งและไม่ตั้ง app.tenant"]
  C1["ทรงที่ 3 · role ที่ถือธง BYPASSRLS อ่าน t_app"] --> C2["6 แถว ทั้งตอนตั้งและไม่ตั้ง app.tenant"]

คำ​บรรยาย​ภาพ: สาม​ทรง​ที่​บท​นี้​วัด ทุก​ทรง​มี policy ผูก​อยู่​กับ​ตาราง​และ​เปิด RLS ไว้​แล้ว ทรง​ที่​หนึ่ง​เป็น​ทาง​เดียว​ที่​นิพจน์​ถูก​เรียก​ใช้ และ​เป็น​ทาง​เดียว​ที่​ตัวเลข​ขยับ

ปลาย​ทาง​สาม​ใบ​ที่​เขียน​ว่า​หก​แถว มา​จาก​เงื่อนไข​คนละ​ข้อ​กัน​ทั้ง​สาม​ใบ ใบ​หนึ่ง​แก้​ด้วย ALTER TABLE ใบ​ที่​สอง​แก้​ด้วย​การ​ไม่​ต่อ​ฐาน​ข้อมูล​ด้วย superuser และ​ใบ​ที่​สาม​แก้​ด้วย​การ​ถอน​ธง​ออก​จาก role

ตัวเลข​ทั้ง​บท​มา​จาก SQL ที่​รัน​จริง ส่วน​คำ​อธิบาย​ว่า​ธง​ใบ​ไหน​ทำ​อะไร มา​จาก​เอกสาร

spike.py รัน​ชุด​ข้อ​อ้าง​ทั้ง​ชุด​สาม​รอบ​ต่อ​การ​เรียก​หนึ่ง​ครั้ง แล้ว​เทียบ​ผล​ที​ละ​คีย์ รอบ​ที่​รัน​เมื่อ 2026-09-06 ได้ 52 ข้อ​อ้าง​ตรง​ทั้งหมด 98 ค่าที่​วัด​ได้ และ 0 ค่าที่​ขยับ​ระหว่าง​รอบ

สิ่ง​ที่​บท​นี้ ไม่​ได้ พิสูจน์​คือ​กลไก​ข้าง​ใน บท​นี้​ไม่​ได้​แสดง​ว่า​ตัว​ตัดสิน อ่าน​ธง​ใบ​ไหน​ก่อน​ใบ​ไหน ข้อ​อ้าง​แบบ​นั้น​อ้าง​เอกสาร​ของ PostgreSQL 18 และ​เอกสาร​เป็น​คนละ​หลัก​ฐาน​กับ probe

ตัวเลข​ทุก​ตัว​ผูก​กับ​รุ่น​ที่​ระบุ​ไว้

ผล​ทั้งหมด​ใน​บท​นี้​วัด​บน PostgreSQL 18.6 บน aarch64-unknown-linux-musl ใน container ของ​คอร์ส​นี้​เอง major อื่น​เป็น​คนละ​การ​วัด และ​บท​นี้​ไม่​ได้​อ้าง​ถึง​มัน

หน่วย​ของ​หลัก​ฐาน​ใน​บท​นี้​มี​สาม​หน่วย คือ​จำนวน​แถว​ที่​ผู้​อ่าน​นับ​ได้ ธง​บู​ลี​นที่​อ่าน​ออก​มา​จาก pg_class กับ pg_roles และ​จำนวน policy ที่​ผูก​กับ​ตาราง ทั้ง​คอร์ส​ไม่มี​หน่วย​เวลา​สัก​ตัว

รู้​แล้ว​ว่า​อะไร​ทำให้ policy ถูก​เรียก​ใช้ ยัง​ไม่รู้​ว่า​ค่าที่​มัน​อ่าน อยู่​ได้​นาน​แค่​ไหน

ทรง​ที่​หนึ่ง​ใต้ FORCE ให้ 0 เมื่อ​ไม่​ตั้ง app.tenant และ​ให้ 3 เมื่อ​ตั้ง​เป็น​ผู้​เช่า​ใบ​ใด​ใบ​หนึ่ง สอง​ค่า​นั้น​วัด​ได้​แล้ว แต่​ยัง​ไม่มี​ข้อ​ไหน​บอก​ว่า​ค่าที่​ตั้ง​ไป​นั้น​ถูก​เก็บ​ไว้​ที่ไหน

ถ้า​มัน​เป็น​ค่า​ของ session คำถาม​ถัด​ไป​คือ session หนึ่ง​กิน​เวลา​ถึง​คำขอ​กี่​ใบ และ​คำ​สั่ง​ที่​ล้าง​ค่า​นั้น​ทิ้ง คืน​สถานะ​กลับ​ไป​เป็น​แบบ​ที่​ไม่​เคย​ตั้ง​เลย​จริง​หรือ​ไม่

บท​ที่ 2 ตั้ง​ค่า​นั้น​ด้วย​สอง​รูป​ที่​ต่าง​กัน แล้ว​วัด​ว่า​หลัง commit แต่ละ​รูป​เหลือ​อะไร คำ​ตอบ​ของ​สอง​รูป​นั้น​ไม่​เท่า​กัน และ​ความ​ต่าง​ของ​มัน​เดิน​ไป​โผล่​อีก​ครั้ง​ที่​บท​ที่ 7


🔗 อ้างอิง​ต้นทาง​ของ​บท​นี้
  • PostgreSQL 18 — Row Security Policies (เข้า​ถึง 2026-09-06) — ที่มา​ของ​กฎ​ว่า​เจ้าของ​ตาราง​ถูก​ยกเว้น​จาก policy ตาม​ค่า​ปริยาย และ superuser ข้าม policy โดย​ไม่มี​เงื่อนไข

  • PostgreSQL 18 — ALTER TABLE (เข้า​ถึง 2026-09-06) — ความ​หมาย​ของ ENABLE FORCE และ NO FORCE ROW LEVEL SECURITY

  • PostgreSQL 18 — CREATE ROLE (เข้า​ถึง 2026-09-06) — ความ​หมาย​ของ​ธง BYPASSRLS บน role

  • ตัวเลข​ทุก​ตัว​ใน​บท​นี้​มา​จาก scripts/clinic-rls/spike.py ซึ่ง​สร้าง​ฐาน​ข้อมูล​กับ role ขึ้น​ใหม่​ทุกร​อบ แล้ว​รื้อ​ทิ้ง​เมื่อ​จบ · ต้อง​สั่ง scripts/clinic-rls/up.sh ก่อน ซึ่ง​ยก container ของ​คอร์ส​นี้​เอง​ขึ้น​ที่ port 55438

เช็กความเข้าใจ — บทที่ 1

ข้อ 1 / 3

เจ้าของตารางสั่ง `enable row level security` ผูก policy ที่เขียนถูกไว้หนึ่งใบ แล้วนับแถวของตารางหกแถวของตัวเอง ได้เท่าไร