انتقل إلى المحتوى الرئيسي
العودة إلى الرؤى
البنية9 دقائق قراءة

عزل المستأجرين المتعددين: لماذا لا يكفي أمان مستوى الصف

أربع طبقات من عزل المستأجرين من بوابة API إلى قاعدة البيانات. دفاع متعدد الطبقات للبنية التحتية المالية.

عزل متعدد المستأجرين في الأنظمة المالية: أمان مستوى الصف ليس كافيًا


مطور ينسى عبارة WHERE. يولّد ORM استعلامًا غير محدد النطاق. نقطة نهاية إدارية تتجاوز مرشح المستأجر لـ "الراحة التشغيلية." خطأ واحد، استعلام واحد، والمستأجر A يرى تاريخ معاملات المستأجر B.

في منتج SaaS، هذا خرق بيانات. في نظام مالي، هو أيضًا انتهاك تنظيمي. يتطلب PSD2 حماية بيانات الدفع. يفرض GDPR غرامات لكل فرد متأثر. يتطلب DORA أن تمنع أنظمة تكنولوجيا المعلومات الوصول غير المصرح به إلى البيانات المالية. تسريب بيانات واحد عبر المستأجرين يُطلق الثلاثة.

من المفترض أن يمنع أمان مستوى الصف (RLS) هذا. قاعدة البيانات تفرض حدود المستأجرين بغض النظر عما يفعله كود التطبيق. لكن RLS هو خط الدفاع الأخير، وليس الوحيد. يجيب على "هل يمكن لجلسة قاعدة البيانات هذه الوصول إلى هذا الصف؟"، لكن شخصًا ما يجب أن يضبط سياق الجلسة بشكل صحيح. شخص ما يجب أن يضمن أن معرف المستأجر المتدفق عبر النظام أصيل، وليس مقدمًا من المتصل.

عزل متعدد المستأجرين في الأنظمة المالية يتطلب ثلاث طبقات. كل طبقة تلتقط أعطالًا تفوتها الأخرى.

الطبقة 1: البوابة

بوابة API تقع بين الإنترنت العام والتطبيق. مهمتها: مصادقة المتصل، تحديد المستأجر الذي ينتمي إليه، وحقن تلك الهوية كرأس موثوق.

الخاصية الحرجة: التطبيق لا يقرأ أبدًا هوية المستأجر من جسم طلب المتصل أو معلمات الاستعلام أو مطالبات JWT التي يتحكم فيها المتصل. تتحقق البوابة من رمز OAuth2 (أو مفتاح API)، تحل المستأجر المرتبط، وتضبط رأس HTTP، X-Tenant-ID، على الطلب الداخلي. التطبيق يقرأ الرأس. المتصل لا يمكنه تزويره.

هذا يمنع فئة من الهجمات لا يمكن لتصفية مستوى التطبيق منعها: متصل مخترق أو خبيث يرسل رمز مصادقة صالحًا لكنه يتلاعب بسياق المستأجر. إذا قرأ التطبيق tenant_id من جسم الطلب، يمكن لمتصل بأوراق اعتماد صالحة الادعاء بأنه أي مستأجر. إذا قرأ التطبيق X-Tenant-ID من رأس محقون بالبوابة، يتم التحقق من الادعاء قبل أن يصل الطلب إلى كود التطبيق.

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

الطبقة 2: التطبيق

كل طريقة خدمة تستقبل سياق المستأجر من رأس البوابة. السياق غير قابل للتغيير طوال مدة الطلب. الخدمة لا يمكنها بناء استعلام لمستأجر مختلف، ليس لأنه ممنوع بالاتفاقية، بل لأن API لا يقبل معرف المستأجر كمعلمة. يقرأه من الرأس المحقون.

هذا يلغي مشكلة عبارة WHERE. التطبيق لا يضيف WHERE tenant_id = ? لكل استعلام يدويًا. سياق المستأجر يُضبط على اتصال قاعدة البيانات في بداية الطلب، و RLS (الطبقة 3) يفرضه بشفافية.

لكن طبقة التطبيق تضيف شيئًا لا تستطيع قاعدة البيانات فعله: التحقق محدد النطاق بالطلب. قبل أي عملية كتابة، تتحقق الخدمة من أن الموارد المعدلة تنتمي للمستأجر الحالي. تحويل من الحساب A إلى الحساب B؟ كلا الحسابين يجب أن ينتميا للمستأجر الطالب. تحديث عميل؟ العميل يجب أن ينتمي للمستأجر الطالب. هذه الفحوصات تحدث في كود التطبيق، قبل لمس قاعدة البيانات.

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

الطبقة 3: قاعدة البيانات

سياسات أمان مستوى الصف في PostgreSQL على كل جدول يحتوي بيانات مستأجر. السياسة:

CREATE POLICY tenant_isolation ON finance_transfer
  USING (tenant_id = current_setting('app.current_tenant_id')::uuid);

التطبيق يضبط app.current_tenant_id في بداية كل اتصال قاعدة بيانات (من الرأس المحقون بالبوابة). كل استعلام يُفلتر تلقائيًا. نسيان عبارة WHERE غير ذي صلة، قاعدة البيانات لن ترجع صفوفًا من مستأجرين آخرين بغض النظر عن الاستعلام.

