From a321109a8f993b1764044006360c1b3eb65cb26c Mon Sep 17 00:00:00 2001 From: Andrey Date: Sat, 19 Sep 2026 11:32:25 +0300 Subject: [PATCH] =?UTF-8?q?:bug:=20fix(gateway):=20=D1=83=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=BD=D0=B0=20=D0=BD?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=B0=D0=BD=D0=B4=D0=B0=D1=80=D1=82=D0=BD?= =?UTF-8?q?=D0=BE=D0=BC=20=D0=BF=D0=BE=D1=80=D1=82=D1=83=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=D0=BD=D0=B8=D0=BC=D0=B0=D0=B5=D1=82=20=D1=84=D0=BE=D1=80?= =?UTF-8?q?=D0=BC=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Шлюз передавал дальше Host без порта ({host} в Caddy, $host в nginx). Браузер при этом шлёт Origin с портом, и CSRF-проверка Django отвергала любой POST на установке, опубликованной как ip:8081: «Origin checking failed». Теперь Host уходит с портом. X-Forwarded-Proto и X-Forwarded-For Caddy принимает от прокси из частных сетей — установку часто ставят за прокси панели, который снимает TLS и ходит к шлюзу по http; без этого CSRF падал бы снова, как только перед установкой появлялся https. Co-Authored-By: Claude Opus 5 --- Caddyfile | 26 ++++++++++++++++++-------- deploy/nginx/frontend.production.conf | 6 +++--- deploy/nginx/local.conf | 14 +++++++------- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/Caddyfile b/Caddyfile index 8d06387..d9e6762 100644 --- a/Caddyfile +++ b/Caddyfile @@ -16,18 +16,29 @@ on_demand_tls { ask http://backend-platform:8000/api/v1/gateway/help-domain/ } + # Установку часто ставят за прокси панели (aaPanel, nginx хоста), который + # снимает TLS и ходит сюда по http. Его X-Forwarded-Proto/For принимаются + # только из частных сетей — оттуда, где такой прокси и стоит; клиент из + # интернета подделать их не может. + servers { + trusted_proxies static private_ranges + } } (surfaces) { + # Host уходит с портом: браузер шлёт Origin с портом (http://ip:8081), и + # без него CSRF-проверка Django отвергала любой POST на нестандартном + # порту. X-Forwarded-Proto Caddy ставит сам: {scheme}, а за доверенным + # прокси — то, что прислал прокси (https, если TLS снят перед нами). + # Платформенная поверхность живёт на своём домене; пока он не задан, # матчер намеренно не совпадает ни с чем. @platform host {$CHATBALLS_PLATFORM_DOMAIN:platform.invalid} handle @platform { reverse_proxy backend-platform:8000 { - header_up Host {host} - header_up X-Real-IP {remote_host} - header_up X-Forwarded-For {remote_host} - header_up X-Forwarded-Proto {scheme} + header_up Host {hostport} + header_up X-Real-IP {client_ip} + header_up X-Forwarded-For {client_ip} } } @@ -35,10 +46,9 @@ # frontend-контейнера. handle { reverse_proxy frontend:80 { - header_up Host {host} - header_up X-Real-IP {remote_host} - header_up X-Forwarded-For {remote_host} - header_up X-Forwarded-Proto {scheme} + header_up Host {hostport} + header_up X-Real-IP {client_ip} + header_up X-Forwarded-For {client_ip} } } } diff --git a/deploy/nginx/frontend.production.conf b/deploy/nginx/frontend.production.conf index 0e81c35..368579e 100644 --- a/deploy/nginx/frontend.production.conf +++ b/deploy/nginx/frontend.production.conf @@ -25,7 +25,7 @@ server { location = /chat-widget.js { proxy_pass http://$backend_app; proxy_http_version 1.1; - proxy_set_header Host $host; + proxy_set_header Host $http_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 $http_x_forwarded_proto; @@ -42,7 +42,7 @@ server { location /api/ { proxy_pass http://$backend_app; proxy_http_version 1.1; - proxy_set_header Host $host; + proxy_set_header Host $http_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 $http_x_forwarded_proto; @@ -52,7 +52,7 @@ server { location /ws/ { proxy_pass http://$backend_app; proxy_http_version 1.1; - proxy_set_header Host $host; + proxy_set_header Host $http_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 $http_x_forwarded_proto; diff --git a/deploy/nginx/local.conf b/deploy/nginx/local.conf index 9fd7bc8..83ae92a 100644 --- a/deploy/nginx/local.conf +++ b/deploy/nginx/local.conf @@ -29,7 +29,7 @@ server { location /api/ { proxy_pass http://$backend_app; - proxy_set_header Host $host; + proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } @@ -37,7 +37,7 @@ server { location /ws/ { proxy_pass http://$backend_app; proxy_http_version 1.1; - proxy_set_header Host $host; + proxy_set_header Host $http_host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; @@ -45,14 +45,14 @@ server { location = /chat-widget.js { proxy_pass http://$backend_app; - proxy_set_header Host $host; + proxy_set_header Host $http_host; } location /chat/ { # Vite React Refresh injects an inline preamble in local development. add_header Content-Security-Policy "default-src 'self'; connect-src 'self' ws: wss:; img-src 'self' data:; media-src 'self' blob:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; object-src 'none'; base-uri 'self'; frame-ancestors *" always; proxy_pass http://$web_chat; - proxy_set_header Host $host; + proxy_set_header Host $http_host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } @@ -64,7 +64,7 @@ server { add_header Permissions-Policy "camera=(self), microphone=(self)" always; rewrite ^ /chat/call.html break; proxy_pass http://$web_chat; - proxy_set_header Host $host; + proxy_set_header Host $http_host; } location / { @@ -74,7 +74,7 @@ server { add_header X-Frame-Options SAMEORIGIN always; proxy_pass http://$frontend; proxy_http_version 1.1; - proxy_set_header Host $host; + proxy_set_header Host $http_host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -90,7 +90,7 @@ server { location /api/ { proxy_pass http://$backend_platform; - proxy_set_header Host $host; + proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }