Server hardening: nginx origin locked to ArvanCloud + CSP, demo logins removed, docs
- ops/nginx/: the two nginx sites as they now run on 249. Only ArvanCloud edges reach the app (direct hits on the IP get 403, so Arvan's DDoS/WAF can't be skipped); the frontend gets CSP (own sources only, the one inline script by hash), X-Frame-Options, nosniff, Referrer-Policy, Permissions-Policy, HSTS; API body limit 100M -> 20M plus HSTS. Checked from outside and in Chrome (all tabs and an island quiz, logged in): no CSP errors. - Demo accounts (users 11-15, 25) and TEST_LOGIN_PHONES removed from the server; the health warning for fixed-code logins now has a test and says how to test instead (an OTPCode made in the admin). - SMS daily cap on the server is 2000 (launch of ~600 users). - CLAUDE.md: never run tests with --parallel on macOS, always --noinput (both hang forever without an error). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
23
ops/nginx/mathapp
Normal file
23
ops/nginx/mathapp
Normal file
@@ -0,0 +1,23 @@
|
||||
# روی ۲۴۹: /etc/nginx/sites-enabled/mathapp (فایلِ معمولی، نه symlink) — API، mathapi.tadrisino2.ir.
|
||||
server {
|
||||
listen 80;
|
||||
listen 443 ssl;
|
||||
server_name mathapi.tadrisino2.ir;
|
||||
|
||||
ssl_certificate /etc/ssl/certs/mathapp.crt;
|
||||
ssl_certificate_key /etc/ssl/private/mathapp.key;
|
||||
|
||||
include /etc/nginx/snippets/zareian-arvan-only.conf;
|
||||
|
||||
# بزرگترین آپلودِ واقعی فایلِ سوال/برنامهی درسی در پنلِ ادمین است (~۹ مگابایت). nginx بدنه را پیش از
|
||||
# رد شدن روی دیسکِ مشترک مینویسد، پس سقفِ ۱۰۰ مگابایتیِ قبلی جای پر کردنِ دیسک میداد.
|
||||
client_max_body_size 20M;
|
||||
|
||||
# بقیهی هدرهای امنیتی (nosniff، X-Frame-Options، Referrer-Policy) را خودِ جنگو میگذارد.
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
|
||||
location / {
|
||||
include proxy_params;
|
||||
proxy_pass http://unix:/run/gunicorn/mathapp.sock;
|
||||
}
|
||||
}
|
||||
31
ops/nginx/mathapp-front
Normal file
31
ops/nginx/mathapp-front
Normal file
@@ -0,0 +1,31 @@
|
||||
# روی ۲۴۹: /etc/nginx/sites-available/mathapp-front (sites-enabled یک symlink به آن است) — math.tadrisino2.ir.
|
||||
server {
|
||||
listen 80;
|
||||
listen 443 ssl;
|
||||
server_name math.tadrisino2.ir;
|
||||
ssl_certificate /etc/ssl/certs/mathapp.crt;
|
||||
ssl_certificate_key /etc/ssl/private/mathapp.key;
|
||||
root /home/administrator/mathapp_frontend_new/dist;
|
||||
|
||||
include /etc/nginx/snippets/zareian-arvan-only.conf;
|
||||
include /etc/nginx/snippets/zareian-front-headers.conf;
|
||||
|
||||
# index.html و config.js اسم ثابت دارند: مرورگر باید هر بار از سرور بپرسد (etag → ۳۰۴)،
|
||||
# وگرنه بعد از دیپلوی نسخهی قدیمی را نشان میدهد.
|
||||
add_header Cache-Control "no-cache" always;
|
||||
|
||||
location / {
|
||||
index index.html;
|
||||
try_files $uri $uri/ @fallback;
|
||||
}
|
||||
|
||||
# فایلهای assets/ اسمشان هش دارد و هیچوقت با همان اسم عوض نمیشوند.
|
||||
location /assets/ {
|
||||
include /etc/nginx/snippets/zareian-front-headers.conf;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||||
}
|
||||
|
||||
location @fallback {
|
||||
rewrite ^ /index.html break;
|
||||
}
|
||||
}
|
||||
22
ops/nginx/zareian-arvan-only.conf
Normal file
22
ops/nginx/zareian-arvan-only.conf
Normal file
@@ -0,0 +1,22 @@
|
||||
# روی ۲۴۹: /etc/nginx/snippets/zareian-arvan-only.conf — داخلِ server {}ِ دو سایتِ ما include میشود.
|
||||
# فقط edgeهای ArvanCloud (و خودِ ماشین) به اپ میرسند؛ درخواستِ مستقیم به IPِ سرور ۴۰۳ میگیرد، پس سپرِ
|
||||
# DDoS/WAFِ آروان دور زده نمیشود. فهرست از https://www.arvancloud.ir/fa/ips.txt (۲۰۲۶-۱۰-۱۳)؛ همان فهرستِ
|
||||
# TRUSTED_PROXY_NETWORKS در config/settings.py. اگر آروان رنجِ تازه بگیرد و اینجا نباشد، کاربرانِ پشتِ آن edge
|
||||
# ۴۰۳ میگیرند (در /var/log/nginx/access.log دیده میشود): هر دو فهرست را با هم تازه کنید.
|
||||
allow 127.0.0.1;
|
||||
allow ::1;
|
||||
allow 185.143.232.0/22;
|
||||
allow 188.229.116.16/30;
|
||||
allow 94.101.182.0/27;
|
||||
allow 2.144.3.128/28;
|
||||
allow 37.32.16.0/27;
|
||||
allow 37.32.17.0/27;
|
||||
allow 37.32.18.0/27;
|
||||
allow 37.32.19.0/27;
|
||||
allow 185.215.232.0/22;
|
||||
allow 178.131.120.48/28;
|
||||
allow 94.101.183.0/28;
|
||||
allow 78.157.36.112/28;
|
||||
allow 95.38.61.80/28;
|
||||
allow 193.24.119.0/29;
|
||||
deny all;
|
||||
17
ops/nginx/zareian-front-headers.conf
Normal file
17
ops/nginx/zareian-front-headers.conf
Normal file
@@ -0,0 +1,17 @@
|
||||
# روی ۲۴۹: /etc/nginx/snippets/zareian-front-headers.conf — هدرهای امنیتیِ math.tadrisino2.ir.
|
||||
# در هر locationی که خودش add_header دارد دوباره include شود: nginx هدرهای سطحِ server را در چنین locationی ارث نمیدهد.
|
||||
#
|
||||
# CSP فقط منابعِ خودمان را میپذیرد:
|
||||
# - script: فایلهای خودِ سایت، و تنها اسکریپتِ درونخطیِ index.html (کلاسِ in-app برای اسپلش) با هشش. اگر آن
|
||||
# اسکریپت در فرانت عوض شود، هش را از نو بسازید (python: base64(sha256(متنِ بینِ <script> و </script>)))؛
|
||||
# وگرنه فقط همان اسکریپت اجرا نمیشود و کنسول «Refused to execute inline script» میگوید.
|
||||
# - connect: API. با عوض شدنِ دامنهی API (config.js) اینجا هم عوض شود (docs/operations.md «عوض کردنِ دامنه»).
|
||||
# - worker blob: کتابخانهی confetti یک Worker از blob میسازد.
|
||||
# - style 'unsafe-inline': <style>ِ index.html و pay.html و استایلِ درونخطیِ React.
|
||||
# HSTS بی includeSubDomains: زیردامنههای دیگرِ tadrisino2.ir مالِ تیمهای دیگرند.
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'sha256-utBc36Q6M7NacvbHQCaMGCgm3osZ627xGQGm/tnSEXM='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://mathapi.tadrisino2.ir; worker-src 'self' blob:; media-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
Reference in New Issue
Block a user