PRODمنصة BaaS الأوروبية السياديةافتح لوحة المعلومات →

الهندسة · 10 دقيقة للقراءة

RLS مقابل قاعدة بيانات مخصصة لكل مشروع

Affane Daylami · Fondateur · 27 يوليو 2026

العودة إلى بلوق

ابحث عن "RLS multi-tenant postgres" وستجد نفس المخطط في كل مكان تقريبًا: قاعدة بيانات مشتركة، وtenant_idcolumn، وسياسة تقوم بتصفية الصفوف. ليس هذا هو النموذج الذي تستخدمه Aurabase لعزل مشاريعها عن بعضها البعض. يتلقى كل مشروع قاعدة بيانات Postgres 16 الخاصة به، ولا تتم مشاركتها مطلقًا مع عميل آخر - يظل RLS موجودًا، ولكن في طابق آخر: في قاعدة بياناتك، لمستخدميك.

تم إنشاء هذا النص الإنجليزي تلقائيًا من النص الأصلي الفرنسي ولم تتم مراجعته بعد.
تمت ترجمة هذه الصفحة تلقائيًا. النسخة الإنجليزية موثوقة.

تم العثور على "RLS" و"متعدد المستأجرين" جنبًا إلى جنب في كل المحتوى المنشور بالفعل حول هذا الموضوع تقريبًا - وهو خيار مشروع للعديد من بنيات SaaS، ولكنه ليس خيار Aurabase لفصل عملائها عن بعضهم البعض. يشرح هذا المنشور الفرق بين محرك التزويد الحقيقي وسياسات RLS المطبقة فعليًا، وليس وصفًا تسويقيًا مبسطًا. للحصول على نظرة عامة حول ما يغطيه محرك Postgres المُدار الخاص بـ Aurabase بما يتجاوز العزل، راجع وثائق قاعدة بيانات .

الأساسيات

  • بين المشروعات، يتم عزل Aurabase بواسطة قاعدة Postgres المخصصة، ولا يتم عزله أبدًا بواسطة RLS وحده - كل مشروع له قاعدته المادية الخاصة، على مجموعة CNPG مخصصة (على مستوى الشركة) أو على مجموعة CNPG الخاصة بمؤسسته الخاصة (مجاني/محترف/فريق)، ولا تتم مشاركتها مطلقًا مع مؤسسة أخرى.
  • يظل RLS (auth.uid(), auth.role(), auth.jwt()) نشطًا وموصى به داخل قاعدة الخاصة بك، لعزل المستخدمين لديك - نفس التقليد المتبع في Supabase.
  • service_role وأدوار الإدارة القائمة على المشروع تتجاوز RLS حسب التصميم (BYPASSRLS): اختيار معماري مفترض لعمليات الخادم، وليس عيبًا.
  • الانحدار الذي تم تصحيحه بالفعل في هذا المستودع - حقوق PUBLIC الممنوحة عن طريق الخطأ في المخططات المشتركة القديمة - يوضح بشكل ملموس سبب مقاومة الحدود عند المستوى الأساسي بشكل أفضل من حدود التطبيق البحتة.
#
النموذج الافتراضي

الاختصار الذي تستخدمه معظم أدلة RLS متعددة المستأجرين

يتكون النمط الأكثر توثيقًا لـ Postgres متعدد المستأجرين من ثلاثة أسطر: قاعدة واحدة، وعمود tenant_id في كل جدول، وسياسة RLS التي تقارن هذا العمود بقيمة مستخرجة من JWT. إنه اقتصادي - مجموعة من الاتصالات، ورسم تخطيطي، ومثيل واحد للتشغيل - ويعمل بشكل جيد عندما يكون المستأجرون كثرًا، وصغارًا، وذوي حصة فردية منخفضة.

