كتابات

· 3 دقائق قراءة

عزل المستأجرين في Postgres: دروس عملية

في منصة تجارة إلكترونية، أن يرى متجر طلبات متجر آخر هو أسوأ خطأ قد تطلقه. هكذا تجعل قاعدة البيانات نفسها ترفضه، لا التطبيق وحده.

محمود عطيةمؤسس نقرة · طالب طب · مهندس برمجيات
  • الهندسة
  • الأمان
  • SaaS
Read in English

في منصة تجارة إلكترونية متعددة المستأجرين، تعيش بيانات كل المتاجر جنبًا إلى جنب: المنتجات والعملاء والطلبات والإيصالات. وأخطر خطأ قد تطلقه ليس انهيار التطبيق، بل أن يرى تاجر عملاء تاجر آخر.

معظم التطبيقات تحمي نفسها من ذلك بشرط where store_id = ? في كل استعلام. وهذا ينجح حتى اليوم الذي ينسى فيه أحدهم شرطًا واحدًا. نقرة تعزل المتاجر على مستوى قاعدة البيانات أيضًا، فيعيد الشرط المنسي لا شيء بدلًا من كل شيء. وهذه دروس تصلح لأي منصة تعتمد على Postgres.

1. كل صف يملكه مستأجر يحمل هويته

كل جدول فيه بيانات متجر يحتوي store_id غير فارغ ومفهرس، وعادة يكون أول عمود في الفهارس المركبة. والربط بين جداول المستأجرين يتضمنه أيضًا. يبدو هذا تكرارًا، لكنه ما يجعل كل ما بعده ممكنًا.

2. دع Postgres يفرض العزل بأمان الصفوف

أمان مستوى الصفوف (RLS) يربط شرطًا بالجدول نفسه. يضبط التطبيق المستأجر الحالي في بداية كل معاملة، وتتكفل السياسة بالباقي:

alter table orders enable row level security;
alter table orders force row level security;

create policy tenant_isolation on orders
  using (store_id = current_setting('app.store_id')::uuid)
  with check (store_id = current_setting('app.store_id')::uuid);
await db.transaction(async (tx) => {
  await tx.execute(sql`select set_config('app.store_id', ${storeId}, true)`); // true = داخل هذه المعاملة فقط
  return tx.select().from(orders); // طلبات هذا المتجر فقط، حتى بدون شرط where
});

شرط with check مهم بقدر using: فهو يمنع الخطأ البرمجي من كتابة صف في متجر آخر.

3. لا تجعل التطبيق يتصل كمستخدم خارق

المستخدم الخارق ومالك الجدول يتجاوزان RLS ما لم يُفرض. يجب أن يتصل التطبيق بدور لا يملك شيئًا ولا يستطيع تجاوز السياسات، بينما تعمل عمليات الترحيل بدور منفصل له صلاحيات أعلى.

4. افشل مغلقًا

إن غاب إعداد المستأجر، فإن current_setting بلا قيمة افتراضية يرفع خطأ، وهذا ما تريده. الاستعلام الذي يعمل بلا مستأجر يجب أن يفشل بصوت عالٍ، لا أن يعيد البيانات بهدوء.

5. للعمل على مستوى المنصة مسار خاص

بعض المهام تعبر المستأجرين بشكل مشروع: الفوترة والتحليلات وقاطع ميزانية الذكاء الاصطناعي. هذه تعمل بدور مخصص وفي كود منفصل بوضوح، فلا يحدث «العبور بين المستأجرين» صدفة داخل معالج طلب.

6. اختبر العزل كأنه ميزة

اكتب اختبارات تنشئ متجرين، وتضبط السياق على أحدهما، ثم تحاول القراءة من الآخر والتعديل عليه والإضافة إليه. يجب أن تفشل كلها في كل مرة. وشغّلها مع كل تغيير في المخطط، لأن جدولًا جديدًا بلا سياسة ثغرة.

7. أسرار لكل مستأجر

العزل ليس صفوفًا فقط. مفاتيح الربط لكل متجر، مع بوابات الدفع أو شركات الشحن، مشفّرة في خزنة خاصة بذلك المتجر. فنسخة احتياطية مسرّبة من قاعدة البيانات لا يجب أن تسرّب مفاتيح صالحة.

8. البيانات ملك التاجر

للعزل وجه ودود أيضًا: لأن كل ما يملكه المتجر محدد بوضوح، يصبح تصديره سهلًا. في نقرة يستطيع التاجر تصدير بياناته في أي وقت. والوفاء بهذا الوعد أسهل كثيرًا حين تعرف قاعدة البيانات بالفعل أين ينتهي متجر ويبدأ آخر.

العزل من تلك المزايا التي لا يلاحظها أحد حين تعمل. ولهذا تحديدًا مكانه قاعدة البيانات، لا ذاكرة كل مطوّر.

↑↓ تنقّل↵ فتحesc إغلاق
mahmoud@attia: ~