التفصيل الرئيسي: دور قاعدة البيانات المستخدم بالتطبيق لا يمكنه تجاوز RLS. فقط دور الترحيل (المستخدم لتغييرات المخطط، وليس أبدًا لاستعلامات التطبيق) لديه إذن BYPASSRLS. إذا كان دور التطبيق يمكنه تجاوز RLS، فإن خطأ SET ROLE واحد أو حقن SQL سيهزم نموذج العزل بالكامل.

-- Application role: RLS enforced
CREATE ROLE finance_api NOINHERIT NOBYPASSRLS;

-- Migration role: RLS bypassed (schema changes only, never used by application)
CREATE ROLE finance_admin BYPASSRLS;

الطبقة 4: دفتر الحسابات

الأنظمة المالية لديها اهتمام لا تملكه منصات SaaS العامة: دفتر الحسابات يجب أن يفرض عزل المستأجرين بشكل مستقل عن قاعدة البيانات العلائقية.

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

هذا ليس تكرارًا. إنه دفاع متعدد الطبقات. فكر في وضع الفشل: خطأ في طبقة التطبيق يبني تحويلًا حيث حساب المدين ينتمي للمستأجر A وحساب الدائن ينتمي للمستأجر B. فحص مستوى التطبيق (الطبقة 2) يجب أن يلتقط هذا. لكن إذا لم يفعل، تحقق مفقود، حالة سباق، مسار كود أضافه مطور جديد لم يعرف عن الفحص، يرفض محرك دفتر الحسابات التحويل. السجل المالي لا يُفسد أبدًا.

ApplicationBug: transfer(debit=TenantA:acct1, credit=TenantB:acct2)
Layer 2Application check: should catchMissed (bug)
Layer 3Database RLS: both accounts visible to TenantA?Blocked
Layer 4Ledger engine: accounts have different tenant encodingRejected

شبكتا أمان مستقلتان خلف التطبيق. أي منهما كافية لمنع الفساد. كلتاهما معًا تجعلانه مستحيلًا هيكليًا.

نشر سير العمل

سير العمل متعدد الخطوات يمتد عبر خدمات متعددة. إنشاء الحساب يستدعي خدمة التمويل، ثم خدمة IBAN، ثم خدمة KYC. كل استدعاء يجب أن يحمل سياق المستأجر الصحيح.

محرك التنفيذ المتين ينشر معرف المستأجر في كل استدعاء سير عمل. عندما يستدعي سير العمل خدمة التمويل، يحقن X-Tenant-ID في رأس طلب HTTP. خدمة التمويل تتحقق منه مقابل القيمة المحقونة بالبوابة. إذا استدعت خطوة سير عمل مزودًا خارجيًا (KYC، فحص AML)، يُستخدم سياق المستأجر لاختيار تكوين المزود الصحيح، دون كشف معرف المستأجر للنظام الخارجي.

إذا لم ينشر محرك سير العمل سياق المستأجر، قد تُنفَّذ الاستدعاءات عبر الخدمات في نطاق المستأجر الخاطئ. خطأ شائع، وليس افتراضيًا، في الأنظمة متعددة المستأجرين التي تضيف تنسيق سير العمل كفكرة لاحقة.

خمسة أسئلة لتقييم عزلك

إجابات ثنائية. لا درجات جزئية.

1. هل يمكن للمتصل تعيين هوية المستأجر الخاصة به؟ إذا كان التطبيق يقرأ tenant_id من جسم الطلب أو مطالبة JWT يتحكم فيها المتصل: نعم. طبقة البوابة مفقودة أو غير مكتملة.

2. هل يمكن لكود التطبيق بناء استعلام لمستأجر مختلف؟ إذا كان أي مسار كود يقبل tenant_id كمعلمة دالة بدلًا من قراءته من سياق الطلب: نعم. طبقة التطبيق بها فجوة.

3. هل لدور قاعدة البيانات أذونات لتجاوز RLS؟ إذا كان التطبيق يتصل بدور لديه BYPASSRLS أو SUPERUSER أو يملك الجداول: نعم. طبقة قاعدة البيانات معطلة. RLS مفروض لكنه بلا معنى.

4. هل يمكن لتحويل نقل أموال بين حسابات مستأجرين مختلفين؟ إذا كان دفتر الحسابات لا يتحقق بشكل مستقل من ملكية المستأجر لكلا الحسابين في التحويل: نعم. طبقة دفتر الحسابات مفقودة.

5. هل ينشر محرك سير العمل سياق المستأجر عبر حدود الخدمات؟ إذا كانت الاستدعاءات عبر الخدمات ضمن سير العمل لا تحمل سياق المستأجر: لا. عملياتك متعددة الخطوات قد تسرب النطاق.

كل "نعم" للأسئلة 1-4 و"لا" للسؤال 5 هي فجوة. في نظام مالي، كل فجوة هي نتيجة تنظيمية محتملة.


اقرأ المزيد: دفتر الحسابات | الأمان والامتثال


المصادر:

  • DORA، اللائحة (EU) 2022/2554، المادة 9 (الحماية والوقاية من الوصول غير المصرح به)
  • PSD2، التوجيه 2015/2366، المادة 94 (حماية البيانات الشخصية)
  • GDPR، اللائحة 2016/679، المادة 32 (أمن المعالجة)
  • توثيق PostgreSQL: سياسات أمان الصف (https://www.postgresql.org/docs/current/ddl-rowsecurity.html)