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

สัญญา​ว่า​ด้วย​การ​มอง​เห็น — ทุก​ข้อ​ที่​พูด​ได้​แล้ว และ​แถว​ที่​ไม่มี​บท​ไหน​พิสูจน์

เจ็ด​บท​ที่​ผ่าน​มา​วัด​ค่า​ออก​มา​เป็น​ชุด บท​นี้​ไม่​สร้าง​ข้อ​อ้าง​ใหม่ มัน​ประกอบ​ของ​เดิม · สาม​ค่าที่​โผล่​เฉพาะ​บท​นี้​คือ dbLeftAfterDrop rolesLeftAfterDrop และ STRICTAFTERRESET.strictCount ซึ่ง​เป็น​ค่าที่ probe วัด ไม่ใช่​ค่าที่​บท​นี้​คิด​ขึ้น มัน​เรียง​สิ่ง​ที่​คุณ​พูด​ได้​แล้ว​ไว้​ข้าง​หลัก​ฐาน​ของ​ข้อ​นั้น แล้ว​บอก​ว่า​ใคร​เป็น​คน​พิสูจน์

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

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

📦 clinic — ตัวเลข​ใน​ตาราง​ข้าง​ล่าง​คุณ​รัน​เอง​ได้

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

เวที A สั่ง scripts/clinic-rls/up.sh ซึ่ง​ยก container ชื่อ kaen-rls-pg ขึ้น​ที่ port 55438 แล้ว​สั่ง python3 scripts/clinic-rls/spike.py เก็บ​กวาด​ด้วย docker rm -f kaen-rls-pg ส่วน​เวที B ถูก​ตรึง​ไว้​ที่ src/data/perf/clinic-rls.ts

ตาราง​สัญญา · ข้อ​ที่​พูด​ได้ กับ​ทรง​ที่​พิสูจน์​มัน

หัวข้อ​ที่​มีชื่อ​ว่า “ตาราง​สัญญา · ข้อ​ที่​พูด​ได้ กับ​ทรง​ที่​พิสูจน์​มัน”

ทุก​แถว​ถือ​คู่​ของ​ทรง​กับ​คีย์ และ​ช่อง​ขวา​สุด​บอก​ว่า​บท​ไหน​เป็น​คน​วัด

ตาราง​ใบ​นี้​อ่าน​จาก​ซ้าย​ไป​ขวา ช่อง​ซ้าย​คือ​ประโยค​ที่​คุณ​พูด​กับ​เพื่อน​ร่วม​งาน​ได้ ช่อง​กลาง​คือ​ของ​ที่​ทำให้​ประโยค​นั้น​ไม่ใช่​ความ​เห็น และ​ช่อง​ขวา​คือ​ที่​ที่​หลัก​ฐาน​ถูก​วัด

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

