Tutorial

Обеспечение безопасности контейнеризованного приложения Node.js с помощью Nginx, Let's Encrypt и Docker Compose

Published on January 22, 2020
Русский
Обеспечение безопасности контейнеризованного приложения Node.js с помощью Nginx, Let's Encrypt и Docker Compose

Введение

Существует множество способов повышения гибкости и безопасности приложения Node.js. Использование обратного прокси-сервера, например Nginx, позволяет равномерно распределять запросы, кэшировать статический контент и реализовать протокол Transport Layer Security (TLS). Активация шифрования HTTPS на сервере гарантирует, что исходящие и входящие коммуникации вашего приложения останутся безопасными.

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

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

Предварительные требования

Для данного обучающего модуля вам потребуется следующее:

  • Сервер Ubuntu 18.04, пользователь без прав root с привилегиями sudo и активный брандмауэр. Дополнительную информацию о настройке этих параметров см. в руководстве по первоначальной настройке сервера.

  • Docker и Docker Compose, установленные на вашем сервере. Инструкции по установке Docker см. в шагах 1 и 2 руководства Установка и использование Docker в Ubuntu 18.04. Дополнительную информацию по установке Compose см. в шаге 1 руководства Установка Docker Compose в Ubuntu 18.04.

  • Зарегистрированное доменное имя. В этом обучающем руководстве мы будем использовать example.com. Вы можете получить бесплатный домен на Freenom или зарегистрировать доменное имя по вашему выбору.

  • На вашем сервере должны быть настроены обе приведенные ниже записи DNS. Вы можете воспользоваться введением в работу с DigitalOcean DNS, чтобы получить подробную информацию о добавлении доменов в учетную запись DigitalOcean, если вы используете этот способ:

    • Запись A, где example.com указывает на публичный IP-адрес вашего сервера.
    • Запись A, где www.example.com указывает на публичный IP-адрес вашего сервера.

Шаг 1 — Клонирование и тестирование приложения Node

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

В корневой директории пользователя без прав root клонируйте репозиторий nodejs-image-demo из учетной записи DigitalOcean на GitHub. Этот репозиторий содержит код настройки, описанной в руководстве Сборка приложения Node.js с помощью Docker.

Клонируйте репозиторий в директорию с именем node_project:

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

Перейдите в директорию node_project:

  1. cd node_project

В этой директории имеется файл Dockerfile, содержащий инструкции по сборке приложения Node с помощью образа Docker node:10 и содержимое директории вашего текущего проекта. Вы можете посмотреть содержимое файла Dockerfile, введя следующую команду:

  1. cat Dockerfile
Output
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 без прав root с соответствующими разрешениями, заданными для кода приложения и директорий node_modules.

Дополнительную информацию о рекомендуемых практиках работы с Dockerfile и образом Node см. в шаге 3 руководства Сборка приложения Node.js с помощью Docker.

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

  1. docker build -t node-demo .

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

  1. docker images

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

Output
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: позволяет присвоить контейнеру запоминающееся имя.

Запустите следующую команду для сборки контейнера:

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

Просмотрите запущенные контейнеры с помощью команды docker ps:

  1. docker ps

Вы увидите вывод, подтверждающий, что контейнер вашего приложения запущен:

Output
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:

  1. docker ps
Output
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 на CONTAINER ID вашего приложения:

  1. docker stop 4133b72391da

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

  1. docker system prune -a

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

Когда образ вашего приложения будет протестирован, вы можете перейти к сборке остальной части настройки с помощью Docker Compose.

Шаг 2 — Настройка конфигурации веб-сервера

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

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

  1. mkdir nginx-conf

Откройте файл с помощью nano или своего любимого редактора:

  1. nano nginx-conf/nginx.conf

Добавьте следующий серверный блок для запросов пользователя прокси в контейнер приложения Node и для перенаправления запросов Certbot в директорию .well-known. Обязательно замените example.com на ваше доменное имя.

~/node_project/nginx-conf/nginx.conf
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, который мы будем использовать для получения сертификатов.

Шаг 3 — Создание файла Docker Compose

