من الحاويات إلى kubernetes تأمين تطبيق Node.js يعمل على الحاويات باستخدام Nginx و Let’s Encrypt و Compose Docker


عبد الصمد العماري

هناك دائمًا طرق متعددة لتعزيز مرونة وأمان تطبيقك Node.js. ويتيح لك استخدام وكيل عكسي مثل Nginx إمكانية تحميل طلبات الرصيد وتخزين محتوى ثابت في ذاكرة التخزين المؤقت وتنفيذ بروتوكول أمان طبقة النقل (TLS) وهي اختصار لـTransport Layer Security. ويضمن تفعيل HTTPS المشفر على خادمك أن الاتصالات من وإلى تطبيقك تبقى آمنة.

يتضمن تطبيق وكيل عكسي باستخدام TLS/SSLعلى الحاويات مجموعة من الإجراءات مختلفة عن العمل مباشرة على نظام تشغيل مضيف. على سبيل المثال، إذا كنت تحصل على شهادات من Let’s Encrypt لتطبيق يعمل على الخادم، فستثبّت البرنامج المطلوب مباشرة على مضيفك. أما الحاويات فتسمح لك أن تسلك نهجا مختلفا. باستخدام Docker Compose، يمكنك إنشاء حاويات لتطبيقك، وخادم الويب الخاص بك، وعميل Certbot الذي سيتيح لك الحصول على شهاداتك. ويمكّنك اتباع هذه الخطوات من الاستفادة من مزايا النمطية وقابلية التنقل في سير عملٍ يعتمد على الحاويات.

في هذا الدرس، سوف تنشر تطبيق Node.js بوكيل عكسي Nginx باستخدام Docker Compose. وستحصل على شهادات TLS/SSL للمجال المرتبط بتطبيقك كما ستتأكد من حصوله على تصنيف أمان عالي من SSL Labs. وفي الأخير، سوف تنشئ وظيفة cron لتجديد شهاداتك بحيث يظل نطاقك آمنًا.

المتطلبات الأساسية

لمتابعة هذا البرنامج التعليمي، ستحتاج إلى ما يلي:

  • خادم أوبونتو 18.04 ومستخدم غير جذري ذي صلاحيات sudo وجدار حماية نشط. للحصول على إرشادات حول كيفية إعدادها، يرجى الاطلاع على دليل إعداد الخادم الأولي.
  • Docker مثبت على خادمك. للحصول على إرشادات حول تثبيت Docker، اتبع الخطوتين 1 و 2 للدليل كيفية تثبيت واستخدام Docker على Ubuntu 18.04. للحصول على إرشادات حول تثبيت Compose، اتبع الخطوة الأولى من كيفية تثبيت Docker Compose على أوبونتو 18.04.
  • اسم نطاق مسجل. سيستخدم هذا البرنامج التعليمي example.com طوال الوقت. يمكنك الحصول على نطاق مجاني من Freenom ، أو استخدام مسجل النطاقات الذي تختاره. سجلّا DNS التاليين المعدّين لخادمك. يمكنك متابعة هذه المقدمة إلى DNS في DigitalOcean للحصول على تفاصيل حول كيفية إضافتها إلى حساب DigitalOcean، إذا كنت تستخدمه:
  • سجل A يتضمن example.com يشير إلى عنوان IP العام لخادمك.
  • سجل A يتضمن www.example.com يشير إلى عنوان IP العام لخادمك.

الخطوة الأولى: استنساخ واختبار تطبيق Node

سوف نبدأ أولًا باستنساخ المستودع الذي يحتوي على شيفرة التطبيق Node، والذي يتضمن الملف Dockerfile الذي سنستخدمه لإنشاء صورة تطبيقنا اعتمادًا على Compose. ويمكننا البدء باختبار التطبيق عبر بنائه وتنفيذه باستخدام الأمر docker run دون وكيل عكسي أو شهادة SSL.

في المجلّد الرئيسي للمستخدم غير الجذري، استنسخ مستودع nodejs-image-demo من حساب DigitalOcean Community GitHub. يتضمن

مستودع التخزين هذا شيفرة الإعداد الموضح في كيفية إنشاء تطبيق Node.js باستخدام Docker.

انسخ المستودع في مجلّد يسمى node_project:

git clone https://github.com/do-community/nodejs-image-demo.git node_project

انتقل إلى المجلّد node_project:

cd  node_project

يوجد في هذا المجلّد ملفّ Dockerfile يحتوي على إرشادات حول بناء تطبيق Node باستخدام صورة Docker node:10 أضافة إلى محتويات مجلّد مشروعك الحالي. يمكنك إلقاء نظرة على محتويات Dockerfile بكتابة مايلي:

cat Dockerfile

المخرجات:

FROM node:10-alpine

RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app

WORKDIR /home/node/app

COPY package*.json ./

USER node

RUN npm install

COPY --chown=node:node . .

EXPOSE 8080

CMD [ "node", "app.js" ]

تبني هذه التعليمات صورة Node عبر نسخ شيفرة المشروع من المجلّد الحالي إلى الحاوية وتثبيت الاعتماديات باستخدام npm install. كما أنها تستخدم أيضًا التخزين المؤقت وطبقات الصور في Docker عبر فصل نسخة من package.json و package-lock.json، التي تحتوي على اعتماديات المشروع المدرجة، عن نسخة باقي شيفرة التطبيق. وتحدد هذه التعليمات كذلك أن تشغيل الحاوية سيكون عبر مستخدم Node غير الجذري بالأذونات المناسبة المعينة على شيفرة التطبيق والمجلّدات node_modules.

لمزيد من المعلومات حول أفضل ممارسات Dockerfile و Node image ، يرجى الاطلاع على التوضيحات في الخطوة 3 حول كيفية بناء تطبيق Node.js باستخدام Docker.

لاختبار التطبيق بدون SSL، يمكنك بناء الصورة وتعليمها باستخدام Docker والراية t-. سوف نسمّي الصورة node-demo، ولكن تبقى لك الحرية في إعطائها اسمًا آخر:

docker build -t node-demo .

بمجرد اكتمال عملية البناء، يمكنك عرض قائمة صورك باستخدام docker images:

docker images

سيظهر لك الإخراج التالي مؤكّدًا بناء صورة التطبيق:

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
node-demo           latest              23961524051d        7 seconds ago       73MB
node                10-alpine           8a752d5af4ce        3 weeks ago         70.7MB

بعد ذلك، أنشئ الحاوية باستخدام docker run. سوف نستخدم ثلاث رايات مع هذا الأمر:

  • p-: ينشر هذا المنفذ على الحاوية ويوجّهه إلى منفذٍ على مضيفنا. سنستخدم المنفذ 80 على المضيف، ولكن يمكنك تعديل هذا عند الضرورة إذا كان لديك عملية أخرى تشغَل هذا المنفذ. لمزيد من المعلومات، راجع هذه التوضيحات في توثيق Docker حول ربط المنافذ.
  • d-: يشغّل هذا الحاوية في الخلفية.
  • name- : يتيح لنا هذا إعطاء الحاوية اسمًا سهل التذكّر.

نفّذ الأمر التالي لإنشاء الحاوية:

docker run --name node-demo -p 80:8080 -d node-demo

تفقّد الحاويات قيد التشغيل باستخدام docker ps:

docker ps

سترى الإخراج التالي الذي يؤكد أن حاوية تطبيقك قيد التشغيل:

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
4133b72391da        node-demo           "node app.js"       17 seconds ago      Up 16 seconds  

يمكنك الآن زيارة عنوان نطاقك لاختبار إعداداتك: http://example.com. لا تنس تعويض example.com باسم نطاقك الخاص. سيعرض تطبيقك صفحة الهبوط التالية:

landing_page.png

الآن وبعد اختبار التطبيق، يمكنك إيقاف الحاوية وحذف الصور. استخدم docker ps مرة أخرى للحصول على معرّف الحاويات CONTAINER ID:

docker ps

المخرجات:

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
4133b72391da        node-demo           "node app.js"       17 seconds ago      Up 16 seconds

أوقف الحاوية باستخدام docker stop. تأكد من تعويض المعرِّف CONTAINER ID المدرج هنا بمعرّف تطبيقك CONTAINER ID:

docker stop 4133b72391da

يمكنك الآن حذف الحاوية المتوقفة وجميع الصور، بما في ذلك الصور غير المستخدمة والمعلّقة، باستخدام docker system prune والراية a-:

docker system prune -a

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

بعد اختبار صورة تطبيقك، يمكنك الانتقال إلى بناء بقية الإعداد باستخدام Docker Compose.

الخطوة الثانية: تحديد تكوين خادم الويب

يمكننا بعد إنشاء Dockerfile لتطبيقنا، إنشاء ملف تكوين لتشغيل حاوية Nginx الخاصة بنا. سنبدأ بالتكوين ذي الحد الأدنى الذي سيتضمن اسم نطاقنا، و الملف الجذري، ومعلومات الوكيل، وكتلة الموقع (Location block) لتوجيه طلبات Certbot إلى المجلّد well-known. إذ سيضع ملفًا مؤقتًا للتحقق من أن معلومات DNS لنطاقنا توجّه نحو خادمنا.

أنشئ أولاً مجلّدًا في مجلّد المشروع الحالي لملف التكوين:

mkdir nginx-conf

افتح الملف باستخدام nano أو المحرر المفضل لديك:

nano nginx-conf/nginx.conf

أضف كتلة الخادم التالية لطلبات المستخدم الوكيل إلى حاوية تطبيقك Node ولتوجيه طلبات Certbot إلى المجلّد well-known. لا تنس تعويض example.com باسم نطاقك الخاص:

server {
        listen 80;
        listen [::]:80;

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;

        server_name example.com www.example.com;

        location / {
                proxy_pass http://nodejs:8080;
        }

        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }
}

تسمح لنا كتلة الخادم هذه بتشغيل حاوية Nginx كبديل عكسي، والذي سيمرر الطلبات إلى حاوية تطبيقنا Node. سيسمح لنا أيضًا باستخدام المكوّن الإضافي webroot الخاص بـ Certbot للحصول على شهادات لنطاقنا. يعتمد هذا المكوّن الإضافي على طريقة التحقق من HTTP-01، والتي تستخدم طلب HTTP لإثبات أن Certbot يمكنه الوصول إلى الموارد من خادمٍ يوافق اسمَ نطاق معين.

احفظ الملف وأغلقه بمجرد الانتهاء من التحرير.

لمعرفة المزيد حول خوارزميات Nginx لخادم المواقع وكتلها، يرجى الرجوع إلى هذه المقالة حول فهم خوارزميات Nginx للخادم وحظر المواقع.

بعد تحديد تفاصيل تكوين خادم الويب، يمكننا الانتقال إلى إنشاء ملف docker-compose.yml، والذي سيتيح لنا إنشاء خدمات التطبيقات الخاصة بنا وحاوية Certbot التي سنستخدمها للحصول على شهاداتنا.

الخطوة الثالثة: إنشاء ملف Docker Compose

سيحدد ملف docker-compose.yml خدماتنا، بما في ذلك تطبيق Node وخادم الويب. سوف يحدد تفاصيل مثل وحدات التخزين المسماة، والتي ستكون ضرورية لمشاركة بيانات اعتماد SSL بين الحاويات، وكذلك معلومات الشبكة والمنفذ. سوف يسمح لنا أيضًا بتحديد أوامر محددة لتنفيذها عند إنشاء حاوياتنا. هذا الملف هو المورد المركزي الذي سيحدّد كيف ستعمل خدماتنا معًا.

افتح الملف في مجلّدك الحالي:

nano docker-compose.yml

حدد خدمة التطبيق أولًا:

version: '3'

services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped

يتضمن تعريف خدمة nodejs ما يلي:

  • Build: يحدّد هذا خيارات التكوين، بما في ذلك context وdockerfile، والتي ستطبّق عندما ينشئ Docker صورة التطبيق. إذا كنت ترغب في استخدام صورة موجودة من سجلٍّ مثل Docker Hub، فيمكنك استخدام التعليمة image عوض ذلك، مع معلومات حول اسم المستخدم والمستودع وعلامة الصورة.
  • Context: يعرّف هذا سياق البناء لصورة التطبيق. وهو في هذه الحالة مجلّد المشروع الحالي.
  • Dockerfile: يحدّد هذا ملف Dockerfile الذي سيستخدمه Compose للبناء وهو الملف Dockerfile الذي رأيناه في الخطوة الأولى.
  • Image و container_name: تعطي هذه أسماء للصورة والحاوية.
  • restart : هذا يحدّد سياسة إعادة التشغيل. تكون قيمته الافتراضية هي "لا"، لكننا عيّننا الحاوية لإعادة تشغيل ما لم يتم إيقافها.

لاحظ أننا لا نُضمّن وصلات الربط (bind mounts) مع هذه الخدمة، وذلك لأن إعدادنا يركز على النشر بدلاً من التطوير. لمزيد من المعلومات، يرجى الاطلاع على توثيق Docker على وصلات الربط والحجوم.

لتمكين الاتصال بين التطبيق وحاويات خادم الويب، سنضيف أيضًا شبكة جسرية (bridge network) تسمى app-network أسفل تعريف إعادة التشغيل:

services:
  nodejs:
...
    networks:
      - app-network

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

بعد ذلك، حدّد خدمة خادم الويب:

...
 webserver:
    image: nginx:mainline-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80" 
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
    depends_on:
      - nodejs
    networks:
      - app-network

تظل بعض الإعدادات التي حددناها لخدمة nodejs كما هي، لكننا أجرينا أيضًا التغييرات التالية:

  • image: يطلب هذا من Compose سحب أحدث صورة ألبية Nginx ‏(Alpine-based Nginx image) من Docker Hub. لمزيد من المعلومات حول الصور الألبية، يرجى الاطلاع على الخطوة الثالثة من كيفية إنشاء تطبيق Node.js باستخدام Docker.
  • ports: هذا يعرض المنفذ 80 لتفعيل خيارات التكوين التي حددناها في تكوين Nginx.

لقد حددنا أيضًا وحدات التخزين المسماة ووصلات الربط التالية:

  • web-root:/var/www/html: سيؤدي ذلك إلى إضافة الأصول الثابتة لموقعنا ، المنسوخة إلى وحدة تخزين تسمى web-root ، إلى مجلّد /var/www/html على الحاوية.
  • ./nginx-conf:/etc/nginx/conf.d: سيؤدي هذا إلى ربط دليل تكوين Nginx على المضيف بالمجلّد ذي الصلة على الحاوية، مع التأكد من أن أي تغييرات نجريها على الملفات الموجودة على المضيف ستنعكس في الحاوية.
  • certbot-etc:/etc/letsencrypt : سيؤدي ذلك إلى ربط شهادات ومفاتيح Let’s Encrypt لنطاقنا على المجلّد المناسب على الحاوية.
  • certbot-var:/var/lib/letsencrypt: يؤدي هذا إلى تحديث مجلّد العمل الافتراضي الخاص بـ Let's Encrypt إلى المجلّد المناسب على الحاوية.

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

...
  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos

يطلب هذا التعريف من Compose سحب صورة certbot/certbot من Docker Hub. كما أنّه يستخدم وحدات التخزين المسمّاة لمشاركة الموارد مع حاوية Nginx، بما في ذلك شهادات النّطاق والمفتاح في certbot-etc، ومجلّد عمل Let's Encrypt في certbot-var، وشيفرة التطبيق في web-root.

مرة أخرى، لقد استخدمنا depends_on لتحديد أن حاوية certbot يجب أن تشتغل بمجرد تشغيل الخدمة webserver.

لقد أضفنا أيضًا خيار command يحدد الأمر الذي سيتم تنفيذه عند بدء تشغيل الحاوية. وهو يتضمن الأمر الفرعي certonly مع الخيارات التالية:

  • webroot--: هذا يطلب من Certbot استخدام البرنامج المساعد webroot لوضع الملفات في مجلد webroot للتصديق.
  • webroot-path--: يحدد مسار المجلّد webroot.
  • email--: بريدك الإلكتروني المفضل للتسجيل والاسترداد.
  • agree-tos--: هذا يحدد أنك توافق على اتفاقية المشتركين في ACME.
  • no-eff-email--: هذا يخبر Certbot أنك لا ترغب في مشاركة بريدك الإلكتروني مع مؤسسة الحدود الإلكترونية Electronic Frontier Foundation (EFF). لا تتردد في حذف هذا إذا كنت تفضل ذلك.
  • staging--: هذا يخبر Certbot أنك ترغب في استخدام بيئة تشغيل Let's Encrypt للحصول على شهادات الاختبار. يتيح لك استخدام هذا الخيار اختبار خيارات التكوين وتفادي حدود طلبات النطاق المحتملة. لمزيد من المعلومات حول هذه الحدود، يرجى الاطلاع على التوثيق الخاص بمعدّل الحدود في Let's Encrypt.
  • d-: يتيح لك ذلك تحديد أسماء النطاق التي ترغب في تطبيقها على طلبك. في هذه الحالة، ضمّننا example.com و www.example.com. لا تنس تعويضها بتفضيلات النطاق الخاصة بك.

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

...
volumes:
  certbot-etc:
  certbot-var:
  web-root:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/views/
      o: bind

networks:
  app-network:
    driver: bridge

تتضمن وحدات التخزين المسماة لدينا شهادات Certbot ووحدات تخزين مجلّد العمل، ووحدة تخزين الأصول الثابتة لموقعنا، web-root. في معظم الحالات، يكون برنامج التشغيل الافتراضي لوحدات تخزين Docker هو برنامج التشغيل المحلي، والذي يقبل على Linux خيارات مماثلة للأمر mount. ونستطيع بفضل هذا تحديد قائمة خيارات برنامج التشغيل باستخدام driver_opts الذي يربط مجلّد views على المضيف، والذي يحتوي على أصول ثابتة للتطبيق، مع وحدة التخزين في وقت التشغيل. يمكن بعد ذلك مشاركة محتويات المجلّد بين الحاويات. لمزيد من المعلومات حول محتويات المجلّد views، يرجى الاطلاع على الخطوة الثانية من كيفية إنشاء تطبيق Node.js باستخدام Docker.

سيبدو ملف docker-compose.yml بهذا الشكل عند الانتهاء:

version: '3'

services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped
    networks:
      - app-network

  webserver:
    image: nginx:mainline-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
    depends_on:
      - nodejs
    networks:
      - app-network

  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos --no-eff-email --staging -d example.com  -d www.example.com 

volumes:
  certbot-etc:
  certbot-var:
  web-root:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/views/
      o: bind

networks:
  app-network:
    driver: bridge

بوضعك لتعريفات الخدمة في مكانها الصحيح، ستكون مستعدًّا لبدء تشغيل الحاويات واختبار طلبات الشهادة الخاصة بك.

الخطوة الرابعة: الحصول على شهادات SSL وبيانات الاعتماد

يمكننا أن نبدأ تشغيل حاوياتنا باستخدام docker-compose up، وهو الأمر الذي سيؤدي إلى إنشاء وتشغيل حاوياتنا وخدماتنا بالترتيب الذي حددناه. إذا كانت طلبات النطاق ناجحة، فستظهر حالة الخروج الصحيحة في الإخراج والشهادات الملائمة موصولة بالمجلد etc/letsencrypt/live/ على حاوية webserver.

أنشئ الخدمات باستخدام docker-compose up والراية d- التي ستشغّل حاويات nodejs و webserver في الخلفية:

docker-compose up -d

سيظهر لك الإخراج التالي الذي يؤكد أن خدماتك أُنشِئت:

Creating nodejs ... done
Creating webserver ... done
Creating certbot   ... done

تحقق من حالة خدماتك باستخدام docker-compose ps:

docker-compose ps

إذا كان كل شيء على ما يرام، فيجب أن تكون خدماتك nodejs و webserver في الحالة "Up" وستكون حاوية certbot في الحالة "0":

  Name                 Command               State          Ports
------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0
nodejs      node app.js                      Up       8080/tcp
webserver   nginx -g daemon off;             Up       0.0.0.0:80->80/tcp

إذا رأيت شيءًا آخر غير Up في عمود الحالة لخدمات node وwebserver، أو قيمة أخرى غير الصفر 0 في حالة الخروج للحاوية certbot، فاحرص على التحقق من سجلات الخدمة باستخدام الأمر docker-compose logs:

docker-compose logs service_name    

يمكنك الآن التحقق من تركيب بيانات الاعتماد على حاوية webserver باستخدام docker-compose exec:

docker-compose exec webserver ls -la /etc/letsencrypt/live

إذا كان الطلب ناجحًا، فسترى في الإخراج ما يلي:

total 16
drwx------ 3 root root 4096 Dec 23 16:48 .
drwxr-xr-x 9 root root 4096 Dec 23 16:48 ..
-rw-r--r-- 1 root root  740 Dec 23 16:48 README
drwxr-xr-x 2 root root 4096 Dec 23 16:48 example.com

الآن بعد تأكّدك من نجاح طلبك، يمكنك تحرير تعريف خدمة certbot من أجل حذف الراية staging--.

افتح docker-compose.yml:

nano docker-compose.yml

ابحث في الملف عن الجزء الذي يتضمن تعريف خدمة certbot، وعوّض الراية staging-- في الخيار command بالراية force-renewal--، والتي ستخبر Certbot أنك تريد طلب شهادة جديدة بنفس النطاقات التي في الشهادة الحالية. يجب أن يبدو تعريف خدمة certbot الآن كما يلي:

...
  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email sammy@example.com --agree-tos

يمكنك الآن تنفيذ docker-compose up لإعادة إنشاء حاوية certbot ووحدات التخزين ذات الصلة. سنضمّن هنا أيضًا الخيار no-deps-- لنطلب من Compose تخطي تشغيل خدمة webserver، لأنها قيد التشغيل بالفعل:

docker-compose up --force-recreate --no-deps certbot

سوف تشير المخرجات التي ستظهر لك إلى نجاح طلبك للشهادة:

certbot      | IMPORTANT NOTES:
certbot      |  - Congratulations! Your certificate and chain have been saved at:
certbot      |    /etc/letsencrypt/live/example.com/fullchain.pem
certbot      |    Your key file has been saved at:
certbot      |    /etc/letsencrypt/live/example.com/privkey.pem
certbot      |    Your cert will expire on 2019-03-26. To obtain a new or tweaked
certbot      |    version of this certificate in the future, simply run certbot
certbot      |    again. To non-interactively renew *all* of your certificates, run
certbot      |    "certbot renew"
certbot      |  - Your account credentials have been saved in your Certbot
certbot      |    configuration directory at /etc/letsencrypt. You should make a
certbot      |    secure backup of this folder now. This configuration directory will
certbot      |    also contain certificates and private keys obtained by Certbot so
certbot      |    making regular backups of this folder is ideal.
certbot      |  - If you like Certbot, please consider supporting our work by:
certbot      |
certbot      |    Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
certbot      |    Donating to EFF:                    https://eff.org/donate-le
certbot      |
certbot exited with code 0

بحصولك على الشهادات اللازمة، يمكنك الانتقال إلى تعديل تكوين Nginx لتضمين SSL.

الخطوة الخامسة: تعديل تكوين خادم الويب وتعريف الخدمة

سيتطلب تفعيل SSL في تكوين Nginx الخاص بنا إضافة إعادة توجيه HTTP إلى HTTPS وتحديد شهادة SSL والمواقع الرئيسية. سيتضمن أيضًا تحديد مجموعتنا Diffie-Hellman، والتي سنستخدمها في Perfect Forward Secrecy.

ونظرًا لأنك ستعيد إنشاء خدمة webserver لتضمين هذه الإضافات، فيمكنك إيقافها الآن:

docker-compose stop webserver

أنشئ بعد ذلك مجلّدًا في مجلّد المشروع الحالي من أجل مفتاحك Diffie-Hellman:

mkdir dhparam

ثم أنشئ مفتاحك باستخدام الأمر openssl:

sudo openssl dhparam -out /home/sammy/node_project/dhparam/dhparam-2048.pem 2048

سوف يستغرق الأمر بضع دقائق لإنشاء المفتاح.

لإضافة معلومات Diffie-Hellman و SSL ذات الصلة إلى تكوين Nginx، احذف أولاً ملفّ تكوين Nginx الذي أنشأته مسبقًا:

rm nginx-conf/nginx.conf

افتح نسخة أخرى من الملف:

nano nginx-conf/nginx.conf

أضف الشيفرة التالية إلى الملف لإعادة توجيه HTTP إلى HTTPS ولإضافة بيانات اعتماد SSL والبروتوكولات وترويسات الأمان (security headers). لاتنس تعويض example.com بنطاقك الخاص:

server {
        listen 80;
        listen [::]:80;
        server_name example.com www.example.com;

        location ~ /.well-known/acme-challenge {
          allow all;
          root /var/www/html;
        }

        location / {
                rewrite ^ https://$host$request_uri? permanent;
        }
}

server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name example.com www.example.com;

        server_tokens off;

        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

        ssl_buffer_size 8k;

        ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;

        ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
        ssl_prefer_server_ciphers on;

        ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

        ssl_ecdh_curve secp384r1;
        ssl_session_tickets off;

        ssl_stapling on;
        ssl_stapling_verify on;
        resolver 8.8.8.8;

        location / {
                try_files $uri @nodejs;
        }

        location @nodejs {
                proxy_pass http://nodejs:8080;
                add_header X-Frame-Options "SAMEORIGIN" always;
                add_header X-XSS-Protection "1; mode=block" always;
                add_header X-Content-Type-Options "nosniff" always;
                add_header Referrer-Policy "no-referrer-when-downgrade" always;
                add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
                # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
                # enable strict transport security only if you understand the implications
        }

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;
}

تحدد هذه الكتلة الخاصة بالخادم HTTP هوية الخادم webroot المستخدم لطلبات تجديد Certbot على الملف .well-known/acme-challenge. وتتضمن أيضًا توجيهًا لإعادة الكتابة (rewrite directive) يوجه طلبات HTTP إلى المجلّد الجذري إلى HTTPS.

