Как я настроил безопасное шифрование данных для сайта с помощью протокола HTTPS и SSL/TLS сертификата

от автора

Всем привет! Сегодня я вам расскажу про то, как я настроил безопасное шифрование данных для сайта с помощью протокола 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/