Файл docker-compose.yml будет определять наши службы, включая приложение Node и веб-сервер. В нем будут указаны такие данные, как имена томов, которые имеют критическое значение для обмена учетными данными SSL между контейнерами, а также информацией о сети и порте. Также он позволяет указать конкретные команды, которые будут запускаться при создании контейнеров. Этот файл является центральным ресурсом, который будет определять, как будет организована совместная работа наших служб.

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

  1. nano docker-compose.yml

Определите службу приложения:

~/node_project/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, но мы задали значение, согласно которому контейнер будет перезапускаться, пока не будет закрыт.

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

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

~/node_project/docker-compose.yml
services:
  nodejs:
...
    networks:
      - app-network

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

Далее нужно определить службу webserver:

~/node_project/docker-compose.yml
...
 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. Дополнительную информацию о образах alpine см. в шаге 3 руководства Сборка приложения 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. Обязательно замените домен и информацию об электронной почте на ваши имя домена и электронную почту:

~/node_project/docker-compose.yml
...
  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

Это определение попросит 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. Обязательно замените эти данные на ваш домен.

В качестве заключительного шага добавьте определения тома и сети. Обязательно замените имя пользователя на имя вашего пользователя без прав root:

~/node_project/docker-compose.yml
...
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, который в Linux принимает параметры, аналогичные команде mount. Благодаря этому мы можем указать список параметров для драйверов с помощью команды driver_opts, которая монтирует директорию views на хосте, содержащем статические активы нашего приложения, на том во время исполнения. Содержимое директории затем может передаваться между контейнерами. Дополнительную информацию о содержании директории views см. в шаге 2 руководства Сборка приложения Node.js с помощью Docker.

Файл docker-compose.yml будет выглядеть следующим образом после завершения настройки:

~/node_project/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  

После добавления определений службы вы можете запустить контейнеры и протестировать запросы сертификата.

Шаг 4 — Получение сертификатов и учетных данных SSL

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

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

  1. docker-compose up -d

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

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

С помощью docker-compose ps проверьте статус ваших служб:

  1. docker-compose ps

Если все будет выполнено успешно, ваши службы nodejs и webserver должны иметь статус Up, а работа контейнера certbot будет завершена с сообщением о статусе 0:

Output
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 в столбце State для служб nodejs и webserver, или любое сообщение о статусе выхода, отличающееся от 0, для контейнера certbot, проверьте журналы службы с помощью команды docker-compose logs:

  1. docker-compose logs service_name

Теперь вы можете убедиться, что ваши учетные данные были смонтированы в контейнер webserver с помощью команды docker-compose exec:

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

Если запрос будет выполнен успешно, вы увидите следующий вывод:

Output
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:

  1. nano docker-compose.yml

Найдите раздел файла с определением службы certbot и замените флаг --staging в параметрах command на флаг --force-renewal, который будет указывать Certbot, что вы хотите запросить новый сертификат с теми же доменами, что и в уже существующем сертификате. Определение службы certbot должно выглядеть следующим образом:

~/node_project/docker-compose.yml
...
  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 --force-renewal -d example.com -d www.example.com
...

Теперь вы можете запустить docker-compose up для воссоздания контейнера certbot и соответствующих томов. Также мы будем использовать параметр --no-deps, чтобы сообщить Compose о том, что можно пропустить запуск службы webserver, поскольку она уже запущена:

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

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

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

Шаг 5 — Изменение конфигурации веб-сервера и определения службы

Активация SSL в нашей конфигурации Nginx будет подразумевать добавление перенаправления HTTP на HTTPS и указание расположения сертификата и ключей SSL. Также нам нужно будет указать нашу группу Diffie-Hellman, которую мы будем использовать для Совершенной прямой секретности.

Поскольку вы будете воссоздавать службу webserver для включения этих нововведений, сейчас вы можете остановить ее работу:

  1. docker-compose stop webserver

Далее создайте директорию в текущей директории проекта для вашего ключа Diffie-Hellman:

  1. mkdir dhparam

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

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

Процесс генерации ключа может занять несколько минут.

Чтобы добавить данные о Diffie-Hellman и SSL в конфигурацию Nginx, первым делом удалите ранее созданный файл конфигурации Nginx:

  1. rm nginx-conf/nginx.conf

Откройте другую версию файла:

  1. nano nginx-conf/nginx.conf

