คำถามเดียว คำตอบพลิกสามชั้น และทั้งสามชั้นวัดเป็นจำนวนแถว
คำถามคือ policy ที่ผูกอยู่กับตารางแล้วและเขียนถูกทุกตัวอักษร กันแถวของผู้เช่าคนอื่น
ไว้ได้จริงหรือยัง
ชั้นที่หนึ่ง เปิดไม่ใช่บังคับ เจ้าของตารางที่สั่ง ENABLE ROW LEVEL SECURITY
แล้วอ่านตารางหกแถวของตัวเอง วัดได้ 6 แถว คือเท่ากับตารางที่ไม่มี policy ผูกอยู่เลย
เติม FORCE ROW LEVEL SECURITY เข้าไป โดยไม่แตะ SQL ของ policy สักตัวอักษร
แล้ววัดได้ 0 แถว ตั้ง GUC เป็นผู้เช่า a ได้ 3 แถว ตั้งเป็น b ได้ 3 แถว
และสั่ง NO FORCE ทั้งที่ GUC ยังตั้งอยู่ กลับไปได้ 6 แถว
สิ่งที่เปลี่ยนในสี่ทรงนั้นคือธงใน catalog ไม่ใช่นิพจน์ที่เขียนไว้ และยังมีอีกสองทาง
ที่ได้ 6 แถว เหมือนกัน คือตารางที่ superuser เป็นเจ้าของ ซึ่ง probe อ่านธงทั้งสองใบ
ได้ true ทั้งคู่ และ role ที่มีธง BYPASSRLS ซึ่งอ่านตารางที่สั่งแค่ ENABLE
ชั้นที่สอง หน่วยที่จับการรั่วนี้ได้มีหน่วยเดียว คำขอที่รั่วสำเร็จทุกครั้ง
มันจึงไม่มีรหัสข้อผิดพลาดให้ดู เหลือแต่จำนวนแถวที่ผู้เรียกได้กลับไป
ชั้นที่สามคมที่สุด หน่วยนั้นมีจุดบอด และคอร์สนี้ชี้มันเอง policy ที่เรียกฟังก์ชัน
ซึ่งไม่ได้ตรึง search_path ทำให้ผู้เช่าที่เห็นแถวสลับจาก 3 กับ 0 เป็น 0 กับ 3
ขณะที่ยอดรวมค้างอยู่ที่ 3 ทั้งก่อนโจมตี ระหว่างโจมตี และหลังตรึงค่านั้นลงบนฟังก์ชัน
ด่านที่เฝ้าแต่ยอดรวม ผ่านการโจมตีนี้ไปอย่างสะอาด
ประกาศไว้ตรงนี้ เพราะหน่วยเป็นสิ่งที่ตัดสินว่าข้ออ้างไหนเข้าคอร์สนี้ได้
ทั้งคอร์สไม่มีหน่วยเวลาสักตัว หน่วยที่คอร์สนี้ยอมรับมีเก้าอย่าง
- จำนวนแถวที่ผู้เรียกอ่านได้ เป็นหน่วยหลักของทั้งแปดบท
- จำนวนแถวที่คำสั่งกระทำจริง ซึ่งเป็น 0 ได้ทั้งที่คำสั่งสำเร็จ
- จำนวน policy ที่ผูกอยู่กับตาราง
- รหัส SQLSTATE ยกเฉพาะรหัส ไม่ยกข้อความ เพราะข้อความเปลี่ยนข้าม major
- ธงบูลีนใน catalog เช่น
relrowsecurity และ relforcerowsecurity
- รหัส HTTP ซึ่งใช้ในบทที่ 7 บทเดียว
- ความยาวและค่าของสตริงที่
current_setting คืนมา ซึ่งบทที่ 2 ใช้แยกสถานะ
“ไม่เคยตั้ง” ออกจาก “ตั้งแล้วสั่ง RESET”
- จำนวน connection ที่ pool ถืออยู่ และจำนวน backend ที่คำขอวิ่งผ่าน ซึ่งบทที่ 7
ใช้ตรึงว่าทั้งสาม pool ถือ connection ใบเดียว
- จำนวนโทเค็นที่ต่างกันระหว่างสามบรรทัดที่ประกาศ pool ซึ่งบทที่ 7 ใช้แสดงว่าสามทาง
ต่างกันตรงไหนพอดี
ราคาของ policy predicate มีนาฬิกาอยู่ข้างใน คอร์สนี้จึงไม่วัดมันและไม่พูดถึงมัน
สองเวทีการวัด เวทีหนึ่งเป็น SQL ล้วน อีกเวทีเป็นแอปจริงที่ยิงคำขอ HTTP
- เวที A
scripts/clinic-rls/spike.py เป็น SQL ล้วน · รันเมื่อ 2026-09-06
ได้ 52 ข้ออ้าง ตรงทั้งหมด · 98 ค่าที่วัดได้ · 3 รอบต่อการเรียกหนึ่งครั้ง
· 0 ค่าขยับระหว่างรอบ · ออกด้วยรหัส 0
- เวที B
scripts/clinic-rls/probe.py ยก Spring Boot 4.1.1 กับ Kotlin 2.4.10
ขึ้นจริง แล้วยิงลำดับคำขอ HTTP ใส่มัน · ตัวเลขที่ได้ถูกตรึงลง
src/data/perf/clinic-rls.ts ซึ่งเป็นของที่สร้างขึ้น ไม่ใช่ของที่พิมพ์มือ
ทั้งสองเวทีวัดบน PostgreSQL 18.6 บน aarch64-unknown-linux-musl
ใน container ชื่อ kaen-rls-pg ที่ port 55438 ซึ่งเป็น container ของคอร์สนี้เอง
ไม่ใช่ของคอร์สคลินิกใบไหนก่อนหน้า
ข้อบังคับที่ทำให้ตัวเลขที่เหลือมีความหมายมีอยู่ข้อเดียว คือทุก role ที่ probe สร้าง
ต้องไม่ใช่ superuser และข้อนี้ถูกยืนยันก่อนวัดทุกรอบ superuser ข้าม RLS
โดยไม่มีเงื่อนไข probe ที่ต่อเป็น postgres จึงพิมพ์ตัวเลขครบโดยไม่ได้วัดอะไรเลย
ประกาศไว้ตรงนี้ เพื่อไม่ให้ใครอ่านจบแล้วเชื่อว่าได้ของที่คอร์สนี้ไม่ได้ให้
- ไม่วัดความเร็ว ราคาของ policy predicate มีนาฬิกาอยู่ข้างใน จึงตกนอกกฎของหน่วย
ที่คอร์สนี้ยอมรับ เหมือนคอร์สที่ 35 และ 39 ในซีรีส์เดียวกัน
- ไม่พูดถึง
LEAKPROOF และการรั่วผ่านข้อความผิดพลาดของฟังก์ชันใน WHERE
เป็นข้อจำกัดที่รู้จักกันดีของ RLS แต่ probe ของคอร์สนี้ไม่ได้วัดมันสักทรง
- ไม่แตะ RLS บนตารางแบบ partition และการสืบทอด ไม่ได้วัด จึงไม่มีข้ออ้าง
- ไม่แตะ
pg_hba.conf TLS และการจัดการ role ทั้งระบบ เป็นคนละคำถาม
- ไม่อ้างถึง RLS ของ SQL Server หรือ Oracle ทุกข้ออ้างผูกกับ PostgreSQL 18.6
- ไม่สอนการเข้ารหัสข้อมูลในแถว เป็นเรื่องของคอร์ส security ทั่วไป
ต้องเขียน SQL เป็น ส่วนความรู้เรื่อง RLS ไม่ต้องมีมาก่อนสักนิด
- อ่านและเขียน SQL ของ PostgreSQL ได้ ระดับที่รู้จัก
SELECT UPDATE INSERT
และเคยเห็น transaction มาก่อน
- ไม่ต้องรู้จัก row-level security มาก่อน ทุกกลไกถูกอธิบายตอนที่มันโผล่ครั้งแรก
- อ่าน Kotlin ออกและเคยเห็น Spring Boot มาก่อน ใช้ในบทที่ 7 บทเดียว
- ต้องมี Docker เพราะทุกข้ออ้างในคอร์สนี้มาจาก probe ที่คุณรันเองได้
ด้วย
scripts/clinic-rls/up.sh แล้วเก็บกวาดด้วย docker rm -f kaen-rls-pg
- ตัวเลขทุกตัวผูกกับรุ่นที่ระบุไว้ PostgreSQL 18.6 · Spring Boot 4.1.1
· Kotlin 2.4.10
แผนผังนี้เติมขึ้นเองเมื่อบทเรียนถูกเพิ่มเข้ามา