راه‌های مختلفی برای ارتقای انعطاف‌پذیری و امنیت یک اپلیکیشن Node.js وجود دارند. استفاده از یک پروکسی بازگشتی مانند Nginx به شما این امکان را می‌دهد که درخواست متعادلی را بارگذاری کنید، محتوای استاتیک داشته باشید و از قابلیت موسوم به «امنیت لایه انتقال» یا TLS استفاده نمایید. فعال‌سازی پروتکل رمزنگاری‌شده HTTPS در سرور موجب می‌شود که امنیت ارتباطی اپلیکیشن شما حفظ شود.

بکارگیری یک پروکسی بازگشتی همراه با TLS/SSL در کانتینرها شامل یک سری مراحل مستقیم در سیستم‌عامل میزبان می‌شود. به عنوان مثال، اگر گواهی‌ها از طریق Let’s Encrypt برای اپلیکیشن در حال اجرا بر روی سرور به‌دست می‌آیند، شما می‌بایست نرم‌افزار موردنیاز خود را مستقیماً‌ بر روی هاست نصب کنید. کانتینرها شرایطی فراهم می‌کنند که بتوانید که از رویکرد متفاوت در این زمینه استفاده کنید. شما با استفاده از Docker Compose قادر خواهید بود که برای اپلیکیشن، وب‌سرور و کلاینت Certbot، کانتینر ایجاد کنید. کلاینت Certbot همان ابزاری است که امکان دسترسی به گواهی‌های SSL را به شما می‌دهد. با دنبال کردن کردن مراحل زیر، می‌توانید از مزایای یک جریان کاری انعطاف‌پذیر و مقرون‌به‌صرفه بهره‌مند شوید.

در این آموزش، از یک اپلیکیشن Node.js همراه با یک پروکسی بازگشتی Nginx برای Docker Compose استفاده خواهیم کرد. گواهی‌های TLS/SSL دامین مرتبط با اپلیکیشن به‌دست می‌آیند و حتماً دقت می‌کنیم که رتبه امنیتی بالایی در SSL Labs داشته باشیم. نهایتاً یک عملیات cron برای تازه‌سازی گواهی‌ها به گونه‌ای تنظیم می‌کنیم که از امنیت دامین اطمینان حاصل گردد.

پیش‌نیازها

برای طی‌کردن مراحل این آموزش، موارد زیر موردنیاز هستند.

  • یک سرور اوبونتو 04، یک کاربر غیر روت با دسترسی‌های sudo و یک فایروال فعال. حتماً در این حالت، تنظیمات اولیه سرور را انجام داده باشید.
  • نصب Docker و Docker Compose در سرور. برای اطلاعات بیشتر می‌توانید به مقالات قبلی وبلاگ آریانت در این زمینه مراجعه کنید.
  • یک دامین ثبت‌شده. در سراسر این مطلب از دامین نمونه com استفاده می‌شود. از برخی جاها مانند Freenom امکان دریافت دامین رایگان وجود دارد و در عین حال، می‌توانید از مرجع ثبت دامین خودتان استفاده کنید.
  • تنظیم این دو رکورد DNS نیز برای سرور؛ یک رکورد A با هدایت com به آدرس IP عمومی سرور و یک رکورد A با هدایت www.example.com باز هم به آدرس IP عمومی سرور.

گام ۱) شبیه‌سازی و تست اپلیکیشن Node

به عنوان گام نخست، منبع را با کد اپلیکیشن Node شبیه‌سازی می‌کنیم که شامل Dockerfile مورد استفاده برای ساخت ایمیج اپلیکیشن با Compose می‌شود. آزمایش اولیه به این صورت است که پس از ساخت اپلیکیشن، بدون استفاده از پروکسی بازگشتی یا SSL، با کمک فرمان docker run آن را اجرا می‌کنیم.

در دایرکتوری خانگی کاربری که دسترسی روت ندارد، منبع nodejs-image-demo را از گیت‌هاب دریافت کنید. این منبع شامل کدهای موردنیاز برای ساخت یک اپلیکیشن Node.js با داکر نیز می‌شود.