ข้อ​ที่​พูด​ได้ทรง · คีย์ · ค่าที่​วัด​ได้บท​ที่​พิสูจน์
เปิด RLS ไม่ใช่​บังคับ RLSt_owner · ownerEnableNoGuc 6 เทียบ​กับ ownerForceNoGuc 0บท​ที่ 1
คำ​สั่ง​ที่​ทำให้​เลข​ขยับ ไม่​ได้​แตะ SQL ของ policyt_owner · ownerForceGucA 3 · ownerForceGucB 3 · ownerNoForceGucB 6บท​ที่ 1
ธง​ครบ​สอง​ใบ​ยัง​ไม่​พอ ถ้า​ผู้​อ่าน​เป็น superusert_super · superRlsEnabled true · superRlsForced true · superOwnerForceNoGuc 6 · superOwnerForceGucA 6บท​ที่ 1
BYPASSRLS ผูก​กับ role ไม่​ได้​ผูก​กับ​ตารางt_app · bypassNoGuc 6 · bypassGucA 6 ขณะ​ที่ appPolicyCount 1บท​ที่ 1
คีย์​ของ​ผู้​เช่า​เป็น​ค่า​ตั้ง​ของ sessiont_app · appGucA 3 · appGucB 3 · appNoGuc 0บท​ที่ 2
ผู้​เช่า​ที่​ไม่มี​อยู่​จริง​ไม่ใช่ error มัน​เป็น​ศูนย์​แถวt_app · appGucUnknown 0บท​ที่ 2
SET LOCAL จบ​ที่ commit · SET ธรรมดา​ไม่​จบt_guc · gucInsideSetLocal 3 แล้ว gucAfterCommitSetLocal 0 เทียบ​กับ gucInsideSet 3 แล้ว gucAfterCommitSet 3บท​ที่ 2
rollback คืน​ค่า​ก่อน​หน้า ไม่​ได้​คืน​ค่า​ว่างt_guc · gucAfterRollbackSet 3 · gucValueAfterRollback clinic-aบท​ที่ 2
”ไม่​เคย​ตั้ง” กับ “เคย​ตั้ง​แล้ว​ล้าง” เป็น​คนละ​สถานะทรง current_setting ที่​ไม่​ใส่​อาร์กิวเมนต์​ที่​สอง · csNoArgNeverSet 42704 เทียบ​กับ csNoArgAfterReset 00000 · csTrueAfterResetLen 0 · csCountAfterReset 0บท​ที่ 2
USING ทำ​หน้าที่​เป็น check ให้​เอง​เมื่อ​ไม่มี WITH CHECKt_insert · insUsingOnlyOther 42501 แล้ว​เติม with check (true) ได้ insWithCheckOther 00000 กระทบ 1 แถว · insWithCheckRowsB 4บท​ที่ 3
ตัว​ที่​ตัดสิน UPDATE ข้าม​ผู้​เช่า คือ policy ของ SELECTt_update · updMoveUsingOnly 42501 · updMoveWithCheckTrue 42501 · updMoveSelectOpen 00000 กระทบ 1 แถว · updSelectOpenRowsA 2 · updSelectOpenRowsB 4บท​ที่ 3
ผู้​เขียน​มอง​ไม่​เห็น​แถว​ที่​ตัว​เอง​เพิ่ง​เขียนt_insert · insWithCheckOtherReturning 42501 · insWithCheckVisibleToApp 4บท​ที่ 3
การ​ปฏิเสธ​มี​สอง​เสียง และ​เสียง​หนึ่ง​ไม่​ดังt_selonly · selOnlyUpdate 00000 กระทบ 0 แถว · selOnlyDelete 00000 กระทบ 0 แถว · selOnlyInsert 42501 · selOnlyRowsAfter 6 · selOnlyBangs 0บท​ที่ 4
เปิด RLS โดย​ไม่มี policy สัก​ใบ คือ​ปฏิเสธ ไม่ใช่​อนุญาตt_combine · combineZeroPolicies 0บท​ที่ 4
permissive รวม​กัน​ด้วย OR · restrictive รวม​ด้วย ANDt_combine · combineTwoPermissive 6 · combinePermissiveRestrictive 0 · combineTenantAndNotArchived 2 · combineRestrictiveAlone 0บท​ที่ 4
ทาง​เข้า​ที่ policy มอง​ไม่​เห็น มี​มากกว่า​หนึ่ง​ทางt_view ใต้ no force · noForceBase 3 แต่ noForceViewDefault 6 และ noForceSecDefiner 6 ขณะ​ที่ noForceViewInvoker 3 และ noForceSecInvoker 3บท​ที่ 5
FORCE ปิด​สอง​ทาง​ที่​เปิด​อยู่ ส่วน​อีก​สอง​ทาง​ไม่​เคย​เปิด เมื่อ policy ผูก​กับ​ค่า​ตั้ง​ของ sessiont_view ใต้ force · forceBase forceViewDefault forceViewInvoker forceSecDefiner forceSecInvoker เป็น 3 ทั้ง​ห้า​ตัว · forceNoGucViewDefault 0 · forceNoGucSecDefiner 0บท​ที่ 5
ตัว​ที่​ปิด​ได้ ขึ้น​กับ​สิ่ง​ที่ policy ผูก​ไว้ ไม่​ได้​ขึ้น​กับ​ชื่อ​ของ​กลไกt_view · policy ที่​อ่าน current_user ให้ currentUserBase 3 · currentUserViewDefault 3 · currentUserViewInvoker 3 · currentUserSecInvoker 3 · currentUserSecDefiner 0บท​ที่ 5
แถว​ที่​เห็น​เปลี่ยน​เจ้าของ​ได้ โดย​ยอด​รวม​ไม่​ขยับt_hijack · hijackBaseTotal 3 กับ hijackBaseRowsB 0 แล้ว​เป็น hijackOwnTotal 3 กับ hijackOwnRowsA 0 และ hijackOwnRowsB 3บท​ที่ 6
ตรึง search_path ลง​บน​ฟังก์ชัน​แล้ว เลข​กลับ​ที่​เดิมt_hijack · hijackFnHasSearchPath false ก่อน​ตรึง · หลัง​ตรึง​ได้ hijackPinnedTotal 3 กับ hijackPinnedRowsB 0บท​ที่ 6
ทาง pg_temp ปิด​อยู่ สำหรับ​การ​ค้น​ชื่อ​ฟังก์ชันt_hijack · hijackTempQualified clinic-b แต่ hijackTempUnqualified clinic-a · hijackTempTotal 3 · hijackTempRowsB 0บท​ที่ 6
connection ที่​ถูก​คืน​เข้า pool พก​ค่า​ตั้ง​ติด​ไป​ด้วยทรง plain-then-no-tenant · PLAINTHENNOTENANT.b ได้ 200 กับ 3 แถว ซึ่ง​เป็น​แถว​ของ​ผู้​เช่า​คน​ก่อนบท​ที่ 7
สอง​ทรง​ที่ “แก้​แล้ว” ปิด​ฝั่ง​อ่าน​ได้​จริงLOCALTHENNOTENANT.b.count 0 · RESETTHENNOTENANT.b.count 0 · FRESHNOTENANT.b.count 0บท​ที่ 7
ยา​แก้​ย้าย​ที่​เขียน มัน​ไม่​ได้​หยุด​การ​เขียนWRITEPLAIN ได้ total 8 · visibleToA 5 · visibleToB 3 · leakWrittenUnder <clinic-a> เทียบ​กับ WRITELOCAL และ WRITERESET ที่​ได้ total 8 · visibleToA 4 · visibleToB 3 · leakWrittenUnder <>บท​ที่ 7
การ​อ่าน​ที่​เข้ม​ขึ้น แยก​สอง​สถานะ​ของ​ค่า​ตั้ง​ไม่​ออกSTRICTFRESH.strictStatus 500 กับ caughtSqlstate 42704 เทียบ​กับ STRICTAFTERRESET.strictStatus 200 กับ strictCount 0บท​ที่ 7
ราคา​ของ policy predicateมี​นาฬิกา​อยู่​ข้าง​ใน จึง​ตก​นอก​หน่วย​ที่​คอร์ส​นี้​ยอมรับ · ไม่​ได้​วัดไม่มี​บท​ไหน​พิสูจน์
LEAKPROOF และ​การ​รั่ว​ผ่าน​ข้อความ​ผิด​พลาด​ของ​ฟังก์ชัน​ใน WHEREเป็น​ข้อ​จำกัด​ที่​รู้จัก​กัน​ดี​ของ RLS แต่ probe ของ​คอร์ส​นี้​ไม่​ได้​วัด​มัน​สัก​ทรงไม่มี​บท​ไหน​พิสูจน์
RLS บน​ตาราง​แบบ partition และ​การ​สืบทอดไม่​ได้​วัดไม่มี​บท​ไหน​พิสูจน์
การ​ค้น​ชื่อ ตาราง ผ่าน pg_tempprobe วัด​เฉพาะ​การ​ค้น​ชื่อ​ฟังก์ชัน และ​กฎ​ของ​สอง​อย่าง​นี้​ไม่​เหมือน​กันไม่มี​บท​ไหน​พิสูจน์
pg_hba.conf · TLS · การ​จัดการ role ทั้ง​ระบบเป็น​คนละ​คำถาม​กับ​คำถาม​ของ​คอร์ส​นี้ไม่มี​บท​ไหน​พิสูจน์
RLS ของ SQL Server และ​ของ Oracleทุก​ข้อ​อ้าง​ใน​คอร์ส​นี้​ผูก​กับ PostgreSQL 18.6ไม่มี​บท​ไหน​พิสูจน์
การ​เข้า​รหัส​ข้อมูล​ใน​แถวเป็น​เรื่อง​ของ​คอร์ส security ทั่วไปไม่มี​บท​ไหน​พิสูจน์

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