الحل الوسط حقيقي: تصبح الحدود بين العميلين تعبير SQL، ويتم تقييمه جدولاً بجدول. سياسة منسية على جدول جديد، اتصال يعمل مع دور مستخدم متميز، إطلاق برنامج نصي لتصحيح الأخطاء مباشرة - كل من هذه الحوادث، مهما كانت تافهة في التشغيل، يمكن أن تكشف بصمت خطوط جميع المستأجرين في نفس الوقت. الحدود الأمنية والحدود الفنية (الأساس) هما نفس الشيء تمامًا.

معلومات

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

#
في الكود

بنيتان، لا توجد قاعدة مشتركة بين المشاريع

منذ الدمج الأخير للموفر (تم وضع علامة عليه في الكود باسم "المهمة 12")، يندرج مشروع Postgres النشط في Aurabase تحت بنيتين بالضبط - تمت إزالة النماذج القديمة ذات القاعدة المشتركة فعليًا بين عدة مشاريع من مسار التزويد.

provisioning/instances.rsrust
pub enum ProjectInstanceKind {
    /// مجموعة CNPG مخصصة لهذا المشروع فقط (على مستوى الشركة).
    FullyDedicated,

    /// قاعدة بيانات عن مجموعة CNPG الخاصة بمنظمة المشروع —
    /// مشتركة مع مشاريع أخرى للمنظمة نفسها،
    /// أبدًا مع منظمة خارجية.
    SharedClusterDedicated,
}

يقرر مستوى المشروع أيًا من الاثنين ينطبق - والكود هو الذي يقرر، وليس مربعًا محددًا في لوحة المعلومات:

provisioning/rules.rsrust
fn should_provision_dedicated(engine: &str, db_url_encrypted: &Option<String>, plan: &str) -> bool {
    matches!(engine, "postgres" | "postgresql")
        && plan == "enterprise"
        && !non_empty(db_url_encrypted)
}

// ينبغي_route_to_fleet(): أي فئة غير تابعة للشركة (مجاني/محترف/فريق)
// الطريق إلى مجموعة CNPG الخاصة بمؤسستك الخاصة، وليس المجموعة أبدًا
// من الآخر - راجع الترحيل 073، الدمج في معماريتين.
الأبعادمخصص بالكامل (الشركة)SharedClusterDedicated (مجاني/محترف/فريق)
مجموعة CNPGمخصصة لهذا المشروع واحدمشتركة، ولكن ليس بين منظمتين أبدًا
قاعدة بيانات بوستجرسالتطبيق، المشروع عليه فقطproject_<uuid>، واحد لكل مشروع في المجموعة
تسجيل دخول PostgreSQLمشروع واحد في المجموعة: لا يوجد خطر للعضوية المشتركةتسجيل الدخول لكل مشروع (F-013)، عضو في أدواره الوحيدة المستأجر_<uuid>

في كلا البنيتين، لا تستضيف القاعدة أو المجموعة أبدًا مؤسستين مختلفتين - لذا فإن السؤال ليس "هل بياناتك معزولة" ولكن "هل قام مشروعك بحساب CloudNativePG لنفسه، أم أنه يشاركها مع مشاريع أخرى في نفس المؤسسة."

يعد هذا الدمج بين معماريتين حديثًا: كان الكود يحمل في السابق مسارين إضافيين - "مسار رئيسي مشترك" حيث تتعايش عدة مشاريع في نفس قاعدة البيانات، معزولة فقط عن طريق الرسم التخطيطي، ومتغير postgrest_dedicated_shared_db. أزالتها عملية ترحيل مخصصة وشددت قيود الجدول projects على القيمتين المتبقيتين فقط، وذلك على وجه التحديد لأن نموذج المخطط المشترك كان مصدر الخطأ الموضح أدناه.

#
الحدود الأمنية

لماذا تتفوق القاعدة المخصصة على EPIRB المشترك بين العملاء

قاعدة بيانات Postgres المنفصلة هي حد مستوى الاتصال، وليس حد مستوى الصف. لا يمكن لدور التطبيق المتصل بقاعدة بيانات المشروع "أ" الاستعلام عن جداول المشروع "ب" ببساطة - فهو لا يحتوي على جلسة مفتوحة له. تظل هذه الخاصية ثابتة حتى إذا كانت سياسة RLS مكتوبة بشكل سيئ، أو مفقودة من جدول، أو تم تجاوزها بواسطة دور عالي: تظل الحالة الأسوأ محصورة داخل قاعدة بيانات واحدة.

تحمل هذه الوديعة أيضًا أثر خطأ حقيقي يوضح الخطر المعاكس. ضمن نموذج المخطط المشترك القديم (الذي تم سحبه منذ ذلك الحين)، منح provision_postgres_schema عن طريق الخطأ GRANT ALL ... TO PUBLIC حقوقًا لكل مخطط مشروع - PUBLIC ينطبق على جميع أدوار في قاعدة البيانات دون شروط العضوية، ويمكن لتسجيل الدخول المعزول لكل مشروع القراءة والكتابة في مخطط آخر. أدى الترحيل التصحيحي (066) إلى إزالة هذه الحقوق من الحقوق الموجودة.

الدرس المستفاد

لم يضيف التصحيح سياسة RLS أخرى لسد التسرب، بل أزال إمكانية مشاركة مشروعين في قاعدة بيانات. فيما يتعلق بالبنيتين الحاليتين، يوثق تعليق من provisioning.rs الأمر باللونين الأبيض والأسود: "كل مشروع لديه بالفعل قاعدة بيانات Postgres الفعلية الخاصة به". إن حدود المستوى الأساسي تجعل فئة كاملة من هذه الأخطاء غير قابلة للوصول ببساطة، بدلاً من الاعتماد على كل سياسة مكتوبة دائمًا بشكل صحيح.

تصحيح من 23 أغسطس 2026 يسير في نفس الاتجاه: REVOKE ALL ON SCHEMA public الذي تم وضعه دون قيد أو شرط من قبل الموفر أصبح مشروطًا بالطوبولوجيا، لأنه يوفر فقط عزلًا حقيقيًا لنموذج قاعدة البيانات المشتركة القديمة - في البنيتين الحاليتين، قام بحظر استيراد عمليات تفريغ SQL التي تشير بشكل صريح إلى public.<table>دون فائدة.

#
RLS في الممارسة العملية

يبقى EPIRB هناك — في قاعدة البيانات الخاصة بك، لمستخدميك

لا شيء مما سبق يجعل EPIRB عديم الفائدة - فهو يغير الأرضيات فقط. بمجرد الدخول إلى قاعدة بيانات مشروع، يكشف Aurabase بالضبط عن اتفاقية PostgREST التي تم تناولها بواسطة Supabase: ثلاث وظائف SQL التي تقرأ مطالبات JWT التي تم تعيينها بواسطة البوابة في request.jwt.claims.

libs/aura-migrations/golden/auth_global.sqlsql
create function auth.uid() returns uuid
    language sql stable security definer
    set search_path to 'pg_catalog', 'pg_temp'
    as $$
    select nullif(nullif(current_setting('request.jwt.claims', true), '')::json->>'sub', '')::uuid
$$;

يتم استخدام هؤلاء المساعدين في السياسات الفعلية لـ Aurabase نفسها - وليس فقط توثيقها لسياساتك. هذه هي السياسة التي تحمي storage_objectsحيث يتم وضعها في المستودع (أعيد تنسيقها على عدة أسطر للقراءة):

libs/aura-migrations/golden/storage.sqlsql
alter table storage_objects enable row level security;

create policy storage_objects_select on storage_objects
  for select using (
    (owner_id = auth.uid())
    or (
      exists (
        select 1 from storage_buckets b
        where (b.name = storage_objects.bucket_name and b.public)
      )
    )
  );

في مخطط project_<uuid>الخاص بك، وهو المخطط الذي يحمل جداول التطبيق الخاصة بك، لا تضع Aurabase أي سياسات نيابةً عنك عمدًا - حيث يوثقها الكود على أنه "نموذج Supabase": يظل RLS الخاص بجداولك مسؤوليتك، مع نفس الوظائف، ونفس بناء الجملة.

#
يفترض BYPASRLS

يتجاوز Service_role RLS — حسب التصميم، وليس من قبيل الصدفة

يقدم Postgres في الأصل سمة الدور , BYPASSRLSوالتي تتجاهل جميع السياسات. تستخدمه Aurabase طوعًا في مجموعتين من الأدوار: aura_service_role (دور الخادم، الذي لا يتم عرضه أبدًا من جانب المتصفح) ودور الإدارة الخاص بكل مشروع، والذي يتم استخدامه أثناء عمليات DDL مثل ALTER SCHEMA ... OWNER TO.

provisioning/roles.sqlsql
create role aura_service_role nologin bypassrls;
-- دور الخادم: لم يتم كشفه مطلقًا من جانب المتصفح، ولم يكن عضوًا أبدًا
-- أدوار من مشروع آخر.

الدور الذي يخدم طلباتك anon/authenticated — tenant_<uuid> — ليس لديه أي BYPASSRLS: ينطبق عليه RLS بشكل طبيعي، دون استثناء. كمكافأة، تتلقى مخططات النظام الخاصة بكل مشروع (_auth, _storage, _platform) RLS نشط بدون سياسة - وبالتالي رفض تام افتراضيًا لأي دور غير تجاوز، وهو دفاع متعمق في حالة وصول مسار التطبيق إليه يومًا ما عن طريق الخطأ.

معلومات

إن تجاوز RLS بدور خادم مرتفع ليس أمرًا فريدًا بالنسبة لـ Aurabase - فهو نفس البناء مثل service_role على جانب Supabase. الهدف ليس تجنب BYPASSRLS، بل عدم منحه مطلقًا لدور يمكن للعميل الوصول إليه، وقصره على مشروع واحد.

يعد دور الخادم هذا جزءًا من وضع أوسع - الأدوار المحددة مسبقًا، وRBAC المخصص، وسجلات التدقيق - المفصلة في صفحة Security & RBAC.

#
الدفاع في العمق

في المجموعة المشتركة، لا تقوم قاعدة البيانات بكل العمل بمفردها

على مستوى SharedClusterDedicated، تتواجد العديد من المشاريع من نفس المؤسسة في مجموعة CNPG واحدة. تفصل قاعدة البيانات الفعلية المشاريع عن بعضها البعض بالفعل، لكن أدوار PostgreSQL — هي — هي كائنات عامة للمجموعة، وليس لقاعدة البيانات. لذلك تضيف Aurabase طبقة: تسجيل دخول منفصل لـ PostgreSQL لكل مشروع.

libs/aura-core/src/lib.rsrust
pub fn project_authenticator_role(project_id: &str) -> String {
    format!("project_{}_authenticator", project_id.replace('-', '_'))
}

// نشط بشكل افتراضي (F013_PER_PROJECT_AUTHENTICATOR)، قابل للإلغاء
// صراحة باعتباره الهروب في حالات الطوارئ.

يتصل كل مشروع بتسجيل الدخول الخاص به، عضوًا فقط في أدوار tenant_<uuid> / tenant_<uuid>_admin الخاصة به - وليس أبدًا أدوار مشروع آخر في نفس المجموعة. قاعدة البيانات تعزل البيانات بالفعل؛ يؤدي تسجيل الدخول هذا لكل مشروع أيضًا إلى عزل الهوية التي تتصل به، بحيث لا يمنح أي حادث في المشروع تسجيل الدخول الخاص به أي عضوية ليرثها إلى شخص آخر.

#
من أجل الهندسة المعمارية الخاصة بك

RLS وحدها أو قاعدة مخصصة: كيفية اتخاذ قرار بشأن SaaS الخاص بك