این منبع را داخل یک دایرکتوری با نام 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 نصب می‌شوند. همچنین با جداسازی کپی‌های package.json و package-lock.json از کپی‌‌های ساید بخش‌های کد اپلیکیشن، از مزایای کچینگ و لایه‌بندی ایمیج داکر استفاده می‌شود. نهایتاً این دستورالعمل‌ها مشخص می‌کنند که کانتینر با کاربری غیر روت با دسترسی‌های مناسب تنظیم‌شده در کد اپلیکیشن و دایرکتوری‌های node_modules اجرا می‌گردد.

برای تست اپلیکیشن بدون SSL می‌توانید برای ساخت و برچسب‌زدن ایمیج از گزینه -t در فرمان docker build استفاده کنید. در این عنوان 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 ایجاد می‌کنیم. سه گزینه در این فرمان به کار ما می‌آیند:
<ul>
<li>-p : این گزینه باعث انتشار پورت به کانتینر و ارجاع آن به یک پورت در هاست ما می‌شود. در اینجا از پورت 80 برای این منظور استفاده می‌کنیم، ولی شما می‌توانید در صورت لزوم، شماره پورت دیگری وارد کنید.</li>
<li>-d : این گزینه موجب اجرای کانتینر در پس‌زمینه می‌شود.</li>
<li>–name: با استفاده از این گزینه می‌توانیم یه عنوان خاص به کانتینر اختصاص دهیم.</li>
</ul>
فرمان زیر را برای ساخت کانتینر اجرا کنید.

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       0.0.0.0:80->8080/tcp   node-demo

اکنون می‌توانید با رفتن به آ‌درس دامین، از نصب خود مطمئن شوید. در نتیجه، با صفحه لندینگ اپلیکیشن به صورت زیر روبرو خواهید شد.

صفحه لندینگ اپلیکیشن Node

صفحه لندینگ اپلیکیشن Node

بعد از تست اپلیکیشن می‌توانید کانتینر را متوقف کرده و ایمیج‌ها را حذف کنید. دوباره فرمان 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       0.0.0.0:80->8080/tcp   node-demo

با فرمان docker stop کانتینر را متوقف نمایید. حتماً دقت کنید که شناسه کانتینر خودتان را در این فرمان وارد کنید.

docker stop 4133b72391da

حالا می‌توانید کانتینر متوقف‌شده و تمام ایمیج‌ها را حذف کنید.

docker system prune -a

برای تأیید حذف این موارد، کلید حرف Y را فشار دهید. به خاطر داشته باشید که این کار باعث پاک‌شدن حافظه نهان داکر نیز می‌شود.

پس از تست ایمیج اپلیکیشن می‌توانید به سراغ سایر قسمت‌ها با استفاده از Docker Compose بروید.

گام ۲) تعریف تنظیمات وب‌سرور

پس از قرارگیری Dockerfile در جای مناسب، می‌توانیم یک فایل تنظیمات برای اجرای کانتینر Nginx بسازیم. کار با یک تنظیمات حداقلی شامل نام دامین، مسیر ریشه داکیومنت، اطلاعات پروکسی و یک بلوک موقعیت برای هدایت  درخواست Certbot به دایرکتوری .well-known آغاز می‌شود. در دایرکتوری .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 هدایت شوند. همچنین به این وسیله می‌توانیم از پلاگین وب‌روت Certbot برای به‌دست آوردن گواهی‌های دامین خود استفاده کنیم. این پلاگین به روش تأییدیه HTTP-01 وابسته است. در این روش، از یک درخواست HTTP برای اثبات توانایی دسترسی Certbot به منابع سرور مربوط به دامین استفاده می‌شود.

وقتی ویرایش به اتمام رسید، فایل را ذخیره کرده و ببندید.