ถ้า role ที่​วัด​เป็น superuser ทุก​ช่อง​ใน​ตาราง​จะ​เป็น​หก​แถว แล้ว​อ่าน​เหมือน​การ​ค้น​พบ

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

เวที A จึง​นับ role ที่​ตัว​เอง​สร้าง​ซึ่ง​เป็น superuser ไว้​เป็น​ค่าที่​วัด​ได้​ตัว​หนึ่ง คือ rolesAreSuperuser เป็น 0 และ​มัน​ตรวจ​ข้อ​นี้​ก่อน​ที่​ทรง​ไหน​จะ​ได้​วัด​สัก​ทรง

อีก​สอง​ค่า​คุม​สิ่ง​ที่​ค้าง​ข้าม​รอบ คือ dbLeftAfterDrop เป็น 0 และ rolesLeftAfterDrop เป็น 0 สถานะ​ที่​ค้าง​อยู่​ทำให้​รอบ​ถัด​ไป​วัด​คนละ​เรื่อง แล้ว​ยัง​พิมพ์​ตัวเลข​ออก​มา​หน้าตา​เหมือน​เดิม

แผนผัง​นี้​ไม่​ได้​เพิ่ม​ข้อ​อ้าง มัน​เรียง​ข้อ​ที่​ตาราง​ข้าง​บน​ถือ​อยู่​แล้ว ตาม​ลำดับ​ที่​คำ​สั่ง​หนึ่ง​เจอ​มัน

