Защита контейнерного приложения Node.js с помощью Nginx, Let’s Encrypt и Docker Compose

Существует несколько способов повысить гибкость и безопасность приложения Node.js. Использование обратного прокси-сервера, такого как Nginx, дает вам возможность настроить балансировку нагрузки, кэшировать статический контент и реализовать TLS. Включение шифрования HTTPS на вашем сервере гарантирует, что связь с вашим приложением будет безопасной.

Внедрение обратного прокси-сервера с TLS/SSL для контейнеров включает другой набор процедур, нежели непосредственно в операционной системе хоста. Например, если вы получаете сертификаты Let’s Encrypt для приложения, работающего на сервере, вы должны установить необходимое программное обеспечение непосредственно на хост. Контейнеры позволяют использовать другой подход. С помощью Docker Compose вы можете создать контейнеры для приложения, веб-сервера и клиента Certbot, которые позволят вам получить сертификаты. Вы можете воспользоваться модульностью и портируемостью контейнерного рабочего процесса.

Этот мануал поможет вам развернуть приложение Node.js с обратным прокси-сервером Nginx с помощью Docker Compose. Вы получите TLS/SSL сертификаты для домена, связанного с приложением, и проверите уровень безопасности с помощью SSL Labs. Затем мы настроим cron для автоматического обновления сертификатов.

Читайте также: Планирование рутинных задач Linux при помощи Cron и Anacron

Требования

  • Сервер Ubuntu 18.04 с пользователем sudo и включенным брандмауэром, как описано здесь.
  • Docker и Docker Compose. Инструкции можно найти в мануалах Установка и использование Docker в Ubuntu 18.04 (разделы 1-2) и Установка Docker Compose в Ubuntu 18.04 (раздел 1).
  • Зарегистрированное доменное имя. В этом мануале мы используем example.com.
  • DNS-записи А для example.com и www.example.com, указывающие на внешний IP-адрес вашего сервера.

1: Клонирование и тестирование приложения

Для начала мы клонируем репозиторий кода приложения Node, в который входит Dockerfile, необходимый для сборки образа приложения с помощью Compose. Сначала можно протестировать приложение, собрав и запустив его с помощью команды docker run без обратного прокси и SSL.

В домашнем каталоге пользователя sudo клонируйте репозиторий nodejs-image-demo.

Клонируйте репозиторий в каталог node_project:

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

Перейдите в этот каталог:

cd  node_project

В этом каталоге есть Dockerfile, который содержит инструкции по созданию приложения Node с помощью образа 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 ./
RUN npm install
COPY . .
COPY --chown=node:node . .
USER node
EXPOSE 8080
CMD [ "node", "app.js" ]

Эти инструкции создают образ Node, копируя код проекта из текущего каталога в контейнер и устанавливая зависимости с помощью npm install. Они также используют преимущества кэширования и наложения образов в Docker, отделяя копию package.json и package-lock.json, содержащие перечисленные зависимости проекта, от копии остальной части кода приложения. Наконец, в инструкциях указывается, что контейнер будет запускаться как пользователь node без прав root с соответствующими правами доступа к коду приложения и каталогам node_modules.

Читайте также: Сборка оптимизированных контейнеров для Kubernetes

Чтобы протестировать приложение без SSL, вы можете создать и пометить образ, используя команду docker build и флаг -t. Мы назовем образ node-demo, но вы можете назвать его как-нибудь иначе:

docker build -t node-demo .

После завершения процесса сборки вы можете просмотреть список образов:

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

Вы увидите:

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

Теперь вы можете посетить свой домен, чтобы проверить установку:

http://example.com

Не забудьте заменить example.com своим собственным доменом. Ваше приложение отобразит домашнюю страницу приложения.

Теперь, когда вы протестировали приложение, вы можете остановить контейнер и удалить образы. Снова используйте 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. Обязательно замените CONTAINER ID, указанный здесь, своим значением.

docker stop 4133b72391da

Теперь вы можете удалить остановленный контейнер и все образы, включая неиспользуемые образы, с помощью утилиты docker system prune и флага -a:

docker system prune -a

Введите y по запросу, чтобы подтвердить, что вы хотите удалить остановленный контейнер и образы. Имейте в виду, что это также удалит ваш кэш сборки.

2: Определение конфигурации веб-сервера

Подготовив приложение с Dockerfile, мы можем создать файл конфигурации для запуска контейнера Nginx. Начнем с базовой конфигурации, которая будет включать в себя доменное имя, document root, информацию о прокси-сервере и блок location для направления запросов Certbot в каталог .well-known. Здесь будет помещен временный файл, подтверждающий, что DNS домена разрешается на сервер.

Сначала создайте в текущем каталоге проекта каталог для конфигурационного файла:

mkdir nginx-conf

Откройте файл в редакторе:

nano nginx-conf/nginx.conf

Добавьте следующий блок server  для проксирования запросов пользователей в контейнер приложений 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;
}
}