أما الكتلة الخاصة بخادم HTTPS فيفعّل SSL و HTTP2. لقراءة المزيد حول كيفية تكرار HTTP/2 على بروتوكولات HTTP والمزايا التي يمكن أن يوفرها لتحسين أداء موقع الويب، يرجى الاطلاع على مقدمة كيفية إعداد Nginx مع دعم HTTP/2 على أوبونتو 18.04. يتضمن هذا الجزء أيضًا سلسلة من الخيارات للتأكد من أنك تستخدم أحدث بروتوكولات SSL والتشفيرات وأن تدبيس OSCP) OSCP stapling) قيد التشغيل. ويسمح لك تدبيس OSCP بتقديم استجابة ذات ختم زمني (time-stamped) من سلطة الإشهاد الخاصة بك أثناء عملية إنشاء الاتصال TLS الأولي، والتي يمكنها تسريع عملية المصادقة.

يحدّد الجزء أيضًا بيانات اعتماد SSL و Diffie-Hellman والمواقع الرئيسية.

ختامًا، لقد نقلنا معلومات مرور الوكيل إلى هذه الكتلة، بما في ذلك كتلة موقع تتضمّن توجيه try_files، لتوجيه الطلبات إلى حاوية تطبيق Node.js ذات تسمية بديلة، وكتلة موقع لتلك التسمية البديلة، والتي تتضمن ترويسات الأمان التي ستمكننا من الحصول على تقييمات حول أشياء مثل مواقع اختبار خادم SSL Labs ومواقع الأمان. تتضمن هذه الترويسات خيارات X-Frame-Options و X-Content-Type-Options وسياسة الإحالة وسياسة أمان المحتوى و X-XSS-Protection. يتم التصريح علنًا بالترويسة HSTS (اختصار للعبارة HTTP Strict Transport Security)، فعّل هذه فقط إذا فهمت الآثار المترتبة على ذلك وعملت على تقييم الوظيفة "التحميل المسبق" (preload).

احفظ الملف وأغلقه عند الانتهاء من التحرير.

قبل إعادة إنشاء خدمة webserver، ستحتاج إلى إضافة بعض الأشياء إلى تعريف الخدمة في ملف docker-compose.yml ، بما في ذلك معلومات المنفذ ذات الصلة لـ HTTPS وتعريف وحدة تخزين Diffie-Hellman.

افتح الملف:

nano docker-compose.yml

أضف في تعريف خدمة webserver تعيين المنفذ التالي ووحدة التخزين المسماة dhparam:

...
 webserver:
    image: nginx:latest
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - dhparam:/etc/ssl/certs
    depends_on:
      - nodejs
    networks:
      - app-network

أضف بعد ذلك وحدة تخزين dhparam إلى تعريفات وحدات التخزين الخاصة بك:

...
volumes:
  ...
  dhparam:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/dhparam/
      o: bind

على غرار وحدة التخزين web-root، ستركّب وحدة تخزين dhparam مفتاح Diffie-Hellman المخزن على المضيف في حاوية webserver.

احفظ الملف وأغلقه عند الانتهاء من التحرير.

ثم أعد إنشاء خدمة webserver:

docker-compose up -d --force-recreate --no-deps webserver

وتحقق من خدماتك باستخدام docker-compose ps:

docker-compose ps

يجب أن تشاهد الإخراج يشير إلى أن العقدة وخدمات خادم الويب تعمل:

  Name                 Command               State                     Ports
----------------------------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0
nodejs      node app.js                      Up       8080/tcp
webserver   nginx -g daemon off;             Up       0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp

ختامًا، يمكنك زيارة عنوان نطاقك لضمان عمل كل شيء على النحو المنتظر. انتقل في متصفحك إلى https://example.com، مع الحرص على تعويض example.com باسم نطاقك. ستظهر لك صفحة الهبوط التالية:

وينبغي أن تشاهد أيضًا رمز القفل في مؤشر أمان متصفحك. إذا كنت ترغب في ذلك، يمكنك الانتقال إلى صفحة الهبوط لاختبار SSL Labs Server أو صفحة اختبار خادم ترويسات الأمان. يجب أن تمنح خيارات التكوين التي أدرجناها لموقعك تصنيفًا على كليهما.

الخطوة السادسة: تجديد الشهادات

تكون شهادات Let’s Encrypt صالحة لمدة 90 يومًا، لذلك ستحتاج إلى إعداد عملية تجديد تلقائية حرصًا على عدم انقضاء صلاحيتها. إحدى الطرق للقيام بذلك هي إنشاء وظيفة باستخدام أداة جدولة cron. في هذه الحالة، سنقوم بجدولة مهمة cron باستخدام سكربتٍ لتجديد شهاداتنا وإعادة تحميل تكوين Nginx الخاص بنا.