پس از کامل‌شدن جزئیات تنظیمات وب‌سرور می‌توانیم به سراغ ساخت فایل 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 است. این گزینه‌ها در هنگام ساخت ایمیج اپلیکیشن توسط Compose به کار گرفته می‌شوند. اگر قصدتان استفاده از ایمیج‌های آماده از منبعی مانند Docker Hub است، می توانید به جای آن، از image instruction استفاده کنید که شامل اطلاعاتی در مورد نام کاربری، منبع و برچسب ایمیج خواهد بود.
  • context: در این بخش، کانتکست ساخت برای ساخت ایمیج اپلیکیشن تعریف می‌شود. در اینجا، از دایرکتوری کنونی پروژه استفاده می‌شود.
  • dockerfile: در اینجا Dockerfile مورد استفاده Compose برای ساخت مشخص می‌شود. یعنی همان Dockerfile که در گام اول داشتیم.
  • image, container_name: عنوان‌های ایمیج و کانتینر
  • restart: روند راه‌اندازی دوباره و ری‌استارت در اینجا تعریف می‌شود. حالت پیش‌فرض به صورت «غیرفعال» و گزینه no است. با این وجود باید کانتینر را برای راه‌اندازی دوباره تنظیم کنیم، مگر اینکه متوقف شده باشد.

به خاطر داشته باشید که در این سرویس bind mount نداریم. به این دلیل که تنظیمات ما متمرکز بر «به‌کارگیری» و نه «توسعه» است.

به منظور فعال‌سازی ارتباط بین اپلیکیشن و کانتینرهای وب‌سرور، یک شبکه بریج با نام 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 را از Docker Hub دریافت نماید.
  • ports: در اینجا، پورت 80 برای فعال‌سازی گزینه‌های تنظیمات Nginx مورد استفاده قرار می‌گیرد.

همچنین حجم‌ها و bind mount های زیر را تعریف کرده‌ایم:

  • web-root:/var/www/html: با این گزینه بخش‌های استاتیک سایت را اضافه می‌کنیم. این بخش‌ها در حجمی با عنوان web-root در دایرکتوری /var/www/htmlواقع در کانتینر کپی می‌شوند.
  • ./nginx-conf:/etc/nginx/conf.d: این گزینه موجب ارتباط bind mount بین دایرکتوری تنظیمات 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 [email protected] --agree-tos --no-eff-email --staging -d example.com  -d www.example.com

این تعریف موجب می‌شود که Compose ایمیج certbot/certbot را از Docker Hub دریافت کند. همچنین از حجم‌های یاد‌ شده برای به اشتراک گذاری منابع با کانتینر Nginx استفاده می‌شود. این منابع شامل مواردی چون گواهی‌ها و کلیدهای دامین در certbot-etc، دایرکتوری Let’s Encrypt در certbot-var و کد اپلیکیشن در web-root می‌شوند.

همچنین از depends_on برای مشخص‌کردن این موضوع که کانتینر certbot می‌بایست حتماً‌ بعد از اجرای سرویس وب‌سرور، شروع به کار کند، استفاده کرده‌ایم.

به همین ترتیب، از گزینه command استفاده کرده‌ایم که موجب اجرای فرمان بعد از شروع به کار کانتینر می‌شود. آیتم‌های زیر در این فرمان به کار رفته‌اند:

  • –webroot: استفاده از پلاگین webroot برای جانمایی فایل‌ها در فولدر webroot جهت تأییدیه.
  • –webroot-path: مسیر دایرکتوری webroot
  • –email: ایمیل ترجیحی برای ثبت‌نام و بازیابی
  • –agree-tos: موافقت با توافقنامه عضویت ACME
  • –no-eff-email: عدم‌ به اشتراک‌گذاری ایمیل با بنیاد الکترونیک فرونتیر یا EFF. در صورت تمایل می‌توانید این مورد را حذف کنید.
  • –staging: این گزینه تمایل شما را برای استفاده از محیط استیجینگ Let’s Encrypt برای دریافت گواهی‌های آزمایشی نشان می‌دهد. استفاده از این گزینه این امکان را برایتان ایجاد می‌کند و گزینه‌های مختلف تنظیمات را امتحان کنید و از برخی محدودیت‌های درخواست دامین در امان بمانید.
  • -d: برای مشخص‌کردن عنوان دامین موردنظر به منظور ثبت درخواست. در اینجا، از آدرس‌های 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 به صورت local driver است که در لینوکس گزینه‌های مشابه با فرمان mount را قبول می‌کند. در نتیجه، می‌توانیم با استفاده از driver_opts، فهرستی از گزینه‌های درایور را بین دایرکتوری هاست و حجم ران‌تایم در اختیار داشته باشیم. در این حالت، محتوای دایرکتوری می‌تواند بین کانتینرها به اشتراک گذاشته شود.

