Защита контейнеризированного приложения Django с помощью Let's Encrypt
Как установить SSL-сертификат для приложения Django?
В этом руководстве мы рассмотрим, как защитить контейнерное приложение Django, работающее за HTTPS-прокси Nginx, с помощью SSL-сертификатов Let's Encrypt.
Данный учебник построен на основе Докеризация Django с Postgres, Gunicorn и Nginx. Предполагается, что вы понимаете, как контейнеризировать приложение Django вместе с Postgres, Nginx и Gunicorn.
В настоящее время просто невозможно вывести на рынок приложение, работающее по протоколу HTTP. Без HTTPS ваш сайт становится менее безопасным и надежным. С Let's Encrypt, который упрощает процесс получения и установки SSL-сертификатов, больше нет причин не иметь HTTPS.
Django на Docker Series:
- Докеризация Django с Postgres, Gunicorn и Nginx
- Защита контейнеризированного Django-приложения с помощью Let's Encrypt (этот учебник!)
- Развертывание Django на AWS с помощью Docker и Let's Encrypt
Пререквизиты
Для выполнения данного урока вам потребуется:
- доменное имя
- работающая виртуальная машина Linux с установленными Docker и Docker Compose, на которой будет развернуто ваше приложение (AWS EC2, Google Compute Engine, DigitalOcean, Linode - все это жизнеспособные варианты)
Нужен дешевый домен для тренировок? Некоторые регистраторы доменов предлагают специальные предложения на домены '.xyz'. Кроме того, вы можете создать бесплатный домен на сайте Freenom.
Подход
Существует несколько различных способов защитить контейнерное приложение Django с помощью HTTPS. Пожалуй, наиболее популярным является добавление в файл Docker Compose нового сервиса, использующего Certbot для выпуска и обновления SSL-сертификатов. Хотя такой подход вполне оправдан, мы воспользуемся несколько иным подходом, используя следующие проекты:
- nginx-proxy - используется для автоматического построения конфигурации прокси-сервера Nginx для запущенных контейнеров, где каждый контейнер рассматривается как отдельный виртуальный хост
- acme-companion - используется для выпуска и обновления SSL-сертификатов Let's Encrypt для каждого из контейнеров, проксируемых nginx-proxy
В совокупности эти проекты упрощают управление конфигурацией Nginx и SSL-сертификатами.
Другой вариант - использовать вместо Nginx Traefik. Вкратце, Traefik работает с Let's Encrypt для выпуска и обновления сертификатов. Подробнее об этом читайте в статье Dockerizing Django with Postgres, Gunicorn, and Traefik.
Let's Encrypt
При первом развертывании приложения необходимо выполнить следующие два шага, чтобы избежать проблем с сертификатами:
- Начните с выпуска сертификатов в среде Let's Encrypt staging
- Затем, когда все будет работать как надо, переключитесь на производственную среду Let's Encrypt
Почему?
Для защиты своих серверов компания Let's Encrypt применяет ограничения скорости в своей производственной системе проверки:
- 5 сбоев проверки на одну учетную запись, на одно имя хоста, в час
- 50 сертификатов может быть создано на один домен в неделю
Если вы допустите опечатку в доменном имени, в записи DNS или что-то подобное, ваш запрос будет отклонен, что будет засчитано в ваш лимит скорости, и вам придется попытаться выпустить новый сертификат.
Чтобы избежать ограничения скорости, во время разработки и тестирования для проверки системы проверки следует использовать среду Staging компании Let's Encrypt. Ограничения по скорости значительно выше в среде staging, что лучше для тестирования. Следует помнить, что выпущенные в staging сертификаты не являются публично доверенными, поэтому, как только все заработает, следует переключиться на их производственную среду.
Настройка проекта
Сначала клонируйте содержимое репозитория проекта на GitHub:
$ git clone https://github.com/testdrivenio/django-on-docker django-on-docker-letsencrypt
$ cd django-on-docker-letsencrypt
Этот репозиторий содержит все необходимое для развертывания Dockerized Django-приложения, за исключением SSL-сертификатов, которые мы будем добавлять в этом руководстве.
Django конфигурация
Во-первых, для запуска приложения Django за HTTPS-прокси необходимо добавить параметр SECURE_PROXY_SSL_HEADER в settings.py:
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
В этом кортеже, если X-Forwarded-Proto
имеет значение https
, запрос является безопасным.
Также необходимо обновить CSRF_TRUSTED_ORIGINS
внутри settings.py:
CSRF_TRUSTED_ORIGINS = os.environ.get("CSRF_TRUSTED_ORIGINS").split(" ")
Docker Compose
Пришло время конфигурировать Docker Compose.
Добавим новый файл Docker Compose для тестирования под названием docker-compose.staging.yml:
version: '3.8'
services:
web:
build:
context: ./app
dockerfile: Dockerfile.prod
command: gunicorn hello_django.wsgi:application --bind 0.0.0.0:8000
volumes:
- static_volume:/home/app/web/staticfiles
- media_volume:/home/app/web/mediafiles
expose:
- 8000
env_file:
- ./.env.staging
depends_on:
- db
db:
image: postgres:13.0-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
env_file:
- ./.env.staging.db
nginx-proxy:
container_name: nginx-proxy
build: nginx
restart: always
ports:
- 443:443
- 80:80
volumes:
- static_volume:/home/app/web/staticfiles
- media_volume:/home/app/web/mediafiles
- certs:/etc/nginx/certs
- html:/usr/share/nginx/html
- vhost:/etc/nginx/vhost.d
- /var/run/docker.sock:/tmp/docker.sock:ro
depends_on:
- web
acme-companion:
image: nginxproxy/acme-companion
env_file:
- ./.env.staging.proxy-companion
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- certs:/etc/nginx/certs
- html:/usr/share/nginx/html
- vhost:/etc/nginx/vhost.d
- acme:/etc/acme.sh
depends_on:
- nginx-proxy
volumes:
postgres_data:
static_volume:
media_volume:
certs:
html:
vhost:
acme:
Добавьте файл .env.staging.db для контейнера db
:
POSTGRES_USER=hello_django
POSTGRES_PASSWORD=hello_django
POSTGRES_DB=hello_django_prod
Измените значения параметров
POSTGRES_USER
иPOSTGRES_PASSWORD
на соответствующие вашему пользователю и паролю.
Мы уже рассмотрели службы web
и db
в предыдущем учебнике, поэтому перейдем к рассмотрению служб nginx-proxy
и acme-companion
.
Базы данных - это критически важные сервисы. Добавление дополнительных слоев, таких как Docker, увеличивает ненужный риск в производстве. Для упрощения таких задач, как обновление мелких версий, регулярное резервное копирование и масштабирование, рекомендуется использовать управляемый сервис, например AWS RDS, Google Cloud SQL или DigitalOcean's Managed Database.
Nginx Proxy Service
Для данного сервиса используется проект nginx-proxy для генерации конфигурации обратного прокси для контейнера web
с использованием виртуальных хостов для маршрутизации.
Обязательно ознакомьтесь с README на репозитории nginx-proxy.
После запуска контейнер, связанный с nginx-proxy
, автоматически обнаруживает контейнеры (в той же сети), у которых установлена переменная окружения VIRTUAL_HOST
, и динамически обновляет конфигурацию своих виртуальных хостов.
Далее добавим файл .env.staging для контейнера web
:
DEBUG=0
SECRET_KEY=change_me
DJANGO_ALLOWED_HOSTS=<YOUR_DOMAIN.COM>
SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=hello_django_prod
SQL_USER=hello_django
SQL_PASSWORD=hello_django
SQL_HOST=db
SQL_PORT=5432
DATABASE=postgres
VIRTUAL_HOST=<YOUR_DOMAIN.COM>
VIRTUAL_PORT=8000
LETSENCRYPT_HOST=<YOUR_DOMAIN.COM>
CSRF_TRUSTED_ORIGINS=https://<YOUR_DOMAIN.COM>
Примечания:
- Измените
<YOUR_DOMAIN.COM>
на ваш реальный домен и измените значения по умолчаниюSQL_USER
иSQL_PASSWORD
на соответствующиеPOSTGRES_USER
иPOSTGRES_PASSWORD
(из .env.staging.db). - Как уже упоминалось,
VIRTUAL_HOST
(иVIRTUAL_PORT
) необходимыnginx-proxy
для автоматического создания конфигурации обратного прокси. LETSENCRYPT_HOST
нужен для того, чтобыnginx-proxy-companion
мог выпустить сертификат Let's Encrypt для вашего домена.- Поскольку приложение Django будет слушать порт 8000, мы также установили переменную окружения
VIRTUAL_PORT
. - Том
/var/run/docker.sock:/tmp/docker.sock:ro
в docker-compose.staging.yml используется для прослушивания вновь зарегистрированных/де-регистрированных контейнеров.
Для целей тестирования/отладки вы можете использовать
*
дляDJANGO_ALLOWED_HOSTS
при первом развертывании, чтобы упростить работу. Только не забудьте ограничить количество разрешенных хостов после завершения тестирования.
Таким образом, запросы к указанному домену будут проксироваться на контейнер, у которого домен задан в качестве VIRTUAL_HOST
переменной окружения.
Далее обновим конфигурацию Nginx в папке "nginx".
Сначала добавьте каталог с именем "vhost.d". Затем добавьте в этот каталог файл default для обслуживания статических и мультимедийных файлов:
location /static/ {
alias /home/app/web/staticfiles/;
add_header Access-Control-Allow-Origin *;
}
location /media/ {
alias /home/app/web/mediafiles/;
add_header Access-Control-Allow-Origin *;
}
Запросы, соответствующие любому из этих шаблонов, будут обслуживаться из папок static или media. Они не будут проксироваться в другие контейнеры. Контейнеры web
и nginx-proxy
совместно используют тома, на которых расположены статические и мультимедийные файлы:
static_volume:/home/app/web/staticfiles
media_volume:/home/app/web/mediafiles
Добавьте в папку "nginx" файл custom.conf для хранения пользовательской конфигурации прокси-сервера:
client_max_body_size 10M;
Обновить nginx/Dockerfile:
FROM nginxproxy/nginx-proxy
COPY vhost.d/default /etc/nginx/vhost.d/default
COPY custom.conf /etc/nginx/conf.d/custom.conf
Удалить nginx.conf.
Теперь ваш каталог "nginx" должен выглядеть следующим образом:
└── nginx
├── Dockerfile
├── custom.conf
└── vhost.d
└── default
Служба сопровождения ACME
В то время как сервис nginx-proxy
занимается маршрутизацией, acme-companion
(через acme-companion) занимается созданием, обновлением и использованием сертификатов Let's Encrypt для проксируемых Docker-контейнеров.
Для выпуска и обновления сертификатов для проксируемых контейнеров необходимо добавить в каждый из них переменную окружения LETSENCRYPT_HOST
(что мы уже сделали). Она также должна иметь то же значение, что и VIRTUAL_HOST
.
Этот контейнер должен совместно использовать следующие тома с nginx-proxy
:
certs:/etc/nginx/certs
хранит сертификаты, закрытые ключи и ключи учетных записей ACMEhtml:/usr/share/nginx/html
пишет http-01 файлы вызоваvhost:/etc/nginx/vhost.d
изменяет конфигурацию vhosts
Для получения дополнительной информации ознакомьтесь с официальной документацией.
Добавьте файл .env.staging.proxy-companion:
DEFAULT_EMAIL=youremail@yourdomain.com
ACME_CA_URI=https://acme-staging-v02.api.letsencrypt.org/directory
NGINX_PROXY_CONTAINER=nginx-proxy
Примечания:
DEFAULT_EMAIL
- это адрес электронной почты, который Let's Encrypt будет использовать для отправки уведомлений о ваших сертификатах (включая продление).ACME_CA_URI
- это URL, используемый для выпуска сертификатов. Опять же, используйте staging, пока не убедитесь, что все работает.NGINX_PROXY_CONTAINER
- имяnginx-proxy
контейнера.
Запуск контейнеров
Все готово к развертыванию.
Пришло время переходить на свой экземпляр Linux.
Предполагая, что на вашем экземпляре создан каталог проекта, например /home/myuser/django-on-docker, скопируйте в него файлы и папки с помощью SCP:
$ scp -r $(pwd)/{app,nginx,.env.staging,.env.staging.db,.env.staging.proxy-companion,docker-compose.staging.yml} user@your-ip-or-domain:/path/to/django-on-docker
Подключитесь к своему экземпляру по SSH и перейдите в каталог проекта:
$ ssh user@your-ip-or-domain
$ cd /path/to/django-on-docker
После этого можно собирать образы и запускать контейнеры:
$ docker-compose -f docker-compose.staging.yml up -d --build
После того как контейнеры будут запущены, перейдите в браузере на свой домен. Вы должны увидеть что-то вроде:
Это ожидаемо. Это окно показано потому, что сертификат был выпущен из постановочной среды, которая, опять же, не имеет таких ограничений по скорости, как производственная среда. Это похоже на самоподписанный сертификат HTTPS. Всегда используйте промежуточную среду, пока не убедитесь, что все работает так, как ожидается.
Как узнать, все ли работает?
Нажмите на кнопку "Дополнительно" и затем на кнопку "Продолжить". Теперь вы должны увидеть свое приложение. Загрузите изображение, а затем убедитесь, что его можно просмотреть по адресу https://yourdomain.com/mediafiles/IMAGE_FILE_NAME
.
Выдача сертификата производства
Теперь, когда все работает как надо, мы можем переключиться на производственную среду Let's Encrypt.
Свернуть существующие контейнеры и выйти из экземпляра:
$ docker-compose -f docker-compose.staging.yml down -v
$ exit
На локальной машине обновите docker-compose.prod.yml:
version: '3.8'
services:
web:
build:
context: ./app
dockerfile: Dockerfile.prod
command: gunicorn hello_django.wsgi:application --bind 0.0.0.0:8000
volumes:
- static_volume:/home/app/web/staticfiles
- media_volume:/home/app/web/mediafiles
expose:
- 8000
env_file:
- ./.env.prod
depends_on:
- db
db:
image: postgres:13.0-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
env_file:
- ./.env.prod.db
nginx-proxy:
container_name: nginx-proxy
build: nginx
restart: always
ports:
- 443:443
- 80:80
volumes:
- static_volume:/home/app/web/staticfiles
- media_volume:/home/app/web/mediafiles
- certs:/etc/nginx/certs
- html:/usr/share/nginx/html
- vhost:/etc/nginx/vhost.d
- /var/run/docker.sock:/tmp/docker.sock:ro
depends_on:
- web
acme-companion:
image: nginxproxy/acme-companion
env_file:
- ./.env.prod.proxy-companion
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- certs:/etc/nginx/certs
- html:/usr/share/nginx/html
- vhost:/etc/nginx/vhost.d
- acme:/etc/acme.sh
depends_on:
- nginx-proxy
volumes:
postgres_data:
static_volume:
media_volume:
certs:
html:
vhost:
acme:
Единственное отличие от docker-compose.staging.yml в том, что мы использовали разные файлы окружения.
.env.prod:
DEBUG=0
SECRET_KEY=change_me
DJANGO_ALLOWED_HOSTS=<YOUR_DOMAIN.COM>
SQL_ENGINE=django.db.backends.postgresql
SQL_DATABASE=hello_django_prod
SQL_USER=hello_django
SQL_PASSWORD=hello_django
SQL_HOST=db
SQL_PORT=5432
DATABASE=postgres
VIRTUAL_HOST=<YOUR_DOMAIN.COM>
VIRTUAL_PORT=8000
LETSENCRYPT_HOST=<YOUR_DOMAIN.COM>
CSRF_TRUSTED_ORIGINS=https://<YOUR_DOMAIN.COM>
.env.prod.db:
POSTGRES_USER=hello_django
POSTGRES_PASSWORD=hello_django
POSTGRES_DB=hello_django_prod
.env.prod.proxy-companion:
DEFAULT_EMAIL=youremail@yourdomain.co
NGINX_PROXY_CONTAINER=nginx-proxy
Обновите их соответствующим образом.
Заметили ли вы разницу, начиная со стадийных версий? Переменная окружения ACME_CA_URI
не установлена, так как образ acme-companion
по умолчанию использует производственное окружение Let's Encrypt.
Скопируйте новые файлы и папки на свой экземпляр с помощью SCP:
$ scp $(pwd)/{.env.prod,.env.prod.db,.env.prod.proxy-companion,docker-compose.prod.yml} user@your-ip-or-domain:/path/to/django-on-docker
Как и прежде, подключитесь к своему экземпляру по SSH и перейдите в каталог проекта:
$ ssh user@your-ip-or-domain
$ cd /path/to/django-on-docker
Сборка образов и разгон контейнеров:
$ docker-compose -f docker-compose.prod.yml up -d --build
Снова перейдите к своему домену. Предупреждение больше не должно появляться.
Поздравляем! Теперь вы используете производственный сертификат Let's Encrypt.
Хотите увидеть процесс создания сертификата в действии, посмотрите журналы:
$ docker-compose -f docker-compose.prod.yml logs acme-companion
Заключение
В заключение, после того как Docker Compose настроен на запуск Django, для настройки HTTPS необходимо добавить (и настроить) сервисы nginx-proxy
и acme-companion
в файл Docker Compose. Теперь можно добавить дополнительные контейнеры, настроив переменные окружения VIRTUAL_HOST
(маршрутизация) и LETSENCRYPT_HOST
(сертификат). Как всегда, не забудьте сначала провести тестирование в тестовой среде Let's Encrypt.
Код можно найти в репо django-on-docker-letsencrypt.
Вернуться на верх