flowchart TB
  Q1{"ผู้อ่านเป็น superuser หรือถือธง BYPASSRLS หรือไม่"}
  Q1 -->|"ใช่"| X1["นิพจน์ไม่ถูกเรียกใช้ · บทที่ 1 · superOwnerForceNoGuc 6 และ bypassNoGuc 6"]
  Q1 -->|"ไม่ใช่"| Q2{"ผู้อ่านเป็นเจ้าของตาราง และตารางยังไม่ถูกสั่ง FORCE หรือไม่"}
  Q2 -->|"ใช่"| X2["เจ้าของอ่านได้ทั้งตาราง · บทที่ 1 · ownerEnableNoGuc 6"]
  Q2 -->|"ไม่ใช่"| Q3{"คำสั่งเดินผ่าน view หรือฟังก์ชันหรือไม่"}
  Q3 -->|"ผ่าน view ธรรมดา หรือ SECURITY DEFINER ใต้ NO FORCE"| X3["ทางอ้อมไม่เท่าทางตรง · บทที่ 5 · noForceBase 3 เทียบ noForceViewDefault 6"]
  Q3 -->|"เดินตรงเข้าตาราง"| Q4{"คำสั่งนี้เขียนหรืออ่าน"}
  Q4 -->|"เขียนข้ามผู้เช่า"| X4["policy ของ SELECT เป็นตัวตัดสิน · บทที่ 3 · updMoveUsingOnly 42501"]
  Q4 -->|"อ่าน"| Q5{"app.tenant ถือค่าอะไรอยู่ตอนนี้"}
  Q5 -->|"ไม่เคยตั้ง หรือถูกล้างไปแล้ว"| X5["0 แถว · บทที่ 2 · appNoGuc 0 และ csCountAfterReset 0"]
  Q5 -->|"ผู้เช่าของผู้เรียกเอง"| Q6{"นิพจน์ของ policy เรียกฟังก์ชันที่ไม่ได้ตรึง search_path หรือไม่"}
  Q5 -->|"ค่าที่คำขอก่อนหน้าทิ้งไว้บน connection เดิม"| X6["แถวของผู้เช่าคนก่อน · บทที่ 7 · PLAINTHENNOTENANT b 3 แถว"]
  Q6 -->|"ไม่ได้ตรึง"| X7["ยอดรวมเท่าเดิม แต่เจ้าของแถวสลับ · บทที่ 6 · hijackOwnTotal 3 กับ hijackOwnRowsB 3"]
  Q6 -->|"ตรึงแล้ว"| X8["3 แถวของผู้เช่าตัวเอง · บทที่ 6 · hijackPinnedTotal 3 กับ hijackPinnedRowsB 0"]

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