افتح سكربتًا بالاسم ssl_renew.sh في مجلّد مشروعك:

nano ssl_renew.sh

أضف الشيفرة التالية إلى السكربت لتجديد شهاداتك وإعادة تحميل تكوين خادم الويب الخاص بك:

#!/bin/bash

/usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml run certbot renew --dry-run \
&& /usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml kill -s SIGHUP webserver

بالإضافة إلى تحديد موقع ملف docker-compose الثنائي، فإننا نحدد أيضًا موقع ملف docker-compose.yml الخاص بنا من أجل تشغيل أوامر docker-compose. في هذه الحالة، نستخدم docker-compose run لتشغيل حاوية certbot ولإبطال الأمر command المقدم في تعريف خدمتنا بأمر آخر: وهو الأمر الفرعي renew، والذي سيقوم بتجديد الشهادات التي تكون على وشك الانتهاء. لقد قمنا بتضمين خيار dry-run-- هنا لاختبار السكربت.

ثم يستخدم السكربت kill docker-compose لإرسال إشارة SIGHUP إلى حاوية خادم الويب لإعادة تحميل تكوين Nginx. لمزيد من المعلومات حول استخدام هذه العملية لإعادة تحميل تكوين Nginx، يرجى الاطلاع على منشور مدونة Docker هذا عن نشر صورة Nginx الرسمية باستخدام Docker.

أغلق الملف عند الانتهاء من التحرير. ثم اجعله قابلًا للتنفيذ:

chmod +x ssl_renew.sh

بعد ذلك، افتح ملف الجذر crontab الخاص بك لتنفيذ سكربت التجديد في مجال زمني محدد:

sudo crontab -e 

إذا كانت هذه هي المرة الأولى التي تقوم فيها بتحرير هذا الملف، فسيُطلب منك اختيار محرّر:

no crontab for root - using an empty one
Select an editor.  To change later, run 'select-editor'.
  1. /bin/ed
  2. /bin/nano        <---- easiest
  3. /usr/bin/vim.basic
  4. /usr/bin/vim.tiny
Choose 1-4 [2]: 
...

أضف السطر التالي في الجزء السفلي من الملف:

...
*/5 * * * * /home/sammy/node_project/ssl_renew.sh >> /var/log/cron.log 2>&1

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

بعد خمس دقائق، تحقق من cron.log لمعرفة ما إذا كان طلب التجديد قد نجح أم لا:

tail -f /var/log/cron.log

يجب أن يظهر لك الإخراج التالي مؤكِّدًا نجاح التجديد:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Killing webserver ... done

يمكنك الآن تعديل ملف crontab لتعيين مجال زمني يومي. لتنفيذ السكربت كل يوم عند الظهر، على سبيل المثال، يمكنك تعديل السطر الأخير من الملف ليبدو كما يلي:

...
0 12 * * * /home/sammy/node_project/ssl_renew.sh >> /var/log/cron.log 2>&1

ستحتاج أيضًا إلى حذف الخيار dry-run-- من السكربت ssl_renew.sh:

#!/bin/bash

/usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml run certbot renew \
&& /usr/local/bin/docker-compose -f /home/sammy/node_project/docker-compose.yml kill -s SIGHUP webserver

ستضمن مهمة cron ألا تنقضي صلاحية شهاداتك " Let’s Encrypt" عبر تجديدها عند الاقتضاء. يمكنك أيضًا إعداد تدوير السجل باستخدام الأداة المساعدة Logrotate لتدوير وضغط ملفات السجل.

خاتمة

لقد استخدمت حاوياتٍ لإعداد وتشغيل تطبيق Node باستخدام وكيل Nginx عكسي. كما حصلت أيضًا على شهادات SSL لنطاق تطبيقك وأعددت وظيفة cron لتجديد هذه الشهادات عند الضرورة.

إذا كنت مهتمًا بمعرفة المزيد عن Let's Encrypt Plugins، فيرجى الاطلاع على مقالاتنا حول استخدام المكون الإضافي Nginx أو المكون الإضافي المستقل.

يمكنك أيضًا معرفة المزيد حول Docker Compose من خلال الاطلاع على الموارد التالية:

ترجمة -وبتصرف- للمقال How To Secure a Containerized Node.js Application with Nginx, Let's Encrypt, and Docker Compose لصاحبته Kathleen Juell





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


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



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن