اذهب إلى المحتوى

رفع مستوى أمان تطبيقات جانغو في بيئة الإنتاج


رشا سعد

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

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

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

متطلبات بيئة العمل

ستحتاج المتطلبات التالية لتتمكن من التطبيق العملي للمقال:

  1. نسخة جاهزة من أي تطبيق جانغو، إن لم تتوفر لديك يمكنك إعدادها بإتباع خطوات المقال تثبيت إطار العمل Django وتهيئة بيئته البرمجية على Ubuntu، إذ استُعمل هنا التطبيق testsite المطوّر بموجبه.
  2. معرفة ببنية ملفات جانغو وإعداداته الأساسية، وننصحك بقراءة سلسلة بناء مدونة عبر جانغو.

تنويه: تم تنظيم هذا المقال وعرضه بطريقة تلائم متطلبات التطبيق testsite فإن كنت تستخدم تطبيقًا آخر ستلاحظ بعض الاختلاف لكن لا بأس يمكنك تنفيذ الفقرات على حدة بما يناسب تطبيقك.

الخطوة 1: إعادة هيكلة إعدادات جانغو

سنُحلل الملف setting.py ونعيد ترتيبه ضمن مجموعة إعدادات منفصلة تستند إلى متغيرات البيئة، ما يعني أن أي نقل مستقبلي للتطبيق من بيئة إلى أخرى مثل نقله من بيئة التطوير إلى بيئة الإنتاج، سيتطلب تغيير بعض الإعدادات فقط بما يتلائم مع البيئة الجديدة.

أنشئ مجلدًا جديدًا باسم settings ضمن المجلد الأب لمشروعك كمايلي:

mkdir testsite/testsite/settings

الاسم testsite هو اسم التطبيق كما ذكرنا في فقرة متطلبات بيئة العمل، أنشئ بعدها ثلاث ملفات بايثون ضمن المجلد settings كما يلي إذ ستحلّ هذه الملفات المنفصلة بدلًا من الملف setting.py:

cd testsite/testsite/settings
touch base.py development.py production.py

تمامًا كما توحي الأسماء فالملف development.py سيتضمن الإعدادات التي ستحتاج إليها في أثناء التطوير، بينما سيحوي الملف production.py الإعدادات اللازمة لمرحلة الإنتاج وقد فصلنا إعدادات المرحلتين عن بعضهما بسبب اختلاف المتطلبات بينهما، فإعدادات مرحلة الإنتاج مثل توجيه الطلبات إلى HTTPS أو إضافة الترويسات أو استخدام قاعدة بيانات الإنتاج لن تنفع خلال مرحلة التطوير، أما الملف base.py فسيشمل الإعدادات المشتركة الأساسية التي سيرثها كلًا من development.py و production.py فهذ العملية ستساهم في عدم تكرار الإعدادات وتصميم كود نظيف لتطبيقك.

طالما أن الملفات الثلاثة ستحمل كامل الإعدادات المطلوبة فلنزيل إذًا setting.py لضمان عمل جانغو بشكلٍ سليم.

ضمن نفس المجلد settings نفذ الأمر التالي المتضمن تعديل اسم setting.py ليصبح base.py:

mv ../settings.py base.py

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

الخطوة 2: استخدام python-dotenv

الحزمة python-dotenv هي إحدى اعتماديات التطبيق، مهمتها تحميل متغيرات البيئة من ملف خاص يدعى env.، وتثبيتها ضروري ليتمكن التطبيق من التعامل مع مجلد الإعدادات والملفات الجديدة التي عملنا عليها في الخطوة السابقة. لنبدأ التطبيق العملي:

توجه إلى مسار الجذر لمشروعك:

cd ../../

وثبت الاعتمادية python-dotenv:

pip install python-dotenv

ومن ثم اضبط إعدادات جانغو ليستخدم dotenv عبر تعديل الملفين manage.py الخاص ببيئة التطوير و wsgi.py الخاص ببيئة الانتاج كما يلي:

افتح أولًا الملف manage.py باستعمال محرر النصوص:

nano manage.py

وأضف ضمنه التعليمات التالية:

import os
import sys
import dotenv

