deploy: harden nginx/coturn/livekit configs (TLS on 443, templates, required secrets)
Переносит рабочие правки продакшен-сервера в репозиторий воспроизводимо: - nginx: HTTPS-блок на 443 (TLS1.2/1.3, http->https redirect, ACME webroot, X-Forwarded-Proto) добавлен в nginx.conf.template. Список доменов и имя каталога сертификата — параметры (NGINX_SERVER_NAMES, NGINX_CERT_NAME), не хардкод. Рендерится штатным entrypoint'ом образа nginx (envsubst-on-templates). - Самоподписанный dev-сертификат генерируется на старте контейнера (docker-entrypoint-certs.sh), если реальный Let's Encrypt не смонтирован — иначе `docker compose up` без профиля ломался бы локально без реальных сертификатов. - healthcheck nginx переключён на /healthz (без TLS-редиректа), иначе http://127.0.0.1:80/ после добавления 443-редиректа всегда unhealthy. - coturn/turnserver.conf и livekit/livekit.yaml переведены в *.template — оба образа не читают env для статических полей (static-auth-secret, use_external_ip/node_ip, webhook.api_key), поэтому реальные файлы генерируются перед стартом стека скриптом deploy/render-templates.sh (вызывается install.sh). Секреты/IP — только в .env, не в git. - docker-compose.yml: LIVEKIT_API_KEY/SECRET везде (backend, livekit, egress) через ${VAR:?} без небезопасных дефолтов; порт 443 + монтирование /etc/letsencrypt (ro) и certbot-webroot у nginx; build-args VITE_LIVEKIT_URL/NEXT_PUBLIC_LIVEKIT_URL для frontend (сейчас не используются кодом, оставлены про запас).
This commit is contained in:
35
deploy/nginx/docker-entrypoint-certs.sh
Executable file
35
deploy/nginx/docker-entrypoint-certs.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
# Готовит /etc/nginx/certs/{fullchain,privkey}.pem ДО того, как штатный
|
||||
# entrypoint официального образа nginx (20-envsubst-on-templates.sh, потом
|
||||
# сам nginx) прочитает конфиг с `ssl_certificate` (см.
|
||||
# deploy/nginx/nginx.conf.template). Монтируется томом в docker-compose.yml
|
||||
# в /docker-entrypoint.d/ (рядом с nginx.conf.template — конфиг и
|
||||
# entrypoint-скрипты nginx монтируются, а не запекаются в образ, чтобы правки
|
||||
# не требовали пересборки фронта), где нативный entrypoint nginx исполняет
|
||||
# все *.sh по алфавиту до старта самого nginx.
|
||||
#
|
||||
# Прод: реальный сертификат смонтирован с хоста в /etc/letsencrypt (ro,
|
||||
# см. docker-compose.yml) — копируем его.
|
||||
# Dev (docs/deploy/dev-setup.md, `docker compose up` без реальных сертификатов
|
||||
# Let's Encrypt): каталог пуст/отсутствует — генерируем самоподписанный
|
||||
# сертификат, чтобы локальный стек стартовал без ручной настройки TLS.
|
||||
set -e
|
||||
|
||||
CERT_NAME="${NGINX_CERT_NAME:-localhost}"
|
||||
SRC="/etc/letsencrypt/live/${CERT_NAME}"
|
||||
DEST=/etc/nginx/certs
|
||||
|
||||
mkdir -p "$DEST"
|
||||
|
||||
if [ -f "$SRC/fullchain.pem" ] && [ -f "$SRC/privkey.pem" ]; then
|
||||
echo "[nginx-certs] Использую боевой сертификат из $SRC"
|
||||
cp "$SRC/fullchain.pem" "$DEST/fullchain.pem"
|
||||
cp "$SRC/privkey.pem" "$DEST/privkey.pem"
|
||||
else
|
||||
echo "[nginx-certs] $SRC не найден — генерирую самоподписанный сертификат (ТОЛЬКО для локальной разработки, в проде смонтируйте /etc/letsencrypt)"
|
||||
openssl req -x509 -nodes -newkey rsa:2048 -days 365 \
|
||||
-keyout "$DEST/privkey.pem" -out "$DEST/fullchain.pem" \
|
||||
-subj "/CN=${CERT_NAME}" 2>/dev/null
|
||||
fi
|
||||
|
||||
chmod 644 "$DEST/fullchain.pem" "$DEST/privkey.pem"
|
||||
@@ -1,6 +1,14 @@
|
||||
# Dev reverse proxy: backend API + раздача фронтенд-SPA (статика в образе).
|
||||
# Завершение TLS для production документировано в docs/deploy/dev-setup.md
|
||||
# и должно добавляться отдельным server-блоком (443) с реальными сертификатами.
|
||||
# Reverse proxy: backend API + раздача фронтенд-SPA (статика в образе) +
|
||||
# LiveKit SFU signaling. TLS-терминация — здесь же (см. блок HTTPS ниже),
|
||||
# сертификаты берутся из /etc/letsencrypt (см. docker-compose.yml, volume ro).
|
||||
#
|
||||
# Шаблон рендерится штатным entrypoint-скриптом официального образа nginx
|
||||
# (docker-entrypoint.d/20-envsubst-on-templates.sh): при старте контейнера
|
||||
# envsubst подставляет ТОЛЬКО переменные, реально заданные в окружении
|
||||
# контейнера (см. `environment:` сервиса nginx в docker-compose.yml), поэтому
|
||||
# нативные nginx-переменные ($scheme, $host, $http_upgrade, $remote_addr,
|
||||
# $connection_upgrade и т.п.) остаются нетронутыми — с ними коллизий по
|
||||
# именам нет. Результат пишется в /etc/nginx/conf.d/default.conf.
|
||||
#
|
||||
# ВАЖНО (stale DNS): статический `upstream { server backend:8000; }` nginx
|
||||
# резолвит один раз при старте/reload и держит IP в памяти. После пересоздания
|
||||
@@ -17,9 +25,57 @@ map $http_upgrade $connection_upgrade {
|
||||
'' close;
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# БЛОК 1: HTTP (порт 80)
|
||||
# Задача: ACME-challenge для Certbot + редирект всего остального на HTTPS.
|
||||
# ==========================================
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
# Список доменов — параметр (NGINX_SERVER_NAMES в .env), не хардкод.
|
||||
server_name ${NGINX_SERVER_NAMES};
|
||||
|
||||
# Раздача файлов для (авто)обновления Certbot — ОБЯЗАТЕЛЬНО оставить
|
||||
# для работы webroot-плагина.
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
# Healthcheck контейнера (см. docker-compose.yml) ходит сюда напрямую по
|
||||
# HTTP — редирект ниже иначе увёл бы его на HTTPS и ломал бы wget без -L.
|
||||
location /healthz {
|
||||
return 200 "ok\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
|
||||
# Жёсткий редирект всего остального на HTTPS.
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# БЛОК 2: HTTPS (порт 443)
|
||||
# Задача: вся основная логика приложения — API, фронтенд, WebSocket, LiveKit.
|
||||
# ==========================================
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name ${NGINX_SERVER_NAMES};
|
||||
|
||||
# Путь СТАТИЧЕН (не envsubst) — реальный источник переменный:
|
||||
# /docker-entrypoint.d/15-vidconf-certs.sh (запускается раньше рендера
|
||||
# этого шаблона) копирует сюда боевой сертификат из
|
||||
# /etc/letsencrypt/live/${NGINX_CERT_NAME}, если он смонтирован с хоста
|
||||
# (см. docker-compose.yml), иначе генерирует самоподписанный — это
|
||||
# позволяет `docker compose up` стартовать локально (dev) БЕЗ реальных
|
||||
# сертификатов Let's Encrypt (см. docs/deploy/dev-setup.md).
|
||||
ssl_certificate /etc/nginx/certs/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/certs/privkey.pem;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_tickets off;
|
||||
|
||||
# --- Backend API ---
|
||||
# Проброс через переменную: proxy_pass с переменной НЕ выполняет
|
||||
@@ -42,11 +98,13 @@ server {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# $scheme здесь равен 'https' — критично, чтобы бэкенд (FastAPI)
|
||||
# генерировал корректные абсолютные ссылки и принимал Secure-куки.
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 3600s;
|
||||
}
|
||||
|
||||
# --- WebSocket upgrade для чата и LiveKit signaling через nginx ---
|
||||
# --- WebSocket upgrade для чата ---
|
||||
location /ws/ {
|
||||
set $backend_upstream http://backend:8000;
|
||||
proxy_pass $backend_upstream;
|
||||
@@ -54,6 +112,9 @@ server {
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 3600s;
|
||||
}
|
||||
|
||||
@@ -78,6 +139,7 @@ server {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 3600s;
|
||||
}
|
||||
|
||||
@@ -112,9 +174,4 @@ server {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /healthz {
|
||||
return 200 "ok\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user