Добавьте следующий код в файл для перенаправления HTTP на HTTPS и добавления учетных данных, протоколов и заголовков безопасности SSL. Обязательно замените example.com​​ на ваше доменное имя:

~/node_project/nginx-conf/nginx.conf

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. Также он содержит директиву перезаписи, которая перенаправляет запросы HTTP в корневую директорию HTTPS.

Блок сервера HTTPS активирует ssl и http2. Дополнительную информацию о том, как выполняется итерация HTTP/2 в протоколах HTTP и какие преимущества это может дать для повышения производительности веб-сайта, см. во вводной части руководства по настройке Nginx с поддержкой HTTP/2 в Ubuntu 18.04. Этот блок также включает ряд параметров, гарантирующих, что вы используете самые актуальные протоколы и шифры SSL, а также то, что расширение OCSP stapling будет активировано. Расширение OCSP stapling позволяет вам обеспечивать ответ с привязкой по времени от издателя сертификата во время первоначального TLS-рукопожатия, что позволяет ускорить процесс аутентификации.

Блок также указывает расположение ваших учетных данных и ключей SSL и Diffie-Hellman.

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

После завершения редактирования сохраните и закройте файл.

Прежде чем воссоздать службу webserver, вам нужно добавить несколько элементов в определение службы в файл docker-compose.yml, включая соответствующую информацию о порте для HTTPS и определение тома Diffie-Hellman.

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

  1. nano docker-compose.yml

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

~/node_project/docker-compose.yml
...
 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 в определения ваших томов:

~/node_project/docker-compose.yml
...
volumes:
  ...
  dhparam:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/dhparam/
      o: bind

Как и в случае с томом web-root, том dhparam будет монтировать ключ Diffie-Hellman, хранящийся на хосте, в контейнер webserver.

Сохраните и закройте файл после завершения редактирования.

Повторно создайте службу webserver:

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

Проверьте ваши службы с помощью команды docker-compose ps:

  1. docker-compose ps

Вы должны увидеть вывод, указывающий, что ваши службы nodejs и webserver запущены:

Output
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 или начальной странице теста сервера Security Headers. Параметры конфигурации, которые мы использовали, должны обеспечить получение оценки А для вашего сайта в обоих тестах.

Шаг 6 — Обновление сертификатов

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

Откройте скрипт с именем ssl_renew.sh в директории проекта:

  1. nano ssl_renew.sh

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

~/node_project/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, чтобы протестировать наш скрипт.

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

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

  1. chmod +x ssl_renew.sh

Далее откройте root-файл crontab для запуска скрипта обновления с заданным интервалом:

  1. sudo crontab -e

Если вы в первый раз редактируете этот файл, вам будет предложено выбрать редактор:

crontab
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]:
...

Добавьте внизу файла следующую строку:

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

В результате будет установлен интервал в 5 минут для выполнения работы, и вы можете проверить, работает ли запрос обновления так, как предполагается. Также мы создали файл журнала, cron.log, чтобы записывать соответствующий вывод выполнения задания.

Через 5 минут проверьте cron.log, чтобы убедиться, что запрос обновления выполнен успешно:

  1. tail -f /var/log/cron.log

Вы должны увидеть вывод, подтверждающий успешное обновление:

Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ** 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 для настройки ежедневного интервала. Чтобы запускать скрипт каждый день в полдень, например, вы должны изменить последнюю строку файла, которая должна выглядеть следующим образом:

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

Также вы можете изменить параметр --dry-run из скрипта ssl_renew.sh:

~/node_project/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, ознакомьтесь со статьями, посвященными использованию плагина Nginx или автономного плагина.

Также вы можете узнать больше о Docker Compose, изучив следующие ресурсы:

Документация Compose также может служить отличным источником информации о приложениях с несколькими контейнерами.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
3 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

I have an error when I up docker-compose ERROR: for webserver Cannot start service webserver: error while mounting volume '/var/lib/docker/volumes/simplelanding_web-root/_data': failed to mount local volume: mount /home/root/simple-landing/views/:/var/lib/docker/volumes/simplelanding_web-root/_data, flags: 0x1000: no such file or directory

Это не работает

How add turnserver (Coturn, exmple) in a docker container?

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.