Всем привет! Сегодня я вам расскажу про то, как я настроил безопасное шифрование данных для сайта с помощью протокола HTTPS и SSL/TLS сертификата.
⚡️Подготовка
Представим что у вас есть свой сайт запущенный на VPS на голом HTTP, также вы уже купили домен и связали его со своим IP-адресом.
Но спустя время вы решили, что не хотите, чтоб весь трафик между клиентами и сервером был виден любому желающему.
Вспомнили, что есть протокол HTTPS, который по сути обычный HTTP, но на 5 и 6 уровне работает протокол SSL/TLS, который занимается шифрованием и аутентификацией.
Дело осталось за малым, перевести сайт с HTTP на HTTPS.
❓Что такое обратный прокси и зачем он нам?
Обратный прокси — это сетевой шлюз, который полностью перехватывает входящие соединения от клиентов, берет на себя всю работу с протоколами (TCP, TLS, HTTP) и от своего имени отправляет новые, чистые запросы на целевые серверы (бэкенды).
Для внешнего мира обратный прокси — это и есть сам сайт. Клиент думает, что общается с бэкендом, но на самом деле он общается только с прокси.
В обычном прокси мы отправляем данные прокси серверу и просим его от своего имени отправить данные кому-то.
В VPN мы просим не просто отправить данные, а инкапсулируем и зашифровываем весь наш пакет, который VPN сервер расшифрует, подменит наш IP и отправит уже в интернет, при получении ответного пакета, при помощи сокета понимает какому клиенту нужно отправить пакет.
Я решил использовать обратный прокси caddy, он потребляет меньше ресурсов, чем nginx и умеет автоматически выпускать сертификаты Let’s encrypt.
Для его работы нужно создать Caddyfile:
Ваш домен { reverse_proxy app:9000 { header_up X-Forwarded-Proto {scheme} } encode gzip}
У меня он максимально простой, так как всего один сайт. Тут мы пишем, что при обращении пользователя на такой домен нужно отдать данные контейнеру app, работающему на 9000 порте.
encode gzip помогает ускорить передачу статических файлов.
Обновим или создадим новый docker-compose.yml
Раньше у меня просто в интернет был проброшен порт из контейнера, на котором работает сайт.
Теперь никто не должен видеть, как у нас устроено внутри. Все запросы пользователей вначале будет встречать обратный прокси caddy. И уже он будет передавать их нужному сервису, запущенному у нас.
Это позволяет не только скрыть внутрянку, но и запускать несколько сервисов на одном порте. Точнее все они будут на разных, но для пользователей будет казаться, что все на 443, например.
Я создал такой новый docker-compose-https.yml:
services: db: image: postgres:17 container_name: db restart: always environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: db ports: - "127.0.0.1:5432:5432" volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d db"] interval: 10s timeout: 30s retries: 5 app: build: context: . dockerfile: Dockerfile container_name: app restart: always depends_on: - db env_file: - .env expose: - "9000" volumes: - ./app:/code/app command: uvicorn main:app --host 0.0.0.0 --port 9000 --reload caddy: image: caddy:2-alpine container_name: caddy restart: always ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy_data:/data - caddy_config:/config depends_on: - appvolumes: postgres_data: caddy_data: caddy_config:
Не забудьте поменять пользователя и пароль, а также используйте .env
Теперь сайт работает в контейнере на 9000 порту, прокидывать его наружу не нужно, а контейнеры итак связанны в общую сеть.
expose 9000 не открывает порт наружу, а используется для документации и позволяет указать, какие порты контейнер использует для связи.
Для запуска контейнеров используем теперь эту команду (при первом запуске нужно добавить флаг в конце —build):
sudo docker compose -f docker-compose-https.yml up -d
В чем заключается проблема?
Если мы просто настроили сертификат и обратный прокси, то это нам позволит отправлять данные между клиентом и обратным прокси сервером по зашифрованному каналу.
Однако, FastAPI общается с Caddy по HTTP и не знает, что клиент теперь использует HTTPS, из-за этого он будет генерировать ссылки со схемой http://
Из-за этого наши перенаправления, ссылки и тд, где мы использовали, например, функцию url_for будут ломаться, создавать бесконечные редиректы, так как браузер ждет ответ только со схемой https://
Тут есть два решения: убрать везде эти функции и писать самому явно схему (но не всегда это можно сделать), либо добавить специальный заголовок в HTTP, при получении пакета обратным прокси сервером.
Разберем второе решение.
⚡️Добавим заголовок во все HTTPS запросы
Ваш Caddyfile должен выглядеть следующим образом:
Ваш домен { reverse_proxy app:9000 { header_up X-Forwarded-Proto {scheme} } encode gzip}
Заголовок header_up X-Forwarded-Proto {scheme} это тот самый нужный нам заголовок.
X-Forwarded Proto это стандарт, который обозначает дословно перенаправленный протокол, а {scheme} является динамической переменной, которая может быть и http, и https.
Можно его явно не прописывать, так как Caddy сам ставит его по умолчанию, но он ставит без динамической переменной.
А без неё не получится тестировать работу у себя на компьютере при разработке по http.
Но этого недостаточно, нужно прописать, чтобы бэкэнд читал этот заголовок.
Добавим middleware функцию
Функция middleware — это промежуточная функция, которая перехватывает все http запросы к нашему приложению.
@app.middleware("http")async def fix_https_urls(request, call_next): if request.headers.get("x-forwarded-proto") == "https": request.scope["scheme"] = "https" response = await call_next(request) return response
Тут мы вначале проверяем у перехваченного запроса заголовок и если он есть и его значение https, то мы сами меняем схему http от обратного прокси на https.
FastAPI теперь будет думать, что общается по https и генерировать ссылки с такой схемой.
А асинхронный вызов функции await call_next(request) передаёт управление и request остальным функциям нашего приложения.
После того, как какая-то ручка выполнила свои действия, то мы получаем response и отдаем его клиенту.
Также, если вы используете куки, то поставьте флаг secure=True, но в локальной разработке отключаем.
💬На этом всё, теперь мы смогли настроить шифрование канала при помощи HTTPS.
Если вам нужно сверстать сайт, собрать Telegram-бота или что-то иное — пишите мне напрямую в личку: @gesesofficiall, обсудим задачу.
Если вам интересна тема сетей, веб-разработки, криптографии, программирования или ИИ — подписывайтесь на мой канал @pxi_ru, там я публикую свои посты и готовые проекты.
ссылка на оригинал статьи https://habr.com/ru/articles/1068204/