إن اختيار Aurabase ليس قاعدة عالمية - بل هو حل وسط لحالة معينة: عزل العملاء عن بعضهم البعض، ربما بمتطلبات امتثال مختلفة، على منصة لا يسيطرون عليها. إذا كنت تقوم ببناء SaaS الخاص بك، فإن نفس السؤال سيطرح عليك، ولكن على نطاق مختلف.

  • RLS مع tenant_id في قاعدة مشتركة — تكون ذات صلة عندما يكون المستأجرون لديك كثيرين، وذوي حصص فردية منخفضة، وتكون تكلفة القاعدة لكل مستأجر غير متناسبة. اختبر كل سياسة باستخدام pg_proveعلى كل جدول، دون استثناء.
  • قاعدة أو رسم تخطيطي مخصص — يكون مناسبًا عندما يكون لدى المستأجر مشكلة امتثال خاصة به (الصحة، والموارد البشرية، والقطاع العام)، أو حجم يبرر عزل الأداء، أو أن تكلفة التسرب بين عميلين محددين ستكون غير متناسبة مع تكلفة البنية التحتية الإضافية.

يطبق مستوى تسعير Aurabase هذا التحكيم نفسه على عملائه: قاعدة مشتركة حسب المؤسسة بشكل افتراضي، ومجموعة مخصصة عندما يبرر تحدي المشروع ذلك. بالنسبة لأنماط RLS داخل قاعدة البيانات الخاصة بك - الملكية، والمستأجرين المتعددين حسب المؤسسة، والتسلسل الهرمي للأدوار - يقدم دليل RLS في الإنتاج تفاصيل الحالات الثلاث مع اختبارات pgTAP. وإذا كانت واجهة برمجة التطبيقات (API) التي تم إنشاؤها تلقائيًا في الرسم التخطيطي الخاص بك تهمك بما يتجاوز REST، فإن المقارنة على pg_graphql مقابل Hasura وPostGraphile تغطي النصف الآخر من سطح Postgres المكشوف بواسطة Aurabase.

#
الأسئلة المتداولة

الأسئلة الشائعة

هل يكفي RLS وحده لعزل المستأجرين في قاعدة بيانات Postgres واحدة؟+
من الناحية الفنية، نعم، إذا كان كل جدول يحمل سياسة صحيحة ولم يفلت أي اتصال من دور عدم التجاوز. هذا هو بالضبط الخطر التشغيلي الذي اختارت Aurabase عدم تحمله بين المشاريع المختلفة - فكل خطأ في السياسة سيظل محصوراً في قاعدة واحدة. ضمن مشروع واحد، بالنسبة للمستأجرين لديك، يظل RLS + Tenant_id خيارًا مشروعًا ومستخدمًا على نطاق واسع.
لماذا لا تحتوي المشاريع المجانية على مجموعة Postgres مخصصة مثل طبقة المؤسسة؟+
من شأن مجموعة CloudNativePG المخصصة لكل مشروع، بما في ذلك المشاريع المجانية، أن تضاعف الحوسبة المحجوزة دون أي علاقة بالاستخدام الفعلي لغالبيتها. بدلاً من ذلك، تمنح Aurabase كل مشروع غير مؤسسي قاعدة بيانات Postgres الفعلية الخاصة به على مجموعة CNPG الخاصة بمؤسسته - لم تتم مشاركتها مطلقًا مع مؤسسة أخرى - بدلاً من مخطط في قاعدة بيانات مشتركة بين عدة عملاء غير معروفين لبعضهم البعض.
كيف يعرف auth.uid() من أنا، من الناحية الفنية؟+
تتحقق بوابة Aurabase من JWT الخاص بك ثم تضع مطالباتها في معلمة جلسة Postgres request.jwt.claims طوال مدة المعاملة. لا يقوم auth.uid() بأي شيء أكثر من استخراج الحقل الفرعي من JSON هذا وإرساله إلى uuid - بدون تقديم مطالبات (طلب مجهول، أو اتصال مباشر خارج البوابة)، يقوم current_setting() بإرجاع NULL وبالتالي يقوم auth.uid() بإرجاع NULL، مما يغلق الوصول إلى السياسة باستخدام (owner_id = auth.uid()).

هل أنت جاهز للنشر؟

الواجهة الخلفية الخاصة بك في خمس دقائق.

لا حاجة لبطاقة ائتمان · 500 ميجابايت مجانًا · 50000 MAU