def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testsite.settings.development')

    if os.getenv('DJANGO_SETTINGS_MODULE'):
    os.environ['DJANGO_SETTINGS_MODULE'] = os.getenv('DJANGO_SETTINGS_MODULE')

    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()

dotenv.load_dotenv(
    os.path.join(os.path.dirname(__file__), '.env')
)

ثم أغلق الملف بعد حفظ التغييرات، وافتح الثاني:

nano testsite/wsgi.py

وأضف ضمنه التعليمات الناقصة المتعلقة بـ dotenv ليصبح بهذا الشكل:

import os
import dotenv

from django.core.wsgi import get_wsgi_application

dotenv.load_dotenv(
    os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
)

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testsite.settings.development')

if os.getenv('DJANGO_SETTINGS_MODULE'):
 os.environ['DJANGO_SETTINGS_MODULE'] = os.getenv('DJANGO_SETTINGS_MODULE')

application = get_wsgi_application()

لا تنسَ حفظ التغييرات قبل إغلاق الملف.

بموجب التعديلات التي أجريتها على الملفين: عند كل تشغيل لهما manage.py في التطوير والملف wsgi.py في الإنتاج، سيبحث جانغو عن الملف env. فإن وجده سيستخدم ملف الإعدادات الذي يوصي به env. وفي حال لم يجده يعتمد إعدادات التطوير افتراضيًا.

لننشئ الآن الملف env. ضمن مجلد المشروع كما يلي:

nano .env

ونضيف ضمنه السطر التالي:

DJANGO_SETTINGS_MODULE="testsite.settings.development"

تنويه: ننصحك بإضافة env. على قائمة العناصر الموجودة في الملف gitingore. لتُحافظ عليه وتحميه من التعديل فهو في نهاية المطاف يتضمن كلمات المرور وإعدادات API وغيرها من المعلومات الحساسة المتعلقة بالتطبيق، وعندها ستنشئ ملف env. خاص بكل بيئة تنشر تطبيقك فيها، ولذا نوصيك بإنشاء نموذج ملف جاهز باسم env.example. ضمن مشروعك يمكنك تعديله بسهولة ليشكل env. جديد كلما دعت الحاجة.

بناءً على السطر الذي أضفناه للملف env.، سيستخدم جانغو افتراضيًا testsite.settings.development وبالمثل إن غيرت قيمة المحدد DJANGO_SETTINGS_MODULE إلى testsite.settings.production فإنه سيبدأ باستخدام إعدادات بيئة الإنتاج.

الخطوة 3: إنشاء ملفات الإعدادات لكل من بيئتي التطوير والإنتاج

افتح الملف base.py وأضف ضمنه إعدادات التهيئة الخاصة بكل بيئة والتي ستعدلها لاحقًا في ملفي development.py و production.py المنفصلين، وتأكد أنك تملك معلومات الاتصال بقاعدة بيانات الإنتاج إذ ستحتاجها في الملف production.py.

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

لنبدأ بنقل الإعدادات من base.py إلى ملف إعدادات التطوير development.py الخاص بتطبيقنا testsite، لذا افتح ملف إعدادات التطوير:

nano testsite/settings/development.py

واكتب ضمنه تعليمة الاستيراد من base.py ومن بعدها الإعدادات الخاصة ببيئة التطوير وفق التالي:

from .base import *

DEBUG = True

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

كما ترى، الإعدادات الخاصة ببيئة التطوير لدينا هي التنقيح DEBUG الذي تم تفعيله بالقيمة True ومحدد قاعدة البيانات DATABASES الذي يشير إلى قاعدة البيانات المحلية وهي في حالتنا SQLite، وهذه الإعدادات لن تتماشى مع بيئة الإنتاج إذ إن الإنتاج يتطلب إلغاء تفعيل التنقيح والربط مع قاعدة بيانات الإنتاج.

تنويه: يعود إلغاء تفعيل التنقيح في بيئة الإنتاج إلى أسبابٍ أمنية لضمان عدم تسريب أي معلومات سرية عن المشروع من الممكن أن تظهر في أثناء التنقيح، وتعرض تطبيقك للخطر مثل API أو KEY أو PASS أو SECRET أو SIGNATURE أو TOKEN، لذا احرص دائمًا على إلغاء تفعيل التنقيح DEBUG قبل نشر تطبيقك في بيئة الإنتاج.

