في كثير من كراسات الشروط ووثائق المتطلبات الأمنية سوف تجد جملة مثل «يجب أن يكون النظام مبنياً على معمارية ثلاثية الطبقات Three-tier Architecture»، وعندما يسأل المدقق فريق التطوير عن ذلك تأتي إجابات مختلفة تماماً: فريق يقول إن لديه واجهة أمامية Frontend وخلفية Backend وقاعدة بيانات Database فالنظام ثلاثي الطبقات، وفريق آخر يقول إن لديه سيرفرين أحدهما للتطبيق والآخر لقاعدة البيانات، وفريق ثالث يقول إن تطبيقه مكتوب بنمط MVC وهو ثلاثة أجزاء أصلاً، وقد تكون الإجابات الثلاث خاطئة في نظر المدقق مع أن كل فريق صادق فيما يصفه، والسبب أن كلمة «طبقة» في العربية تستخدم لشيئين مختلفين في الإنجليزية: الطبقة Layer التي تصف ترتيب الكود، والمستوى Tier الذي يصف أين يعمل كل جزء وما الذي يفصله عن جاره في الشبكة.
وهذا الخلط ليس مسألة مصطلحات فقط، لأن الضوابط الأساسية للأمن السيبراني ECC التي شرحناها في الضوابط الأساسية للأمن السيبراني NCA ECC بلغة بسيطة تطلب صراحة «استخدام مبدأ المعمارية متعددة المستويات Multi-tier Architecture» لتطبيقات الويب الخارجية، وضوابط الأنظمة الحساسة CSCC تطلب ألا يقل عدد المستويات عن ثلاثة، وفي دليل ضوابط أمن التطبيقات وضعنا لها الضابط ARC-ASC-02-03، وكل هذا يحتاج فهماً واضحاً لما يعنيه المستوى قبل أن تبني النظام أو تكتب الرد على المدقق.
وفي هذا المقال سوف نستخدم كلمة «الطبقة» لمصطلح Layer وكلمة «المستوى» لمصطلح Tier، وهي نفس الكلمة التي تستخدمها النسخة العربية من ضوابط الهيئة الوطنية للأمن السيبراني، أما العنوان فيستخدم «متعددة الطبقات» لأنها العبارة التي يبحث بها أغلب الناس. وسوف نناقش في هذا المقال ما يلي:
- الفرق بين الطبقة Layer والمستوى Tier بتعريفات من مصادر معروفة.
- كيف تطور توزيع التطبيقات من الحاسوب المركزي Mainframe إلى Client-Server ثم المعمارية ثلاثية المستويات، ولماذا تغير كل شكل.
- هل تطبيق MVC القديم الذي يبني الصفحات في السيرفر ثلاثي المستويات؟ وماذا عن تطبيقات SPA مع API والخدمات المصغرة Microservices؟
- جدول يفك أشهر صور الخلط: Frontend وBackend، وسيرفران، وContainers على سيرفر واحد.
- ما الذي تطلبه ECC وCSCC بالنص، وكيف يبدو ذلك في الشبكة، وكيف تثبته للمدقق.
- ثمن الفصل، ومتى يكفي سيرفر واحد ومتى تحتاج سيرفرات منفصلة.
- تجربة عملية بثلاث شبكات Docker نرى فيها أن ال Reverse Proxy لا يصل إلى قاعدة البيانات، ثم نكسر التصميم عمداً ونرى الفرق.
الفرق بين الطبقة Layer والمستوى Tier
الطبقة Layer تقسيم منطقي Logical للكود حسب المسؤولية داخل برنامج واحد، والتقسيم الأشهر ثلاث طبقات: طبقة العرض Presentation Layer التي تستقبل الطلب وتعرض النتيجة، وطبقة منطق العمل Business Logic Layer التي فيها قواعد النظام، وطبقة الوصول إلى البيانات Data Access Layer التي تكلم قاعدة البيانات. ويصف Martin Fowler هذا التقسيم في مقاله PresentationDomainDataLayering بأنه من أشهر طرق تقسيم البرامج إلى ثلاث طبقات واسعة: العرض ومنطق المجال والوصول إلى البيانات، ثم يقول بوضوح إن هذه «طبقات منطقية وليست مستويات فعلية»، وإنه يستطيع تشغيل الطبقات الثلاث على حاسوبه المحمول، أو تشغيل العرض والمنطق على جهاز مكتبي وقاعدة البيانات على سيرفر.
أما المستوى Tier فهو فصل فعلي Physical في التشغيل، أي أن كل مستوى يعمل في عملية Process أو جهاز مستقل، وبينه وبين جاره اتصال عبر الشبكة يمكن التحكم فيه بجدار ناري Firewall. وتقول Microsoft في صفحة N-tier architecture style إن المعمارية متعددة المستويات تقسم التطبيق إلى طبقات منطقية ومستويات فعلية، وإن الطبقات تفصل المسؤوليات وتدير الاعتماديات، أما المستويات فهي «منفصلة فعلياً وتعمل على أجهزة منفصلة»، وإنك قد تضع كل طبقة في مستواها، لكن هذا ليس شرطاً، فيمكن أن تجمع عدة طبقات في مستوى واحد. وتذكر IBM في شرحها Three-tier architecture نفس الفرق: الطبقة تقسيم وظيفي داخل البرنامج، والمستوى تقسيم وظيفي يعمل على بنية تحتية منفصلة.
والصورة التالية تبين الفرق: في اليمين برنامج واحد فيه ثلاث طبقات تنادي بعضها باستدعاء دالة Function Call داخل نفس العملية، وفي اليسار ثلاثة مستويات على ثلاثة أجهزة بينها شبكة وقواعد جدار ناري:

وقد يتساءل البعض: ما الذي يهمني في هذا الفرق ما دام الكود مرتباً؟ والإجابة أن ترتيب الكود يحمي الفريق من الفوضى، لكنه لا يحمي البيانات من المخترق، فإذا وجد المخترق ثغرة تنفيذ أوامر Remote Code Execution في طبقة العرض، فهو داخل نفس العملية التي فيها طبقة الوصول إلى البيانات وكلمة مرور قاعدة البيانات، أما إذا كان كل مستوى في جهاز أو شبكة مستقلة، فالثغرة في المستوى الأمامي توصله إلى ذلك المستوى فقط، وعليه أن يجد طريقاً آخر ليعبر الجدار الناري إلى المستوى التالي، وهذا ما تقصده الضوابط عندما تطلب المستويات وليس الطبقات.
كيف وصلنا إلى هنا؟ من الحاسوب المركزي Mainframe إلى الخدمات المصغرة Microservices
الفكرة أسهل في الفهم عندما ترى كيف تغير مكان كل جزء من التطبيق عبر العقود، والصورة التالية تلخص ذلك قبل أن نفصل كل مرحلة:

الحاسوب المركزي والطرفيات الصماء Dumb Terminals: مستوى واحد
في الستينيات والسبعينيات كانت المؤسسات الكبيرة تعمل على حاسوب مركزي Mainframe واحد، مثل عائلة IBM System/360 التي أعلنتها IBM في 7 أبريل 1964، وكان الموظف يجلس أمام طرفية Terminal لا تعالج شيئاً بنفسها، فهي ترسل ضغطات الأزرار إلى الحاسوب المركزي وتعرض الشاشة التي يرسلها، أما الشاشات ومنطق العمل والبيانات فكلها في جهاز واحد، لذلك يسمى هذا الشكل مستوى واحداً One-tier حتى لو كان الكود داخله مرتباً في طبقات. ونقطة قوته أن كل شيء في مكان واحد تحت سيطرة فريق واحد، ونقطة ضعفه أن الجهاز غال جداً وكل زيادة في المستخدمين تعني حاسوباً مركزياً أكبر.
Client-Server: مستويان Two-tier
مع انتشار الحواسيب الشخصية في الثمانينيات والتسعينيات صار الجهاز المكتبي قادراً على المعالجة، فانتقلت الشاشات ومنطق العمل إلى برنامج سطح مكتب يسمى العميل الثقيل Fat Client، وبقيت البيانات في سيرفر قاعدة بيانات مشترك، وهذا هو نموذج العميل والخادم Client-Server بمستويين: البرنامج على جهاز الموظف يرسل استعلامات SQL مباشرة إلى قاعدة البيانات. وإذا عملت في شركة قديمة فغالب الظن أنك رأيت برنامج محاسبة أو مستودعات مكتوباً بلغة Delphi أو Visual Basic أو PowerBuilder يتصل بقاعدة SQL Server أو Oracle بهذا الشكل، وما زالت هذه البرامج تعمل في مؤسسات كثيرة.
والحل بسيط ويعمل، لكن له مشكلات تظهر مع الوقت، وهذا هو المهم في قصتنا:
- بيانات دخول قاعدة البيانات على كل جهاز: البرنامج يحتاج اسم مستخدم وكلمة مرور لقاعدة البيانات، فهي موجودة في ملف إعدادات أو داخل البرنامج نفسه على كل جهاز في الشركة، ومن يستخرجها يستطيع أن يتصل بقاعدة البيانات بأي أداة ويتجاوز كل قواعد البرنامج.
- منفذ قاعدة البيانات مفتوح للشبكة كلها: لأن كل جهاز يحتاج أن يصل إلى المنفذ 1433 أو 5432 أو 1521، فقاعدة البيانات مكشوفة لكل جهاز في الشبكة الداخلية، بما فيها الجهاز المصاب ببرنامج خبيث.
- الصلاحيات في البرنامج وليست في السيرفر: البرنامج هو الذي يخفي زر الحذف عن الموظف العادي، لكن قاعدة البيانات نفسها تقبل الحذف من نفس الحساب المشترك.
- التحديث على كل جهاز: أي تغيير في منطق العمل يعني تثبيت نسخة جديدة على مئات الأجهزة، وفي الفترة بينهما تعمل نسختان من المنطق على نفس البيانات.
المعمارية ثلاثية المستويات Three-tier
الحل كان أن ينتقل منطق العمل من جهاز الموظف إلى سيرفر في المنتصف يسمى سيرفر التطبيقات Application Server، فيصبح العميل رقيقاً Thin Client يعرض الشاشات ويرسل الطلبات فقط، وسيرفر التطبيقات هو الوحيد الذي يملك بيانات الدخول إلى قاعدة البيانات ويطبق القواعد والصلاحيات، وقاعدة البيانات لا تقبل الاتصال إلا منه. وتذكر IBM في نفس الصفحة أن ميزة هذا الشكل الأمنية أن مستوى العرض ومستوى البيانات لا يتصلان مباشرة، فيعمل مستوى التطبيق كجدار حماية داخلي يمنع حقن SQL وغيره من الاستغلال إذا صمم جيداً، وأن نموذج المستويين الأصلي يسمح بالاتصال المباشر بين العرض والبيانات.
والصورة التالية تضع الأشكال الثلاثة بجانب بعضها، ولاحظ أن السؤال الأهم في كل شكل هو: من يتصل بقاعدة البيانات؟

هل تطبيق MVC القديم الذي يبني الصفحات في السيرفر ثلاثي المستويات؟
هذا من أكثر الأسئلة التي تسبب الخلط، والسبب أن MVC أيضاً ثلاثة أجزاء: النموذج Model والعرض View والمتحكم Controller. لكن MVC نمط لتصميم واجهة المستخدم وترتيب الكود، وليس نموذجاً لتوزيع التطبيق على الأجهزة، فقد ابتكره Trygve Reenskaug كما يذكر في صفحته عن MVC في Xerox PARC عام 1978، وكان هدفه أن يربط بين النموذج الذهني للمستخدم والنموذج الرقمي في الحاسوب، أي أنه وصف لكيف يتعامل المستخدم مع البيانات على الشاشة، ولم يكن عن الشبكات والسيرفرات أصلاً.
وعندما انتقل النمط إلى تطبيقات الويب في أطر مثل Ruby on Rails وASP.NET MVC وLaravel وSpring MVC، صار يصف ترتيب الكود داخل سيرفر التطبيق: ال Controller يستقبل الطلب، وال Model فيه البيانات والقواعد، وال View قالب يبني صفحة HTML. والصورة التالية تبين مكان النمط من المستويات:

فإجابة السؤال: تطبيق MVC التقليدي يمكن أن يكون ثلاثي المستويات، لكن ليس لأنه MVC، وإنما لأن المتصفح يعمل على جهاز المستخدم، وسيرفر التطبيق على جهاز، وقاعدة البيانات على جهاز أو شبكة لا يصل إليها إلا سيرفر التطبيق. فإذا كانت قاعدة البيانات على نفس السيرفر ومنفذها مفتوح للإنترنت، فالتطبيق ما زال MVC بطبقاته الثلاث، لكنه في نظر الضوابط أقرب إلى مستويين، لأن ما يفصل البيانات عن الإنترنت هو كلمة مرور فقط. وتذكر أن تطبيقاً مكتوباً بلا أي نمط، كملفات PHP يخلط كل منها HTML بالاستعلامات، يمكن أن يكون ثلاثي المستويات إذا نشر بالطريقة الصحيحة، والكود السيئ مشكلة صيانة وأمان أخرى بالطبع.
تطبيقات SPA مع API: هل تضيف الواجهة مستوى؟
في العقد الأخير انتقل جزء كبير من طبقة العرض إلى المتصفح نفسه، فتطبيق الصفحة الواحدة Single Page Application المكتوب بـ React أو Vue أو Angular هو ملفات JavaScript وHTML ثابتة ينزلها المتصفح من سيرفر ملفات أو شبكة توزيع محتوى CDN، ثم تطلب البيانات بصيغة JSON من واجهة برمجة API في سيرفر التطبيق، وال API هو الذي يكلم قاعدة البيانات، والصورة التالية تبين الخطوات:

وهنا خطأ شائع: يظن الفريق أن ال SPA مستوى العرض، وال API مستوى التطبيق، وقاعدة البيانات مستوى البيانات، فالنظام ثلاثي المستويات وانتهى الأمر. والإجابة أن هذا صحيح في وصف أماكن الكود، لكن سيرفر الملفات الثابتة لا يقف بين الإنترنت وال API، فكل من يعرف عنوان ال API يستطيع أن يرسل له أي طلب بدون الواجهة، لذلك لا تضيف ملفات ال SPA حاجزاً أمنياً، وال API هو المستوى المعرض للإنترنت فعلاً، وكل التحقق Validation والصلاحيات Authorization مكانها هناك كما شرحنا في أمن واجهات API. والتصميم الذي يحقق المستويات هنا أن يكون أمام ال API بوابة أو Reverse Proxy مع جدار حماية تطبيقات الويب WAF في منطقة معزولة، ثم ال API في شبكة داخلية، ثم قاعدة البيانات في شبكة لا يصل إليها إلا ال API. ويذكر Fowler في نفس المقال أن تقسيم العرض بين عميل غني Rich Client في المتصفح وطبقة خلفية مخصصة له Backend For Frontend في السيرفر هو مجرد طريقة أخرى لتوزيع نفس الطبقات المنطقية.
المعمارية متعددة المستويات N-tier والخدمات المصغرة Microservices
الرقم ثلاثة ليس حداً، فالأنظمة الكبيرة تضيف مستويات حسب الحاجة: جدار حماية تطبيقات الويب WAF وال Reverse Proxy في المقدمة، ثم مستوى الويب، ثم مستوى أو أكثر لمنطق العمل، ثم الطوابير Queues والذاكرة المؤقتة Cache، ثم قاعدة البيانات، وصفحة Microsoft المذكورة ترسم مثالاً فيه مستويان أوسطان وطابور رسائل بينهما. وتفرق الصفحة بين نموذجين للاتصال: النموذج الصارم Strict الذي يمر فيه الطلب على كل مستوى بالترتيب ولا يتجاوز أي مستوى، والنموذج المرن Relaxed الذي يسمح بتجاوز بعض المستويات، وتذكر أن الصارم أبطأ وأثقل، والمرن يربط الأجزاء ببعضها أكثر فيصعب تغييرها. وهذا يخص الشبكة مباشرة، لأن كل استثناء يسمح بتجاوز مستوى هو قاعدة إضافية في الجدار الناري.
أما الخدمات المصغرة Microservices فهي تقسيم آخر للنظام حسب مجالات العمل، وكل خدمة لها طبقاتها وقاعدة بياناتها غالباً، وكثرة الخدمات لا تعني أن النظام متعدد المستويات بالمعنى الأمني، فإذا كانت الخدمات العشرون وقواعد بياناتها كلها في شبكة واحدة مسطحة Flat Network يصل فيها كل شيء إلى كل شيء، فالمخترق الذي يدخل أي خدمة يصل إلى كل قواعد البيانات. وفي Kubernetes بالذات تذكر وثائق Network Policies أن ال Pod غير معزول افتراضياً وأن كل الاتصالات الداخلة والخارجة مسموحة، وأن سياسات الشبكة لا تعمل إلا مع إضافة شبكة Network Plugin تدعمها، فوجود Namespace لكل خدمة لا يفصل شيئاً في الشبكة بنفسه، وقد شرحنا متى تحتاج Kubernetes أصلاً في هل تحتاج Kubernetes فعلاً؟.
فك الخلط: ماذا يعني كل وصف تسمعه؟
الجدول التالي يجمع أشهر الأوصاف التي تسمعها في الاجتماعات، وما تعنيه فعلاً، وهل تحقق المستويات بالمعنى الذي تطلبه الضوابط، مع العلم أن الحكم النهائي لإدارة الأمن السيبراني في جهتك وللمدقق:
| ما يقوله الفريق | ماذا يعني فعلاً | هل هو ثلاثي المستويات؟ |
|---|---|---|
| «عندنا Frontend وBackend وقاعدة بيانات» | وصف لأجزاء الكود، وقد تكون الثلاثة على سيرفر واحد وفي شبكة واحدة | لا يكفي الوصف للحكم، والسؤال الصحيح: ما الذي يفصل كل جزء عن الآخر في الشبكة؟ ومن يستطيع الوصول إلى منفذ قاعدة البيانات؟ |
| «تطبيقنا MVC» | نمط لترتيب الكود داخل سيرفر التطبيق | MVC لا يحدد المستويات، فالحكم على طريقة النشر كما شرحنا أعلاه |
| «عندنا سيرفران: تطبيق وقاعدة بيانات» | فصل فعلي للبيانات عن التطبيق، والتطبيق نفسه يستقبل الإنترنت مباشرة | خطوة صحيحة، لكن الجهاز الذي يستقبل الإنترنت هو نفسه الذي يملك بيانات دخول قاعدة البيانات، والقراءة الأحوط أن تضيف مستوى أمامياً في منطقة معزولة DMZ فيه ال Reverse Proxy وال WAF |
| «كل جزء في Container مستقل على نفس السيرفر» | عزل في العمليات وأنظمة الملفات، لكن Compose يضع الخدمات في شبكة واحدة افتراضياً فيصل كل Container إلى الآخر | لا، ما دامت الشبكة واحدة ومنفذ قاعدة البيانات منشوراً، وهذا ما سنراه في التجربة |
| «Containers على نفس السيرفر في شبكات Docker منفصلة، وقاعدة البيانات في شبكة داخلية Internal» | فصل في الشبكة يمنع ال Proxy من الوصول إلى قاعدة البيانات، مع بقاء الجميع على نفس النواة Kernel ونفس الجهاز | يحقق فكرة المستويات على مستوى الشبكة، لكن تعريف الضوابط يذكر «منصات منفصلة»، فقد لا يقبله المدقق للأنظمة الحساسة، وسنناقش هذا في قسم الثمن |
| «SPA مع API» | العرض في المتصفح، والمنطق في ال API | ثلاثة أماكن للكود، لكن المستوى الأمامي المعرض للإنترنت هو ال API نفسه إذا لم يكن أمامه Proxy أو بوابة في منطقة معزولة |
| «عندنا Microservices على Kubernetes» | خدمات كثيرة، وكل Pod يصل إلى كل Pod افتراضياً | لا يكفي، حتى تفصل المناطق بسياسات شبكة Network Policies تعمل فعلاً |
| «ثلاثة VMs في ثلاث شبكات فرعية Subnets وبينها جدار ناري لا يسمح إلا للمستوى المجاور» | فصل فعلي في الأجهزة والشبكة | نعم، وهذا الشكل الذي ترسمه Microsoft في مثالها: كل مستوى في Subnet، وقاعدة البيانات لا تقبل إلا من مستوى منطق العمل |
ما الذي تطلبه الضوابط بالنص؟
في الإصدار الثاني من الضوابط الأساسية للأمن السيبراني ECC-2:2024، المكون الفرعي 2-15 «حماية تطبيقات الويب Web Application Security» هدفه حماية تطبيقات الويب الخارجية للجهة، والضابط 2-15-3 يقول إن متطلبات حمايتها يجب أن تغطي بحد أدنى عدة بنود، منها البند 2-15-3-2 ونصه في النسخة العربية (صفحة 21): «استخدام مبدأ المعمارية متعددة المستويات (Multi-tier Architecture)»، وفي النسخة الإنجليزية (صفحة 28): «Adoption of the multi-tier architecture principle». ويأتي قبله البند 2-15-3-1 الذي يطلب جدار حماية تطبيقات الويب، وبعده 2-15-3-3 الذي يطلب بروتوكولات آمنة مثل HTTPS.
والضابط لا يذكر عدداً للمستويات، لكن قائمة المصطلحات في نفس الوثيقة (صفحة 26 في العربية وصفحة 37 في الإنجليزية) تعرف المعمارية متعددة المستويات بأنها «معمارية أو بنية تطبق أسلوب عميل-خادم الذي يتم فيه تطوير وصيانة منطق العملية الوظيفية، والوصول إلى البيانات، وتخزين البيانات وواجهة المستخدم كوحدات مستقلة على منصات منفصلة». ولاحظ عبارة «على منصات منفصلة» separate platforms، فالتعريف لا يكتفي بأن يكون الكود مقسماً إلى وحدات، وإنما يطلب أن تعمل هذه الوحدات على منصات منفصلة، وهذا هو المستوى Tier وليس الطبقة Layer.
أما ضوابط الأمن السيبراني للأنظمة الحساسة CSCC-1:2019 فتضيف الرقم صراحة في موضعين (صفحة 27 من الوثيقة):
- الضابط 2-12-2 في مكون حماية تطبيقات الويب الخارجية للأنظمة الحساسة: بالإشارة إلى البند 2-15-3-2 في ECC، يجب استخدام مبدأ المعمارية متعددة المستويات بحد أدنى ثلاثة مستويات.
- البند 2-13-3-1 في مكون أمن التطبيقات الداخلية للأنظمة الحساسة: استخدام مبدأ المعمارية متعددة المستويات على ألا يقل عدد المستويات عن ثلاثة.
فالخلاصة أن كل تطبيق ويب خارجي في جهة تنطبق عليها ECC يحتاج مستويات منفصلة، وكل نظام حساس، خارجياً كان أو داخلياً، يحتاج ثلاثة مستويات على الأقل. وفي دليلنا المقترح كتبنا الضابط ARC-ASC-02-03 المعمارية متعددة المستويات بهذا المعنى: أن يفصل التطبيق الخارجي إلى مستويات منفصلة على الأقل للعرض والمنطق والبيانات، بحيث لا تكون قاعدة البيانات قابلة للوصول من الإنترنت ولا من طبقة العرض مباشرة، وألا يقل عدد المستويات عن ثلاثة في الأنظمة الحساسة، وأن يكون الفصل بالشبكة وقواعد الجدار الناري وليس بالمجلدات، مع دليل الالتزام: مخطط المعمارية وقواعد الجدار الناري بين المستويات. ويكمله الضابط ARC-ASC-12-01 جدار حماية تطبيقات الويب المقابل للبند 2-15-3-1، وبقية ضوابط النطاق 12 الخاصة بتشغيل التطبيق خلف ال Reverse Proxy.
كيف تبدو المستويات في الشبكة؟
الترجمة العملية للمستويات هي مناطق شبكة Network Zones بينها جدار ناري، وكل منطقة لا تكلم إلا جارتها. والمنطقة الأمامية تسمى المنطقة منزوعة السلاح DMZ، ويعرفها معجم NIST بأنها شبكة محيطية Perimeter Network أو شبكة فرعية محمية Screened Subnet تفصل الشبكة الداخلية الأكثر ثقة عن الشبكة الخارجية الأقل ثقة. وكل منطقة هنا شبكة فرعية Subnet لها نطاق عناوين، وشرحنا حسابها وتخطيطها في الشبكات الفرعية Subnets وCIDR، وكيف تبني المناطق نفسها بالشبكات الافتراضية VLAN وVPC وأدوات مفتوحة المصدر في تقسيم الشبكة Network Segmentation، والصورة التالية تبين الشكل الكامل:

في الشكل أعلاه لاحظ التالي:
- مستوى العرض في ال DMZ: فيه ال Reverse Proxy وجدار حماية تطبيقات الويب WAF، وهو الوحيد الذي يقبل اتصالاً من الإنترنت، وعلى المنفذ 443 فقط، ولا يحمل أي بيانات أو أسرار لقاعدة البيانات. وقد شرحنا طبقات الحماية التي توضع فيه في حماية تطبيقات الويب على مستوى HTTP، وما يحتاجه التطبيق ليعمل خلفه في تطبيقك خلف Reverse Proxy.
- مستوى التطبيق في منطقة داخلية: يقبل الاتصال من ال DMZ فقط وعلى منفذ التطبيق فقط، وهو الوحيد الذي يملك بيانات الدخول إلى قاعدة البيانات.
- مستوى البيانات في منطقة مقيدة: لا يوجد أي مسار إليه من الإنترنت، ولا من ال DMZ، ويقبل الاتصال على منفذ قاعدة البيانات من مستوى التطبيق فقط، ويفضل أن تقيد قاعدة البيانات نفسها مصادر الاتصال أيضاً، كملف
pg_hba.confفي PostgreSQL، حتى لا يعتمد الفصل على الجدار الناري وحده. - بيانات دخول منفصلة لكل مستوى: حساب التطبيق في قاعدة البيانات ليس حساب المدير، ولا يملك صلاحية إنشاء الجداول أو المستخدمين في الإنتاج Production، وال Proxy لا يعرف أي كلمة مرور لقاعدة البيانات.
- الخروج Egress مقيد أيضاً: مستوى البيانات لا يحتاج الإنترنت، فلا تسمح له بالخروج إليه، والسبب أن المخترق الذي يصل إلى قاعدة البيانات يحتاج طريقاً لإخراج البيانات أو لتنزيل أدواته.
- الإدارة من مسار منفصل: دخول SSH ولوحات الإدارة لا يمر من ال DMZ، وإنما عبر VPN أو سيرفر قفز Bastion، كما شرحنا في ما هو VPN؟.
وفي خارطة CTO لشركة SaaS ناشئة تجد كيف تكبر هذه المناطق مع الشركة من سيرفر واحد إلى شبكات منفصلة، وإذا كان نظامك يخدم عدة عملاء على نفس البنية فالفصل بين المستويات لا يغني عن الفصل بين العملاء الذي شرحناه في تعدد المستأجرين Multi-tenancy، فالأول يحمي البيانات من الإنترنت، والثاني يحمي بيانات كل عميل من الآخر.
كيف تثبت ذلك للمدقق؟
المدقق لا يقبل جملة «نظامنا ثلاثي المستويات» في رد مكتوب، وإنما يطلب أدلة يستطيع مراجعتها، وهذه أهمها:
- مخطط الشبكة Network Diagram: يبين المناطق والشبكات الفرعية وعناوينها، وأين يقع كل سيرفر أو Container، ومن أين يدخل المستخدم ومن أين يدخل فريق الإدارة.
- جدول التدفقات المسموحة: المصدر والوجهة والمنفذ وسبب كل قاعدة، مثل القائمة أسفل الصورة أعلاه، ويجب أن يطابق الإعداد الفعلي.
- تصدير قواعد الجدار الناري: مخرج
nft list rulesetأوiptables-saveأوufw status verboseعلى السيرفرات، أو قائمة مجموعات الأمان Security Groups وقواعدها من مزود السحابة، أو تصدير الإعداد من جدار الحماية الشبكي. - إثبات من الخارج: فحص منافذ من الإنترنت يبين أن المفتوح هو 443 فقط، ومحاولة اتصال من مستوى العرض إلى قاعدة البيانات تفشل، كما سنفعل في التجربة.
- إعداد قاعدة البيانات: عنوان الاستماع، وقيود مصادر الاتصال، وصلاحيات حساب التطبيق.
- للبيئات المبنية على Docker: مخرج
docker network inspectلكل شبكة، وملف Compose الذي يبين أن قاعدة البيانات بلا منفذ منشور، كما شرحنا في شبكات Docker وأفضل الممارسات.
ثمن المستويات، ومتى يكفي سيرفر واحد؟
كل مستوى تضيفه له ثمن، وصفحة Microsoft نفسها تذكر أن الفصل الفعلي للمستويات يحسن التوسع والتحمل، لكنه يضيف زمن استجابة Latency بسبب الاتصال الإضافي عبر الشبكة، وأن الأنظمة الكبيرة تجعل إدارة أمن الشبكة صعبة، وأن مرور الطلب على عدة مستويات يصعب الاختبار والمراقبة، وأن المستوى الأوسط الذي لا يفعل إلا عمليات CRUD بسيطة يضيف تعقيداً بلا قيمة حقيقية. وبالأرقام العملية: كل مستوى سيرفر إضافي تدفع له وتحدثه وتراقبه وتأخذ نسخته الاحتياطية، وكل قاعدة في الجدار الناري شيء يجب أن يراجع، وكل قفزة في الشبكة تضيف أجزاء من الملي ثانية داخل نفس مركز البيانات.
لذلك فالسؤال الصحيح ليس «كم مستوى عندي؟» وإنما «ما الذي يحتاجه نظامي والضابط الذي ينطبق عليه؟»، وفيما يلي القاعدة التي نقترحها:
- تطبيق صغير على سيرفر افتراضي VPS واحد: يمكنك تحقيق فكرة المستويات على مستوى الشبكة: ال Reverse Proxy وحده ينشر المنفذ 443، والتطبيق في شبكة Docker داخلية لا يصل إليها إلا ال Proxy، وقاعدة البيانات في شبكة Docker داخلية أخرى لا يصل إليها إلا التطبيق ولا تنشر أي منفذ، وحساب التطبيق في قاعدة البيانات محدود الصلاحيات. وهذا يحقق الهدف الأمني الأهم: ثغرة في ال Proxy لا توصل إلى قاعدة البيانات. لكن تذكر أن المكونات كلها تشترك في نفس النواة ونفس الجهاز، فثغرة هروب من ال Container واختصاراً Container Escape أو اختراق حساب الإدارة على السيرفر يتجاوز كل هذا الفصل.
- تطبيق ويب خارجي في جهة تنطبق عليها ECC: التعريف يذكر «منصات منفصلة»، والقراءة الأحوط أن تكون قاعدة البيانات على الأقل على سيرفر أو VM منفصل في شبكة فرعية منفصلة، وأن يكون ال Proxy أو ال WAF في منطقة أمامية، وقبل أن تعتمد على شبكات Docker على سيرفر واحد اسأل إدارة الأمن السيبراني في جهتك واكتب القرار وسببه، والسبب أن الحكم على كفاية الفصل لها وللمدقق وليس لفريق التطوير.
- نظام حساس Critical System: ثلاثة مستويات على الأقل بنص CSCC، والمتوقع أن تكون على أجهزة أو VMs منفصلة في مناطق شبكة منفصلة بينها جدار ناري، مع سجلات للتدفقات المرفوضة، فلا تعتمد على سيرفر واحد مهما كانت شبكات Docker مرتبة.
وقد شرحنا في مدخل إلى تصميم الأنظمة كيف يكبر النظام من سيرفر واحد خطوة خطوة، وفصل قاعدة البيانات هناك كان أول خطوة لأسباب الأداء، وهنا نرى أنه أول خطوة لأسباب الأمان أيضاً.
تجربة عملية: ثلاث شبكات Docker لثلاثة مستويات
الآن سوف نبني نسخة صغيرة من الشكل السابق على جهاز واحد بـ Docker Compose: Reverse Proxy بـ nginx، وتطبيق صغير بلغة Python يقرأ من قاعدة البيانات، وقاعدة بيانات PostgreSQL، وكل منها في الشبكات التي يحتاجها فقط، ثم نتحقق من الداخل من يصل إلى من. وكل الأسماء تبدأ بـ lab-tiers-، والمنفذ الوحيد المنشور هو 127.0.0.1:21480، والصورة التالية تبين ما سنبنيه والنتائج التي ظهرت:

الملفات
التطبيق ملف واحد يستقبل الطلب ويسأل قاعدة البيانات عن اسم المستخدم الذي اتصل به وعنوانها وإصدارها، والمثال التالي يبين الملف app/app.py:
import os, socket, psycopg
from http.server import BaseHTTPRequestHandler, HTTPServer
DSN = os.environ["DATABASE_URL"]
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
with psycopg.connect(DSN, connect_timeout=3) as conn:
row = conn.execute("select current_user, inet_server_addr(), version()").fetchone()
body = f"app={socket.gethostname()} db_user={row[0]} db_addr={row[1]}\n{row[2].split(',')[0]}\n"
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(body.encode())
HTTPServer(("0.0.0.0", 8000), Handler).serve_forever()وملف app/Dockerfile يثبت مكتبة psycopg وقت البناء، لأن التطبيق لن يستطيع الوصول إلى الإنترنت بعد تشغيله كما سنرى:
FROM python:3.13-slim
RUN pip install --no-cache-dir "psycopg[binary]==3.2.*"
COPY app.py /app.py
CMD ["python", "/app.py"]وإعداد nginx في الملف nginx.conf يمرر كل الطلبات إلى التطبيق باسمه في الشبكة:
server {
listen 80;
location / {
proxy_pass http://app:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}وملف init.sql ينشئ حساباً خاصاً بالتطبيق بلا صلاحيات إدارية، ويشغله PostgreSQL مرة واحدة عند إنشاء قاعدة البيانات:
CREATE ROLE shop_app LOGIN PASSWORD 'app-secret' NOSUPERUSER NOCREATEDB NOCREATEROLE;
GRANT CONNECT ON DATABASE shop TO shop_app;
GRANT USAGE, CREATE ON SCHEMA public TO shop_app;وأهم ملف هو compose.yaml:
name: lab-tiers
services:
proxy:
image: nginx:1.29-alpine
container_name: lab-tiers-proxy
ports:
- "127.0.0.1:21480:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
networks: [dmz, app]
depends_on: [app]
app:
build: ./app
image: lab-tiers-app:1
container_name: lab-tiers-app
environment:
DATABASE_URL: postgresql://shop_app:app-secret@db:5432/shop
networks: [app, data]
depends_on: [db]
db:
image: postgres:18
container_name: lab-tiers-db
environment:
POSTGRES_DB: shop
POSTGRES_PASSWORD: admin-secret
volumes:
- lab-tiers-pgdata:/var/lib/postgresql
- ./init.sql:/docker-entrypoint-initdb.d/10-app-role.sql:ro
networks: [data]
networks:
dmz:
name: lab-tiers-dmz-net
app:
name: lab-tiers-app-net
internal: true
data:
name: lab-tiers-data-net
internal: true
volumes:
lab-tiers-pgdata:
name: lab-tiers-pgdataفي الإعداد أعلاه لاحظ التالي:
- ال Proxy هو الخدمة الوحيدة التي فيها
ports، ومربوط بالعنوان127.0.0.1في التجربة، وفي السيرفر الحقيقي ينشر 80 و443 فقط. - كل خدمة في الشبكات التي تحتاجها فقط: ال Proxy في
dmzوapp، والتطبيق فيappوdata، وقاعدة البيانات فيdataوحدها، فلا توجد شبكة مشتركة بين ال Proxy وقاعدة البيانات. - الشبكتان
appوdataعليهماinternal: true، وتقول وثائق Compose إن Compose يعطي الشبكات اتصالاً خارجياً افتراضياً، وإن هذا الخيار ينشئ شبكة معزولة عن الخارج، فلا التطبيق ولا قاعدة البيانات يستطيعان الخروج إلى الإنترنت من خلالها. - كلمة مرور المدير
admin-secretلا يعرفها التطبيق، والتطبيق يتصل بالحساب المحدودshop_app، وفي الإنتاج تأتي هذه القيم من مدير أسرار وليس من الملف.
التشغيل والتحقق
الآن سوف نقوم بالتشغيل ثم نرسل طلباً من الجهاز عبر ال Proxy:
docker compose up -d --build
docker compose ps --format 'table {{.Name}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
curl -s http://127.0.0.1:21480/والمخرج سوف يكون كما يلي:
NAME IMAGE STATUS PORTS
lab-tiers-app lab-tiers-app:1 Up About a minute
lab-tiers-db postgres:18 Up About a minute 5432/tcp
lab-tiers-proxy nginx:1.29-alpine Up About a minute 127.0.0.1:21480->80/tcp
app=64b0ecc8ec31 db_user=shop_app db_addr=192.168.144.2
PostgreSQL 18.6 (Debian 18.6-1.pgdg13+2) on x86_64-pc-linux-gnuلاحظ أن عمود PORTS لقاعدة البيانات فيه 5432/tcp بلا سهم، أي أن المنفذ موجود داخل ال Container وغير منشور على الجهاز، وأن الطلب مر من ال Proxy إلى التطبيق ثم إلى قاعدة البيانات بالحساب shop_app. والآن نتأكد من الشبكات ومن فيها:
for n in lab-tiers-dmz-net lab-tiers-app-net lab-tiers-data-net; do
docker network inspect -f '{{.Name}} internal={{.Internal}} -> {{range .Containers}}{{.Name}} {{end}}' $n
done
docker inspect -f '{{.Name}}: {{range $k,$v := .NetworkSettings.Networks}}{{$k}}={{$v.IPAddress}} {{end}}' lab-tiers-proxy lab-tiers-app lab-tiers-dbوالمخرج سوف يكون كما يلي:
lab-tiers-dmz-net internal=false -> lab-tiers-proxy
lab-tiers-app-net internal=true -> lab-tiers-proxy lab-tiers-app
lab-tiers-data-net internal=true -> lab-tiers-db lab-tiers-app
/lab-tiers-proxy: lab-tiers-app-net=192.168.112.3 lab-tiers-dmz-net=192.168.160.2
/lab-tiers-app: lab-tiers-app-net=192.168.112.2 lab-tiers-data-net=192.168.144.3
/lab-tiers-db: lab-tiers-data-net=192.168.144.2 ال Proxy له عنوانان في شبكتي dmz وapp، والتطبيق له عنوانان في شبكتي app وdata، وقاعدة البيانات لها عنوان واحد في شبكة data. والآن نفترض أن المخترق وجد ثغرة في ال Proxy وصار ينفذ أوامر داخله، فهل يصل إلى قاعدة البيانات؟ نجرب بالاسم ثم بالعنوان مباشرة:
docker exec lab-tiers-proxy nslookup db
docker exec lab-tiers-proxy nc -zv -w 3 192.168.144.2 5432; echo "exit=$?"والمخرج سوف يكون كما يلي:
Server: 127.0.0.11
Address: 127.0.0.11:53
Non-authoritative answer:
*** Can't find db: No answer
nc: 192.168.144.2 (192.168.144.2:5432): Operation timed out
exit=1خادم DNS الداخلي في Docker لا يعرف الاسم db لمن ليس في شبكته، ومحاولة الاتصال بالعنوان مباشرة تنتهي بانتهاء المهلة، لأنه لا يوجد مسار من شبكتي ال Proxy إلى شبكة البيانات. والآن نجرب نفس الاتصال من التطبيق، ثم نجرب خروج التطبيق إلى الإنترنت:
docker exec lab-tiers-app python -c "import socket;s=socket.create_connection(('db',5432),3);print('connected to',s.getpeername())"
docker exec lab-tiers-app python -c "import urllib.request;urllib.request.urlopen('https://example.com',timeout=5)"والمخرج سوف يكون كما يلي (السطر الأخير من الخطأ الثاني):
connected to ('192.168.144.2', 5432)
urllib.error.URLError: <urlopen error [Errno -3] Temporary failure in name resolution>التطبيق يصل إلى قاعدة البيانات لأنه المستوى المجاور لها، ولا يستطيع الخروج إلى الإنترنت لأن شبكتيه داخليتان، وهذا هو قيد الخروج Egress الذي ذكرناه، فإذا احتاج التطبيق الإنترنت لإرسال بريد أو الاتصال بمزود دفع فالأفضل أن يمر ذلك عبر وكيل خروج Egress Proxy محدد بدل أن تفتح الشبكة كلها. وأخيراً نتحقق من صلاحيات الحسابين في قاعدة البيانات:
docker exec lab-tiers-db psql -U postgres -d shop -c "select rolname, rolsuper, rolcreatedb, rolcreaterole from pg_roles where rolname in ('postgres','shop_app');"والمخرج سوف يكون كما يلي:
rolname | rolsuper | rolcreatedb | rolcreaterole
----------+----------+-------------+---------------
postgres | t | t | t
shop_app | f | f | f
(2 rows)نكسر التصميم: الشكل الشائع بمستويين
الآن سوف نشغل الشكل الذي نراه كثيراً في ملفات Compose المنشورة على الإنترنت: التطبيق ينشر منفذه مباشرة بدون Proxy، وقاعدة البيانات تنشر منفذها «حتى نتصل بها من أداة الإدارة»، والاثنان في الشبكة الافتراضية، والتطبيق يتصل بحساب المدير. والمثال التالي يبين الملف compose.flat.yaml:
name: lab-tiers-flat
services:
app:
image: lab-tiers-app:1
container_name: lab-tiers-flat-app
ports:
- "127.0.0.1:21481:8000"
environment:
DATABASE_URL: postgresql://postgres:app-secret@db:5432/shop
db:
image: postgres:18
container_name: lab-tiers-flat-db
ports:
- "127.0.0.1:21482:5432"
environment:
POSTGRES_DB: shop
POSTGRES_PASSWORD: app-secret
volumes:
- lab-tiers-flat-pgdata:/var/lib/postgresql
volumes:
lab-tiers-flat-pgdata:
name: lab-tiers-flat-pgdataنشغله ونرى الشبكة والمنافذ:
docker compose -f compose.flat.yaml up -d
docker compose -f compose.flat.yaml ps --format 'table {{.Name}}\t{{.Status}}\t{{.Ports}}'
docker network inspect -f '{{.Name}} internal={{.Internal}}: {{range .Containers}}{{.Name}} {{end}}' lab-tiers-flat_default
curl -s http://127.0.0.1:21481/والمخرج سوف يكون كما يلي:
NAME STATUS PORTS
lab-tiers-flat-app Up 21 seconds 127.0.0.1:21481->8000/tcp
lab-tiers-flat-db Up 19 seconds 127.0.0.1:21482->5432/tcp
lab-tiers-flat_default internal=false: lab-tiers-flat-app lab-tiers-flat-db
app=81fcfe294443 db_user=postgres db_addr=192.168.176.3
PostgreSQL 18.6 (Debian 18.6-1.pgdg13+2) on x86_64-pc-linux-gnuالتطبيق يعمل والصفحة تظهر، ولهذا يبقى هذا الشكل في الإنتاج سنوات، لكن لاحظ ثلاثة أشياء: قاعدة البيانات منشورة على منفذ في الجهاز، والتطبيق يتصل بحساب postgres المدير، والشبكة غير داخلية. والآن نتصل بقاعدة البيانات من الجهاز نفسه بمكتبة psycopg2، كما سيفعل أي شخص يصل إلى هذا المنفذ ويعرف كلمة المرور أو يخمنها:
python - <<'EOF'
import psycopg2
c = psycopg2.connect(host="127.0.0.1", port=21482, user="postgres", password="app-secret", dbname="shop", connect_timeout=3)
cur = c.cursor(); cur.execute("select current_user, inet_server_port(), (select count(*) from pg_database)")
print(cur.fetchone())
EOF
docker exec lab-tiers-flat-app python -c "import urllib.request;print(urllib.request.urlopen('https://example.com',timeout=5).status)"والمخرج سوف يكون كما يلي:
('postgres', 5432, 4)
200اتصلنا بقاعدة البيانات مباشرة بحساب المدير دون أن نمر على التطبيق وقواعده، وهذا هو نموذج المستويين بكل مشكلاته، والتطبيق المعرض للطلبات يستطيع الخروج إلى الإنترنت فيمكن للمخترق أن ينزل أدواته أو يرسل البيانات إلى الخارج. وفي التجربة ربطنا المنفذ بالعنوان 127.0.0.1 فقط، أما في سيرفر حقيقي فكثير من الملفات تكتب "5432:5432" بدون عنوان، فيصبح المنفذ مفتوحاً على كل عناوين السيرفر، وقد شرحنا في شبكات Docker وأفضل الممارسات لماذا لا يحميك ufw في هذه الحالة.
والإصلاح هو ما بنيناه في البداية خطوة خطوة: أن تحذف ports من قاعدة البيانات، وأن تضع أمام التطبيق Proxy وتحذف ports منه أيضاً، وأن تقسم الشبكة إلى dmz وapp وdata وتجعل الأخيرتين internal: true، وأن تنشئ للتطبيق حساباً محدوداً في قاعدة البيانات. وإذا احتجت الوصول إلى قاعدة البيانات من أداة إدارة، فاستخدم docker exec أو نفقاً عبر SSH أو VPN، ولا تنشر المنفذ.
التنظيف
بعد انتهاء التجربة نحذف ما أنشأناه فقط بالاسم:
docker compose -f compose.flat.yaml down -v
docker compose down -v
docker image rm lab-tiers-app:1أخطاء شائعة
- اعتبار ترتيب المجلدات مستويات: وجود مجلدات
controllersوservicesوrepositoriesطبقات جيدة، لكنها كلها في نفس العملية، والضابط يسأل عن المستويات. - اعتبار MVC أو SPA دليلاً على ثلاثة مستويات: كلاهما يصف مكان الكود، والحكم على طريقة النشر والشبكة.
- نشر منفذ قاعدة البيانات «مؤقتاً»: المؤقت يبقى، والمنفذ المنشور يجعل قاعدة البيانات مستوى معرضاً مهما كان أمامها من مستويات.
- شبكة واحدة لكل شيء: Containers أو VMs منفصلة في شبكة مسطحة واحدة تعني أن أي مكون مخترق يصل إلى كل مكون آخر.
- حساب المدير في التطبيق: المستويات تحدد من يصل إلى قاعدة البيانات، والصلاحيات تحدد ما يستطيع فعله بعد الوصول، وتحتاج الاثنين.
- مستوى أوسط شكلي: Proxy يمرر كل شيء إلى قاعدة البيانات، أو API عام يقبل استعلامات SQL من العميل، يضيف جهازاً ولا يضيف حماية.
- نسيان طرق الإدارة: مستويات مرتبة للمستخدمين، ثم لوحة إدارة قاعدة البيانات مكشوفة للإنترنت أو SSH مفتوح على سيرفر البيانات، وهذا يتجاوز كل الفصل.
- مخطط لا يطابق الواقع: المدقق يقارن المخطط بقواعد الجدار الناري الفعلية، فأي قاعدة قديمة تسمح بـ
anyتهدم المخطط كله.
هل نظامك ثلاثي الطبقات Three-tier فعلاً؟ قائمة تحقق
- هل يمكنك أن ترسم مخططاً فيه المناطق والشبكات الفرعية ومكان كل مكون، ويطابق ما في الإنتاج؟
- هل الشيء الوحيد الذي يقبل اتصالاً من الإنترنت هو ال Reverse Proxy أو ال WAF، وعلى 443 فقط؟
- هل يعمل ال Proxy في منطقة أو شبكة منفصلة عن التطبيق، ولا يحمل أي أسرار لقاعدة البيانات؟
- هل يقبل التطبيق الاتصال من مستوى ال Proxy فقط وعلى منفذه فقط؟
- هل قاعدة البيانات بلا منفذ منشور على الإنترنت، ولا تقبل إلا من مستوى التطبيق، بقاعدة في الجدار الناري وقيد في إعداد قاعدة البيانات نفسها؟
- هل محاولة الاتصال من مستوى العرض إلى قاعدة البيانات تفشل فعلاً عند تجربتها؟
- هل حساب التطبيق في قاعدة البيانات محدود الصلاحيات، ومختلف عن حساب المدير؟
- هل الخروج إلى الإنترنت من مستوى البيانات ممنوع، ومن مستوى التطبيق محدود بما يحتاجه؟
- هل الإدارة عبر VPN أو Bastion، وليس عبر منافذ مفتوحة على أي مستوى؟
- إذا كان النظام حساساً: هل المستويات الثلاثة على أجهزة أو VMs منفصلة في مناطق منفصلة؟
- هل لديك تصدير حديث لقواعد الجدار الناري ونتيجة فحص منافذ من الخارج لتقدمهما للمدقق؟
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- الطبقة Layer تقسيم منطقي للكود داخل برنامج واحد، والمستوى Tier فصل فعلي في التشغيل بين أجهزة أو عمليات بينها شبكة وجدار ناري، والضوابط تطلب المستويات.
- الحاسوب المركزي كان مستوى واحداً، وClient-Server كان مستويين يحمل فيهما كل جهاز بيانات دخول قاعدة البيانات، والمعمارية ثلاثية المستويات وضعت سيرفر التطبيقات في الوسط فلا يرى قاعدة البيانات أحد غيره.
- MVC نمط ابتكر عام 1978 لترتيب الكود وواجهة المستخدم، وSPA مع API طريقة لتوزيع طبقة العرض، وكلاهما لا يحدد عدد المستويات، والحكم على طريقة النشر.
- البند 2-15-3-2 في ECC-2:2024 يطلب مبدأ المعمارية متعددة المستويات لتطبيقات الويب الخارجية، وتعريفه في الوثيقة يذكر «منصات منفصلة»، والضابط 2-12-2 والبند 2-13-3-1 في CSCC يطلبان ثلاثة مستويات على الأقل للأنظمة الحساسة.
- في الشبكة: Proxy وWAF في ال DMZ، والتطبيق في منطقة داخلية، وقاعدة البيانات في منطقة مقيدة، وكل منطقة تكلم جارتها فقط، مع حسابات منفصلة وخروج مقيد وإدارة من مسار منفصل.
- على سيرفر واحد تحقق شبكات Docker الداخلية فكرة الفصل كما رأينا في التجربة، أما الأنظمة الحساسة فتحتاج أجهزة منفصلة، والحكم على الكفاية لإدارة الأمن السيبراني والمدقق.
وأقرب المقالات إلى هذا الموضوع على الموقع:
دليلمدخل إلى تصميم الأنظمة: من سيرفر واحد إلى ملايين المستخدمينكيف يكبر متجر إلكتروني من سيرفر واحد إلى نظام يخدم ملايين المستخدمين، وما الثمن الذي تدفعه في كل خطوة.
دليلشبكات Docker وأفضل الممارسات مع Docker Composeلماذا يبقى المنفذ الذي تنشره في Compose مفتوحاً للإنترنت رغم ufw، وكيف تصل التطبيقات إلى بعضها عبر شبكات Docker دون نشر منافذ.
دليلحماية تطبيقات الويب بطبقات أمان في ال Reverse Proxyكلمة المرور وحدها لا تحمي تطبيقاً مكشوفاً على الإنترنت، لذلك نضع أمامه طبقات حماية في ال Reverse Proxy نفسه.
دليلضوابط أمن التطبيقات: دليل مقترح بنطاقات وضوابط مرقمة، مربوط بضوابط NCA وOWASP ASVSمقترح مستقل من عرب رووت بتسعين ضابطاً مرقماً لأمن تطبيقات الويب وواجهات API وخط CI/CD، لكل ضابط متطلب ودليل للمدقق، مربوط بالبنود التي يدعمها في ECC-2:2024 وCSCC وبأرقام OWASP ASVS 5.0، مع ملف Excel للتقييم الذاتي.
دليلالضوابط الأساسية للأمن السيبراني NCA ECC بلغة بسيطةكيف تقرأ الضوابط الأساسية ECC دون أن تضيع في أرقامها، وعلى من تنطبق، وما المطلوب من جهتك فعلاً في كل مكون.
دليلخارطة CTO لشركة SaaS ناشئة من اليوم الأول إلى خمسين موظفاًماذا يجهز المدير التقني في كل مرحلة من نمو شركة SaaS ناشئة، وما الذي يؤجله حتى يحتاجه فعلاً.سجل التحديثات
- أكتوبر 2026: كتابة المقال، ومراجعة نصوص ECC-2:2024 بالعربية والإنجليزية وCSCC-1:2019، وتنفيذ التجربة العملية على Docker Engine 29.8.0 وDocker Compose 5.5.1 مع nginx 1.29.8 وPostgreSQL 18.6 وPython 3.13.16 وpsycopg 3.2.13.