Этот блок server запустит контейнер Nginx в качестве обратного прокси-сервера, который будет передавать запросы контейнеру приложения Node. Он также позволяет использовать плагин Certbot webroot для получения сертификатов для домена. Этот плагин зависит от метода проверки HTTP-01, который использует HTTP-запрос, чтобы доказать, что Certbot может получить доступ к ресурсам с сервера, отвечающего на этот домен.

Сохраните и закройте файл.

Читайте также: Алгоритмы выбора блоков server и location в Nginx

Добавив конфигурации веб-сервера, мы можем перейти к созданию файла docker-compose.yml, который позволит создавать сервисы приложений и контейнер Certbot, необходимый для получения сертификатов.

3: Создание файла 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 с информацией о вашем пользователе, репозитории и теге образа.
  • context: определяет контекст для сборки образа приложения. В данном случае это текущий каталог проекта.
  • dockerfile: Здесь указывается Dockerfile, который Compose будет использовать для сборки – Dockerfile, который вы просматривали в разделе 1.
  • image, container_name: они определяют имена образа и контейнера.
  • restart: определяет политику перезапуска. По умолчанию используется no, но в этом случае контейнера будет перезапущен в любом случае, если только он не был остановлен.

Обратите внимание, мы не включаем bind mount, так как наша установка сосредоточена на развертывании, а не на разработке. Для получения дополнительной информации, пожалуйста, обратитесь к документации Docker по bind mount и томам.

Чтобы настроить взаимодействие между приложением и контейнерами веб-сервера, мы также добавим мостовую сеть под названием app-network после определения перезапуска:

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

Определяемая пользователем мостовая сеть типа этой обеспечивает связь между контейнерами на одном хосте демона Docker. Это оптимизирует трафик и связь в приложении, поскольку открывает все порты между контейнерами в одной и той же мостовой сети, не открывая порты для внешнего мира. Таким образом, вы можете выборочно открывать только те порты, которые необходимы для работы веб-интерфейса.

Затем определите сервис webserver:

...
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 mounts:

  • 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 user@example.com --agree-tos --no-eff-email --staging -d example.com  -d www.example.com

Это определение извлечет образ certbot/certbot из Docker Hub. Оно также использует именованные тома для совместного потребления ресурсов с контейнером Nginx, включая сертификаты домена и ключ в каталоге certbot-etc и код приложения в web-root.

Опять же, мы использовали depends_on, чтобы указать, что контейнер certbot должен запускаться после запуска сервиса webserver.

Мы также включили параметр command, который задает команду, необходимую для запуска контейнера. Она включает подкоманду certonly со следующими параметрами:

  • —webroot: позволяет Certbot использовать плагин webroot для размещения файлов в папке webroot для аутентификации.
  • —webroot-path: указывает путь к каталогу webroot.
  • —email: адрес электронной почты для регистрации и восстановления.
  • —agree-tos: означает, что вы принимаете ACME’s Subscriber Agreement.
  • —no-eff-email: указывает Certbot, что вы не хотите делиться своей электронной почтой с Electronic Frontier Foundation  (EFF). Если хотите, можете просто удалить эту опцию.
  • —staging: позволяет использовать промежуточную среду 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/user/node_project/views/
o: bind
networks:
app-network:
driver: bridge

Именованные тома включают в себя сертификат Certbot и тома рабочего каталога, а также том статических ресурсов сайта, web-root. В большинстве случаев драйвером по умолчанию для томов Docker является локальный драйвер, который в Linux принимает параметры, аналогичные команде mount. Благодаря этому можно указать список параметров драйвера с помощью driver_opts, что смонтирует каталог views на хосте, который содержит статические активы приложения, с томом в среде выполнения. Содержимое каталога затем может быть разделено между контейнерами.

В результате 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 user@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/user/node_project/views/
o: bind
networks:
app-network:
driver: bridge

Определив сервис, вы можете запустить контейнеры и протестировать процесс получения сертификатов.

4: Получение SSL-сертификатов

Мы можем запустить контейнеры с помощью команды docker-compose up, которая создаст и запустит контейнеры и сервисы в указанном нами порядке. Если запросы домена пройдут успешно, вы увидите правильное состояние в выходных данных и сертификаты, смонтированные в папке /etc/letsencrypt/live  в контейнере веб-сервера.

Создайте сервисы с помощью docker-compose up и флага -d, который запустит контейнеры nodejs и webserver в фоновом режиме:

docker-compose up -d

Вы увидите вывод, подтверждающий, что сервисы созданы:

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

Проверьте состояние сервисов:

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

Если в столбце State для nodejs и webserver вы видите что-то кроме Up или состояние контейнера certbot – не 0, обязательно проверьте логи сервисов с помощью команды 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 user@example.com --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

5: Отладка конфигурации веб-сервера

SSL в конфигурации Nginx подразумевает поддержку перенаправления HTTP на HTTPS. Также нужно определить пути к сертификату SSL и ключам и указать группу Диффи-Хеллмана, которую мы будем использовать для Perfect Forward Secrecy.

Чтобы обновить конфигурацию веб-сервера и включить эти дополнения, вы можете остановить его:

docker-compose stop webserver

Затем в каталоге проекта создайте каталог для ключа Диффи-Хеллмана:

mkdir dhparam

Сгенерируйте его с помощью команды openssl:

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

Создание ключа займет несколько минут.

Чтобы добавить соответствующую информацию Диффи-Хеллмана и 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;
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, которая направляет HTTP-запросы к корневому каталогу на HTTPS.

Блок server HTTPS включает ssl и http2. Этот блок также содержит ряд параметров, которые включают поддержку самых современных SSL-протоколов и шифров и OSCP stapling. OSCP stapling (или сшивание) позволяет вам предложить ответ с меткой времени от вашего центра сертификации во время начального рукопожатия TLS, что может ускорить процесс аутентификации.

Читайте также:

Также блок определяет расположение ключей SSL и Диффи-Хеллмана.

Мы также переместили в этот блок информацию о прокси. Он включает блок location с директивой try_files, который направляет запросы к контейнеру приложения с псевдонимом Node.js, и блок location для этого псевдонима, который включает заголовки безопасности (благодаря этому вы можете получить наивысшую оценку в тестах типа SSL Labs и Security Headers). Эти заголовки включают X-Frame-Options, X-Content-Type-Options, Referrer Policy, Content-Security-Policy и X-XSS-Protection. Заголовок HTTP Strict Transport Security (HSTS) закомментирован – включите его, только если вы понимаете последствия и оценили его функции предварительной загрузки.

Сохраните и закройте файл.

Прежде чем снова создать сервис webserver, необходимо добавить несколько обновлений в определение сервиса в docker-compose.yml, включая информацию о порте для HTTPS и определение тома Диффи-Хеллмана.

Откройте файл:

nano docker-compose.yml

В определение сервиса webserver добавьте следующее преобразование портов и dhparam по имени volume:

...
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.

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

Как и том web-root, том dhparam будет монтировать ключ Диффи-Хеллмана, хранящийся на хосте, в контейнере 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

Теперь откройте свой домен, чтобы убедиться, что все работает как положено. Перейдите в браузере по адресу https://example.com (заменив example.com собственным доменом). Вы увидите домашнюю страницу приложения.

Вы также должны увидеть символ зеленого замочка в индикаторе безопасности вашего браузера. При желании вы можете пройти SSL Labs Server Test или Security Headers server test. Учитывая включенные ранее параметры конфигурации, приложение должно пройти оба теста с оценкой А.

6: Обновление сертификатов

Сертификаты Let’s Encrypt действительны в течение 90 дней, поэтому нужно настроить автоматический процесс обновления, чтобы постоянно поддерживать шифрование. Один из способов сделать это – это создать задание для утилиты планирования cron. В этом случае мы запланируем задачу cron с помощью скрипта, который будет обновлять сертификаты и перезагружать конфигурацию Nginx.

Откройте сценарий ssl_renew.sh в каталоге проекта:

nano ssl_renew.sh

Добавьте в сценарий следующий код, который будет обновлять сертификаты и перезагружать конфигурацию веб-сервера:

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

Помимо расположения бинарного файла docker-compose нужно также указать расположение файла docker-compose.yml для запуска команд docker-compose. В этом случае мы используем команду docker-compose run для запуска контейнера certbot и переопределения команды, представленной в параметре command в определении сервиса, — вместо нее будет использоваться команда renew, которая обновит сертификаты, срок действия которых близок к истечению. Опция —dry-run позволяет протестировать скрипт.

Затем сценарий использует команду docker-compose kill для отправки сигнала SIGHUP в контейнер веб-сервера, чтобы перезагрузить конфигурацию Nginx. Дополнительную информацию об этом процессе перезагрузки конфигурации Nginx можно найти в блоге Docker (смотрите пост о развертывании официального образа Nginx с помощью Docker).

Закройте файл, когда вы закончите редактирование. Сделайте его исполняемым:

chmod +x ssl_renew.sh

Откройте crontab пользователя root, чтобы запустить сценарий обновления.

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/user/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/user/node_project/ssl_renew.sh >> /var/log/cron.log 2>&1

Также из сценария your ssl_renew.sh нужно удалить опцию —dry-run:

#!/bin/bash
/usr/local/bin/docker-compose -f /home/user/node_project/docker-compose.yml run certbot renew \

&& /usr/local/bin/docker-compose -f /home/user/node_project/docker-compose.yml kill -s SIGHUP webserver

Благодаря cron сертификаты Let’s Encrypt будут своевременно обновляться.

Заключение

Вы научились использовать контейнеры для настройки и запуска приложения Node с обратным прокси-сервером Nginx. Также вы теперь умете запрашивать сертификаты SSL для своего приложения и настраивать cron для обновления этих сертификатов.

Если вы хотите узнать больше о Let’s Encrypt, ознакомьтесь с нашими мануалами:

Дополнительную информацию о Docker Compose вы найдете в таких мануалах:

Также много полезной информации о мультиконтейнерных приложениях можно найти в документации Docker Compose.

Tags: , , , , , , ,