จุด​ที่​อ่าน​ผิด​กัน​บ่อย​ที่สุด​อยู่​ที่​คำถาม​ข้อ​แรก คน​ที่​ตรวจ​ตาราง​ของ​ตัว​เอง​ว่า​เปิด​ครบ​แล้ว ยัง​ตอบ​คำถาม​ข้อ​นั้น​ไม่​ได้ เพราะ​คำ​ตอบ​อยู่​ที่ role ของ​ผู้​อ่าน ไม่​ได้​อยู่​ที่​ตาราง

สอง​ทรง​ที่​เหลือ​อยู่​ใน​ตาราง เปลี่ยน​ของ​ข้าง​ใน​ไป​แล้ว โดย​จำนวน​แถว​ไม่​ขยับ

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

ทรง​แรก​คือ​ทรง​ของ​บท​ที่ 6 hijackBaseTotal เป็น 3 และ​หลัง​ผู้​โจมตี​วาง schema ของ​ตัว​เอง​ไว้​หน้า search_path แล้ว hijackOwnTotal ก็​ยัง​เป็น 3 หลัง​ตรึง​ค่า​นั้น​ลง​บน​ฟังก์ชัน hijackPinnedTotal ก็​เป็น 3 อีก​ครั้ง

สาม​สถานะ​ให้​เลข​เดียวกัน สิ่ง​ที่​ขยับ​อยู่​ใน​ช่อง​ที่​ยอด​รวม​ไม่​ได้​มอง คือ hijackBaseRowsB เป็น 0 แล้วก​ลาย​เป็น hijackOwnRowsB เป็น 3 และ hijackOwnRowsA เป็น 0

ทรง​ที่​สอง​คือ​ทรง​ของ​บท​ที่ 7 WRITEPLAIN ซึ่ง​เป็น​ทรง​ที่​รั่ว มี total เป็น 8 และ WRITELOCAL กับ WRITERESET ซึ่ง​เป็น​สอง​ทรง​ที่ “แก้​แล้ว” ก็มี total เป็น 8 เท่า​กัน​ทั้ง​สาม​ทรง

ช่อง​ที่​แยก​ทรง​ที่​แก้​แล้ว​ออก​จาก​ทรง​ที่​รั่ว คือ leakWrittenUnder ซึ่ง​เป็น <clinic-a> ใน​ทรง​แรก และ​เป็น <> ใน​สอง​ทรง​หลัง ส่วน​รหัส HTTP กับ​จำนวน​แถว​ที่​ลง​จริง​เท่า​กัน​หมด

ยอด​รวม​ที่​เท่า​เดิม เป็น​ได้​ทั้ง​ความ​ปลอดภัย​และ​การ​รั่ว

WRITELOCAL มี visibleToA เป็น 4 และ visibleToB เป็น 3 ขณะ​ที่ total เป็น 8 สิ่ง​ที่​ผู้​เช่า​สอง​คน​เห็น​รวม​กัน จึง​น้อย​กว่า​จำนวน​แถว​ใน​ตาราง