نهایتاً فایل docker-compose.yml تکمیل‌شده به صورت زیر خواهد بود:

version: '3'

&nbsp;

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 [email protected] --agree-tos --no-eff-email --staging -d example.com  -d www.example.com

&nbsp;

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 با پیام خروج صفر نشان داده می‌شوند.

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 برای سرویس‌ها و یا وضعیت خروجی غیر از صفر برای کانتینر certbot وجود داشته باشد، حتماً وضعیت سرویس را با فرمان docker-compose logs بررسی کنید.

docker-compose logs service_name

همچنین با استفاده از فرمان docker-compose exec می‌توانید وضعیت اعتبار گواهی را در کانتینر webserver بررسی کنید.

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 را با –force-renewal جایگزین کنید. در حال حاضر، تعریف سرویس 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 [email protected] --agree-tos --no-eff-email --force-renewal -d example.com -d www.example.com

...

حالا می‌توانید فرمان docker-compose up را اجرا کنید تا کانتینر certbot و حجم‌های مربوط به آن ایجاد شوند. همچنین از گزینه –no-deps برای عبور ار مرحله شروع سرویس 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 به منظور «محرمانگی پیشرو» یا PFF نیز می‌شود.

در اینجا می‌خواهیم سرویس 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 اضافه کنید. حتماً به یاد داشته باشید که آدرس example.com را با دامین خودتان جایگزین کنید.

server {

listen 80;

listen [::]:80;

server_name example.com www.example.com;

&nbsp;

location ~ /.well-known/acme-challenge {

allow all;

root /var/www/html;

}

&nbsp;

location / {

rewrite ^ https://$host$request_uri? permanent;

}

}

&nbsp;

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 server مشخص می‌کند که webroot برای درخواست‌های تازه‌سازی Certbot در دایرکتوری .well-known/acme-challenge مورد استفاده قرار گیرد. همچنین این بلوک شامل یک ارجاع‌دهنده درخواست‌های HTTP دایرکتوری روت به HTTPS می‌شود.

بلوک HTTPS server موجب فعال‌سازی ssl و http2 می‌شود. همچنین این بلوک شامل یک سری گزینه‌ها برای اطمینان از کاربرد آخرین نسخه پروتکل‌ها  و  رمزنگاری‌های SSL و فعال‌بودن استپلینگ OSCP است. استپلینگ OSCP این امکان را به شما می‌دهد که در طول فرآیند اولیه TLS handshake، بتوانید پاسخ بر اساس فواصل زمانی از مرجع صدور گواهی داشته باشید. این کار مسلماً باعث افزایش سرعت فرآیند تأییدیه می‌شود.

این بلوک همچنین موقعیت اعتبارها و کلید SSL و Diffie-Hellman را مشخص می‌کند.

نهایتاً اطلاعات عبور پراکسی را در این بلوک وارد کرده‌ایم که شامل یک بلوک location با پارامتر try_files می‌شود. این پارامتر درخواست‌ها را به کانتینر اپلیکیشن Node.js ما هدایت می‌کند. همین‌طور یک بلوک location شامل هدرهای امنیتی که موجب کسب رتبه A از مراجعی مانند SSL Labs و وب‌سایت Security Headers می‌شود.

وقتی کار ویرایشتان تمام شد، فایل را ذخیره کرده و آن را ببندید.

قبل از اینکه سرویس 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

در خروجی می‌بایست اجرای سرویس‌های nodejs و webserver را مشاهده کنید.

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

نهایتاً از دامین خود بازدید کنید تا از درست‌بودن تمام موارد مطابق انتظار مطمئن شوید. در نتیجه، صفحه لندینگ زیر برایتان نمایش داده می‌شود.

صفحه لندینگ اپلیکیشن Node

صفحه لندینگ اپلیکیشن Node