لننتقل للملف production.py:

nano testsite/settings/production.py

سنعتمد نفس الطريقة، ولكن مع إلغاء تفعيل التنقيح ووضع محددات قاعدة بيانات الإنتاج كما يلي:

from .base import *

DEBUG = False

ALLOWED_HOSTS = []

DATABASES = {
    'default': {
        'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.sqlite3'),
        'NAME': os.environ.get('SQL_DATABASE', os.path.join(BASE_DIR, 'db.sqlite3')),
        'USER': os.environ.get('SQL_USER', 'user'),
        'PASSWORD': os.environ.get('SQL_PASSWORD', 'password'),
        'HOST': os.environ.get('SQL_HOST', 'localhost'),
        'PORT': os.environ.get('SQL_PORT', ''),
    }
}

يمكنك استخدام dotenv مع وضع قيم افتراضية لضبط محددات قاعدة البيانات وفق المثال المقدم أعلاه، ولكن احرص على تبديل قيم المتغيرات بما يناسب قاعدة بيانات التطوير الخاصة بك.

إذًا، فقد ضبطنا التطبيق ليعمل بنموذجين مختلفين من الإعدادات يتم القرار بينهما بناءً على قيمة المحدد DJANGO_SETTINGS_MODULE الموجود ضمن الملف env.، ففي حالتنا على سبيل المثال سيعني العمل بإعدادات الإنتاج أن DEBUG سيحمل القيمة False، وأن ALLOWED_HOSTS سيُحدد، أما التطبيق فسيبدأ باستخدام قاعدة بيانات مختلفة هي قاعدة بيانات الإنتاج (التي يُفترض أنك جهزتها مسبقًا على الخادم الخاص بك).

الخطوة 4: ضبط إعدادات الأمان لتطبيق جانغو

يتضمن جانغو إعدادات أمانٍ جاهزة لتضيفها إلى مشروعك، وهذه الإعدادات ضرورية لكل تطبيق يعمل في بيئة الإنتاج ويطلق للاستخدام العام. سيقتصر عملنا مع إعدادات الأمان على تهيئة ملف الإنتاج production.py فقط إذا لا يوصى باستخدامها في بيئة التطوير.

أهم ما تتناوله هذه الإعدادات هو فرض استخدام HTTPS للعديد من مميزات مواقع الويب مثل ملفات تعريف الارتباط للجلسة وملفات تعريف الارتباط CSRF وتوجيه HTTP إلى HTTPS… إلخ، لذا فإن تنفيذ هذه الخطوة يفترض أن تطبيقك مجهز بخادم ويب وأن لديك اسم نطاق يشير إلى خادم التطبيق.

افتح الملف production.py باستخدام محرر النصوص:

nano production.py

أضف ضمنه القيم الناقصة ليصبح بهذا الشكل:

from .base import *

DEBUG = False

ALLOWED_HOSTS = ['your_domain', 'www.your_domain']

DATABASES = {
    'default': {
        'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.sqlite3'),
        'NAME': os.environ.get('SQL_DATABASE', os.path.join(BASE_DIR, 'db.sqlite3')),
        'USER': os.environ.get('SQL_USER', 'user'),
        'PASSWORD': os.environ.get('SQL_PASSWORD', 'password'),
        'HOST': os.environ.get('SQL_HOST', 'localhost'),
        'PORT': os.environ.get('SQL_PORT', ''),
    }
}

SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True

لنشرح المحددات بالترتيب:

  • SECURE_SSL_REDIRECT: يوجه كافة الطلبات من HTTP إلى HTTPS بالطبع مع استثناء بعض الحالات المعفاة من ذلك، يستخدم التطبيق إذًا الاتصالات المشفرة ما يستلزم تهيئة شهادات TLS على الخادم، وفي حال كنت تستخدم Nginx أو أباتشي فسيكون هذا الضبط إضافيًا وغير ملزم إذ إن كلا الخادمين يقوم فعلًا بهذا التوجيه.
  • SESSION_COOKIE_SECURE: يخبر هذا المحدد المتصفح بعدم إمكانية التعامل مع ملفات تعريف الارتباط خارج HTTPS. ما يعني أن كل ما يولّده مشروعك من ملفات تعريف ارتباط (مثل تلك الخاصة بعمليات تسجيل الدخول) ستعمل فقط عبر اتصالٍ مشفر.
  • CSRF_COOKIE_SECURE: يشبه المحدد السابق ولكنه يُطبق على مفتاح CSRF الخاص بك. تحمي هذه الطريقة تطبيقك من هجمات تزوير الطلبات عبر المواقع من خلال تأكدها -باستخدام مفاتيح CSRF- من أن كافة النماذج المقدمة سواء لتسجيل الدخول أو الاشتراكات أو غيرها هي نماذج أصلية تم إنشاؤها بواسطة تطبيقك ولم يزوّرها طرفٌ ثالث.
  • SECURE_BROWSER_XSS_FILTER: يضع هذا المحدد الترويسة X-XSS-Protection: 1; mode=block في كافة الردود الصادرة عن التطبيق ما لم تكن موجودة أساسًا، يمنع هذا الإعداد أي طرف ثالث من حقن سكربتات برمجية في مشروعك، فعلى سبيل المثال لو خزن مستخدمٌ ما سكربتًا في قاعدة بياناتك مستغلًا أحد الحقول العامة، فلن يعمل هذا السكربت عند استرجاعه وعرضه للمستخدمين الآخرين.

يمكنك الحصول على مزيد من المعلومات حول الإعدادات الأمنية بالاطلاع على توثيقات جانغو.

تحذير: توصي توثيقات جانغو بعدم الاعتماد كليًا على SECURE_BROWSER_XSS_FILTER وإغفال الجوانب الأخرى، بل عليك دائمًا التأكد من ضبط حقول الإدخال لتطبيقك لتقبل المدخلات الصالحة فقط.

إعدادات إضافية

تتعلق الإعدادات الإضافية الواردة أدناه بأمن النقل الصارم HSTS الذي يفرض استخدام SSL على كامل موقعك وفي كل الأوقات:

  • SECURE_HSTS_SECONDS: يعين المدة الزمنية المحددة لاستخدام HSTS مقدرةً بالثانية، ففي حال ضبطت القيمة إلى ساعة واحدة (بالطبع حولها إلى ثواني قبل كتابتها) فهذا يعني أن متصفحك سيستخدم HTTPS حصرًا لمدة ساعة كاملة في كل مرة تزور فيها إحدى صفحات التطبيق، وإن زُرت أي جزء غير آمن من الموقع خلال هذه الساعة فإنك ستحصل على رسالة خطأ.
  • SECURE_HSTS_PRELOAD: استخدامه مشروط بتعيين قيمة للمحدد السابق أي زمن HSTS، يوجه هذا النوع من الترويسات متصفح الإنترنت ليُحمّل موقعك مسبقًا أي ليُضيفه على قائمة خاصة مدمجة مع كود المتصفح وظيفتها فرض استخدام الاتصالات المشفرة مع المواقع الموجودة ضمنها (ومن بينها موقعك)، تعمل هذه الطريقة التي يصطلح على تسميتها Preload مع المتصفحات الشائعة مثل Firefox و Chrome، ولكن احذر في حال اعتمدتها لموقعك فلن يكون التخلي عن التشفير أمرًا سهلًا بعدها، إذ سيتطلب إزالة الموقع يدويًا من قائمة HSTS Preload وقد تمتد العملية لأسابيع.
  • SECURE_HSTS_INCLUDE_SUBDOMAINS: تفعيله يعني تطبيق ترويسة HSTS على كافة النطاقات الفرعية تحت اسم نطاق الرئيس، فلو كان نطاقك your_domain فإن أي رابط فرعي تحته مثل unsecure.your_domain حتى لو كان لا يرتبط بتطبيق جانغو سيخضع للتشفير.

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

الإعدادات التي عرضناها تعد أساسًا جيدًا لمعظم تطبيقات جانغو، ولكن ادرس بيئتك الفعلية وفكر كيف سينعكس تطبيق هذه الإعدادات على مشروعك.