แถว​ที่​หาย​ไป​ไม่​ได้​ถูกกัน​ไว้​จาก​ใคร​คน​ใด​คน​หนึ่ง มัน​ไม่มี​ผู้​เช่า​คน​ไหน​เห็น​เลย และ​ไม่มี​รหัส​ข้อ​ผิด​พลาด​ใบ​ไหน​บอก​เรื่อง​นี้ ทั้ง WRITEPLAIN และ WRITELOCAL มี aStatus เป็น 200 และ aInserted เป็น 1 เหมือน​กัน

ด่าน​ที่​เฝ้า​เฉพาะ total ผ่าน​ทั้ง​สอง​ทรง​ไป​อย่าง​สะอาด และ​ด่าน​ที่​เฝ้า​เฉพาะ​รหัส HTTP ก็​เช่น​กัน ข้อ​ที่​ทำให้​เห็น​ความ​ต่าง​คือ​การ​นับ​แยก​เป็น​ราย​ผู้​เช่า

ตาราง​ข้าง​บน​ถือ​ของ​สอง​ชนิด และ​การ​สลับ​สอง​ชนิด​นี้​เป็น​ความ​ผิด​พลาด​คนละ​แบบ

ช่อง​กลาง​ของ​ทุก​แถว​เป็น​ค่าที่ SQL หรือ​คำขอ HTTP ตัว​จริง​คืน​กลับ​มา ส่วน​คำ​อธิบาย​ว่า ธง​ใบ​ไหน​ทำ​อะไร หรือ​ทำไม pg_temp ไม่​ถูก​ค้น​สำหรับ​ชื่อ​ฟังก์ชัน มา​จาก​เอกสาร ของ PostgreSQL 18 และ​เอกสาร​เป็น​คนละ​หลัก​ฐาน​กับ probe

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

เวที B ตรึง​ค่า​ของ​ตัว​เอง​ลง src/data/perf/clinic-rls.ts ซึ่ง​เป็น​ของ​ที่​สร้าง​ขึ้น ไม่ใช่​ของ​ที่​พิมพ์​มือ ทรง​ของ pool ทั้ง​สาม​ใบ​ถูก​ตรึง​ไว้​ที่ maximumPoolSize 1 และ minimumIdle 1 ตาม​ที่ POOL บันทึก​ไว้

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

ผล​ทั้งหมด​ใน​บท​นี้​วัด​บน PostgreSQL 18.6 บน aarch64-unknown-linux-musl ใน container ของ​คอร์ส​นี้​เอง ส่วน​เวที B ยก Spring Boot 4.1.1 กับ Kotlin 2.4.10 ขึ้น​จริง major อื่น​เป็น​คนละ​การ​วัด

หน่วย​ของ​หลัก​ฐาน​ใน​คอร์ส​นี้​มี​จำนวน​แถว จำนวน​แถว​ที่​คำ​สั่ง​กระทำ จำนวน policy รหัส SQLSTATE ธง​บู​ลีน​ใน catalog รหัส HTTP ที่​บท​ที่ 7 วัด​และ​บท​นี้​ยก​มา ความ​ยาว​และ​ค่า​ของ​สตริง​ที่ current_setting คืน​มา จำนวน connection ที่ pool ถือ​อยู่​พร้อม​จำนวน backend ที่​คำขอ​วิ่ง​ผ่าน และ​จำ​นว​น​โท​เค็​นที่​ต่าง​กัน​ระหว่าง​สาม​บรรทัด​ที่​ประกาศ pool รวม​เก้า​หน่วย ตาม​ที่​หน้า landing ของ​คอร์ส​ประกาศ​ไว้ ทั้ง​คอร์ส​ไม่มี​หน่วย​เวลา​สัก​ตัว

สัญญา​ใบ​นี้​ใช้ได้​กับ​ตาราง​ของ​คุณ ก็​ต่อ​เมื่อ​คุณ​ตอบ​คำถาม​หก​ข้อ​ใน​แผนผัง​ด้วย​ของ​จริง

ข้อ​แรก​ที่​ควร​ทำ​กับ​ระบบ​ของ​ตัว​เอง ไม่ใช่​การ​เขียน policy ใบ​ใหม่ แต่​เป็นการ​ตอบ​ว่า role ที่​แอป​ใช้​ต่อ​ฐาน​ข้อมูล เป็น superuser หรือ​ถือ​ธง BYPASSRLS อยู่​หรือ​ไม่ สอง​ข้อ​นั้น​ทำให้ policy ทุก​ใบ​ที่​คุณ​เขียน​ไม่​ถูก​เรียก​ใช้

ข้อ​ที่​สอง​คือ​ทาง​เข้า ตาราง​เดียวกัน​มี view และ​ฟังก์ชัน​ชี้​เข้าหา​มัน​กี่​ทาง และ​แต่ละ​ทาง​ถูก​วัด​แล้ว​หรือ​ยัง บท​ที่ 5 วัด​สี่​ทาง และ​ตัวเลข​ของ​สี่​ทาง​นั้น​ไม่​เท่า​กัน จนกว่า​จะ​สั่ง FORCE

ข้อ​ที่​สาม​คือ​อายุ​ของ​ค่า​ตั้ง ถ้า​แอป​ของ​คุณ​ใช้ connection pool คำถาม​ไม่ใช่​ว่า ตั้ง​ค่า​ถูก​หรือ​ไม่ แต่​เป็น​ว่า​ค่า​นั้น​ถูก​ล้าง​เมื่อไร และ​คำขอ​ใบ​ถัด​ไป​เห็น​อะไร

🔁 แต่ละ​ข้อ​ใน​สัญญา ถูก​วัด​ไว้​ที่ไหน
  • บท​ที่ 1 — ธง​ของ​ตาราง ชนิด​ของ role และ​ธง​บน role
  • บท​ที่ 2 — อายุ​ของ​ค่า​ตั้ง และ​สอง​สถานะ​ที่​จำนวน​แถว​แยก​ไม่​ออก
  • บท​ที่ 3 — USING WITH CHECK และ RETURNING บน​ฝั่ง​เขียน
  • บท​ที่ 4 — การ​ปฏิเสธ​ที่​ไม่​ส่ง​เสียง และ​การ​รวม policy หลาย​ใบ
  • บท​ที่ 5 — view และ​ฟังก์ชัน​ที่ policy มอง​ไม่​เห็น
  • บท​ที่ 6 — การ​โจมตี​ที่​ไม่​ขยับ​ยอด​รวม
  • บท​ที่ 7 — connection ที่​พก​ค่า​ตั้ง​ข้าม​คำขอ

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

  • PostgreSQL 18 — CREATE POLICY (เข้า​ถึง 2026-09-06) — กฎ​การ​รวม policy หลาย​ใบ คือ permissive รวม​ด้วย OR และ restrictive รวม​ด้วย AND

  • PostgreSQL 18 — CREATE ROLE (เข้า​ถึง 2026-09-06) — ที่มา​ของ​กฎ​ว่า​ธง BYPASSRLS ผูก​อยู่​กับ role ไม่​ได้​ผูก​กับ​ตาราง

  • PostgreSQL 18 — SET (เข้า​ถึง 2026-09-06) — อายุ​ของ​ค่า​ตั้ง​ใน session และ​ความ​ต่าง​ของ SET LOCAL

  • ค่า​ของ​เวที A ทุก​ตัว​มา​จาก scripts/clinic-rls/spike.py ต้อง​สั่ง scripts/clinic-rls/up.sh ก่อน ซึ่ง​ยก container ของ​คอร์ส​นี้​เอง​ขึ้น​ที่ port 55438 · ค่า​ของ​เวที B ทุก​ตัว​มา​จาก src/data/perf/clinic-rls.ts ซึ่ง scripts/clinic-rls/probe.py สร้าง​ขึ้น​และ​ตรึง​ไว้​ใน repo

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

ข้อ 1 / 3

ด่านที่เฝ้าเฉพาะยอดรวมของแถว ผ่านสองทรงในคอร์สนี้ไปโดยไม่แดง ทรงไหนบ้าง