همچنین باید آیکون قفل را در نمایشگر امنیتی مرورگر خود ببینید. در صورت تمایل، می توانید به صفحه لندینگ SSL Labs Server Test و یا سرور تست Security Headers بروید. تنظیماتی که قبلاً اِعمال کردیم، می‌توانند در هر دوی این صفحات رتبه A را برایتان ایجاد کنند.

گام ۶) تازه‌سازی گواهی‌ها

گواهی‌های Let’s Encrypt برای مدت ۹۰ روز معتبر خواهند بود. بر این اساس، می‌بایست یک الگوی تازه‌سازی گواهی‌ها تنظیم کنید تا گواهی‌های منقضی‌شده مورد استفاده قرار نگیرند. یکی از روش‌های برای این منظور، ابزار زمان‌بندی انجام کارها موسوم به cron است. در اینجا ما با استفاده از یک اسکریپت، یک وظیفه cron برای تازه‌سازی گواهی‌ها و بارگذاری دوباره تنظیمات Nginx ایجاد می‌کنیم.

برای این منظور، یک اسکریپت با نام ssl_renew.sh در دایرکتوری پروژه باز کنید.

nano ssl_renew.sh

حالا کد زیر را به اسکریپت اضافه کنید.

#!/bin/bash

COMPOSE=”/usr/local/bin/docker-compose --no-ansi”

DOCKER=”/usr/bin/docker”

cd /home/ammy/node_project/

$COMPOSE run certbot renew --dry-run && $COMPOSE kill -s SIGHUP webserver

$DOCKER system prune -af

این اسکریپت ابتدا کد باینری docker-compose را به صورت یک متغیر با نام COMPOSE درنظر می‌گیرد. همچنین استفاده از گزینه –no-ansi باعث می‌شود که فرمان‌های docker-compose بدون کنترل کاراکترهای ANSI اجرا شوند. همین کار با باینری docker نیز انجام می‌گیرد. نهایتاً دایرکتوری به ~/node_project تغییر پیدا می‌کند و فرمان‌های docker-compose به صورت زیر اجار می‌گردند.

  • docker-compose run: اجرای یک کانتینر certbot و بازیابی فرمان تعریف‌شده در سرویس certbot. به جای استفاده از زیرفرمان certonly، از renew استفاده می‌کنیم. در نتیجه، گواهی‌هایی که نزدیک به تاریخ انقضای خود هستند، تازه‌سازی می‌شوند. گزینه –dry-run برای تست اسکریپت وارد شده است.
  • docker-compose kill: یک سیگنال SIGHUP به کانتینر webserver ارسال می‌شود تا تنظیمات Nginx بارگذاری شوند.

سپس اجرای فرمان docker system prune برای حذف کلیه کانتینرها و ایمیج‌های بلااستفاده انجام می‌شود.

وقتی کار ویرایش به اتمام رسید، فایل را ببندید. حالا آن را به فایل اجرایی تبدیل کنید

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'.
<ol>
 	<li>/bin/ed</li>
 	<li>/bin/nano <---- easiest</li>
 	<li>/usr/bin/vim.basic</li>
 	<li>/usr/bin/vim.tiny</li>
</ol>
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.)

&nbsp;

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

COMPOSE="/usr/local/bin/docker-compose --no-ansi"

DOCKER="/usr/bin/docker"

cd /home/sammy/node_project/

$COMPOSE run certbot renew && $COMPOSE kill -s SIGHUP webserver

$DOCKER system prune -af

این وظیفه cron باعث می‌شود که از عدم‌ منقضی‌شدن گواهی‌های Let’s Encrypt اطمینان حاصل گردد. همچنین می‌توانید با استفاده از ابزاری مانند Logrotate حجم کمتری برای فایل‌های ثبت تاریخچه داشته باشید.

جمع‌بندی

در اینجا از کانتینرها برای تنظیم و اجرای یک اپلیکیشن Node همراه با پروکسی بازگشتی Nginx استفاده کردیم. همچنین گواهی‌های امنیتی SSL برای دامین اپلیکیشن و یک وظیفه cron برای تازه‌سازی این گواهی‌ها تنظیم شد. امیدواریم که این مطلب نیز مورد استفاده شما قرار گرفته باشد و در ادامه مطالب وبلاگ آریانت نیز با ما همراه باشید.