الخطوة 5: استخدام python-dotenv مع معلومات التطبيق السرية

ستتعلم في هذا الجزء الأخير كيفية الاستفادة المثلى من حزمة python-dotenv لحماية معلومات تطبيقك الحساسة مثل المفتاح السري SECRET_KEY ورابط تسجيل الدخول إلى لوحة التحكم، في الواقع تعدّ هذه الآلية فكرةً ممتازة لمن يرغب برفع تطبيقه على GitHub أو GitLab أو أي منصة أخرى مشابهة فهي ستحمي بياناته السرية من النشر أمام العامة، وبدلًا من نشرها على المنصة سينشئ المطور ملف env. جديد يحدد من خلاله المتغيرات السرية لكل بيئة جديدة يستخدمها سواء محليًا أو على خادم.

لنبدأ بإخفاء المفتاح السري SECRET_KEY، افتح الملف env.:

nano .env

واكتب ضمنه التالي:

DJANGO_SETTINGS_MODULE="django_hardening.settings.development"
SECRET_KEY="your_secret_key"

ثم افتح الملف base.py:

nano testsite/settings/base.py

وعدّل ضمنه المفتاح السري SECRET_KEY ليأخذ قيمته من متغيرات البيئة المعرفة في env. وفق التالي:

. . .
SECRET_KEY = os.getenv('SECRET_KEY')
. . .

أما عمليتنا الأخيرة فهي إخفاء عنوان URL للوحة التحكم عبر إضافة سلسلة طويلة من المحارف العشوائية إليه، سيحميك ذلك هجمات القوة الغاشمة brute force التي تتعرض لها مواقع الويب بهدف فرض حقول تسجيل الدخول عنوةً ومحاولة تخمينها من قبل المهاجم.

افتح الملف env. مجددًا:

nano .env

وأضف على محتوياته المحدد SECRET_ADMIN_URL:

DJANGO_SETTINGS_MODULE="django_hardening.settings.development"
SECRET_KEY="your_secret_key"
SECRET_ADMIN_URL="very_secret_url"

لنخفي الآن عنوان URL بالمحدد SECRET_ADMIN_URL، افتح الملف urls.py:

nano /testsite/urls.py

تنويه: لا تنسَ استبدال your_secret_key و very_secret_url بالسلاسل النصية التي تشكل مفتاحك السري وعنوان URL الخاص بك، وفي حال رغبت باستعمال سلاسل نصية عشوائية فإن بايثون يقدم لك مكتبة مميزة secrets.py لتوليدها، مع مجموعة مفيدة من الأمثلة لإنشاء برامج بسيطة تولد هذا النوع من السلاسل العشوائية الآمنة.

ومن ثم عدّله ليصبح كما يلي:

import os
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path(os.getenv('SECRET_ADMIN_URL') + '/admin/', admin.site.urls),
]

وبذلك يكون رابط لوحة التحكم لتطبيقك هو /very_secret_url/admin بدلًا من /admin/ فقط.

خاتمة

أعددنا في هذا المقال تطبيق جانغو سهل الاستخدام والنشر في بيئاتٍ مختلفة، ويستثمر ميزات python-dotenv للتعامل مع الإعدادات والأسرار، بالإضافة لكونه آمن ومجهز بضوابط الأمان الأساسية اللازمة لمرحلة الإنتاج، وفي حال طبقت المعايير التي ناقشناها والموصى بها فإنك ستحظى بتطبيقٍ آمن يحمل المزايا الأساسية التالية:

  • تشفير كافة الاتصالات عبر SSL/HTTPS (مثل: النطاقات الفرعية وملفات تعريف الارتباط وملفات ارتباط CSRF).
  • الحماية من هجمات البرمجة العابرة للمواقع XSS.
  • الحماية من هجمات تزوير الطلبات عبر المواقع CSRF.
  • إخفاء المفتاح السري للمشروع.
  • إخفاء رابط لوحة التحكم ما يمنع هجمات القوة الغاشمة brute force.
  • فصل إعدادات التطوير عن إعدادات الإنتاج.

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

ترجمة -وبتصرف- للمقال How To Harden the Security of Your Production Django Project لصاحبه Ari Birnbaum.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...