رفتن به محتوا
LoopX

آزمون بخش ۲: reverse proxy و HTTPS

این آزمون چهار درس بخش «reverse proxy و HTTPS» را می‌سنجد: reverse proxy، WebSocket، HTTPS با Let’s Encrypt و امنیت و هدرهای امنیتی. جواب هر سؤال همان لحظه با توضیح نشان داده می‌شود و نتیجه‌ات در همین مرورگر ذخیره می‌شود (پیشرفت من).

؟ آزمون بخش ۲
  1. اپ پشت Nginx همه‌ی سفارش‌ها را از IP 127.0.0.1 می‌بیند. چه چیزی کم است؟

  2. ایمیل‌های بازیابی رمز لینک‌هایی به http://127.0.0.1:3000/reset دارند. علت؟

  3. در location /api/، تفاوت proxy_pass http://app; و proxy_pass http://app/; برای درخواست /api/users چیست؟

  4. کاربر هدر X-Forwarded-For: 8.8.8.8 فرستاده. اپ با $proxy_add_x_forwarded_for چه می‌بیند و به کدام IP اعتماد کند؟

  5. اپ خاموش است. کاربر چه کدی می‌گیرد و در error.log چه می‌بینی؟

  6. گزارشی که ۹۰ ثانیه طول می‌کشد بعد از ۶۰ ثانیه 504 می‌گیرد. چه تغییری؟

  7. آپلود فایل ۳ مگابایتی 413 می‌گیرد. چرا؟

  8. برای keepalive بین Nginx و اپ، کدام ترکیب لازم است؟

  9. چت WebSocket پشت Nginx با proxy_pass ساده خطای 400 می‌دهد. علت؟

  10. پاسخ موفق دست‌دادن WebSocket چیست؟

  11. چت بعد از یک دقیقه سکوت قطع می‌شود. دو راه‌حل؟

  12. certbot برای چالش HTTP-01 چه چیزی لازم دارد؟

  13. گواهی Let's Encrypt چند روزه است و تمدید خودکار با چیست؟

  14. بلافاصله بعد از گرفتن گواهی چه دستوری می‌زنی؟

  15. در ssl_certificate کدام فایل certbot را می‌گذاری؟

  16. چند سایت HTTPS روی یک IP داری. Nginx گواهی درست را از کجا انتخاب می‌کند؟

  17. server_tokens off چه می‌کند؟

  18. هدرهای امنیتی روی صفحه‌ی 404 نیستند ولی روی 200 هستند. چه چیزی جا افتاده؟

  19. HSTS را با includeSubDomains و max-age یک‌ساله گذاشته‌ای و intranet.example.com هنوز HTTPS ندارد. چه می‌شود؟

  20. با CSP script-src 'self'، چه اتفاقی برای یک <script> درون‌خطی می‌افتد؟

  21. در location /admin/ با allow 10.0.0.0/8; deny all; و auth_basic، کاربر از IP مجاز بدون رمز چه کدی می‌گیرد؟

🏁 چالش: اپ چت، پشت HTTPS، امنسخت

یک استقرار کامل بساز و با یک اسکریپت ثابت کن:

۱. یک اپ کوچک (Node یا Python) روی 127.0.0.1 با دو بخش: یک API (/api/whoami که IP، Host و پروتکلی را که می‌بیند برگرداند) و یک WebSocket (/ws/ که پیام را برگرداند: echo). ۲. Nginx با HTTPS برای app.test (گواهی خودامضا کافی است) و ریدایرکت HTTP به HTTPS. ۳. /api/ با هدرهای درست proxy (اپ باید IP واقعی، app.test و https را ببیند) و proxy_read_timeout 10s. ۴. /ws/ با تنظیم درست WebSocket. ۵. هدرهای امنیتی با always و server_tokens off. ۶. اسکریپت آزمایش: ریدایرکت، پاسخ API، دست‌دادن WebSocket (101)، و وجود HSTS روی ۴۰۴.

راهنمایی

اپ را با Python و کتابخانه‌ی websockets (برای /ws/) یا Node بنویس؛ ساده‌ترین راه، دو پروسه‌ی جدا روی دو پورت (3000 برای API، 8765 برای WebSocket). گواهی: openssl req -x509 ... با subjectAltName=DNS:app.test. هدرهای proxy: include proxy_params;. برای آزمایش با گواهی خودامضا: curl --cacert. برای WebSocket: curl -i -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: ...'.

راه‌حل
Terminal window
pkill -f 'lx-quiz2' 2>/dev/null
rm -rf /srv/lx-quiz2 && mkdir -p /srv/lx-quiz2 /etc/nginx/ssl
grep -v '\.test$' /etc/hosts > /tmp/hosts.lx; cat /tmp/hosts.lx > /etc/hosts
echo '127.0.0.1 app.test' >> /etc/hosts
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq python3-websockets > /dev/null 2>&1
cat > /srv/lx-quiz2/lx-quiz2-api.js <<'EOF'
// /api/whoami: what the app sees about the client
require("http").createServer((req, res) => {
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({
ip: req.headers["x-real-ip"], host: req.headers["host"], proto: req.headers["x-forwarded-proto"],
}) + "\n");
}).listen(3000, "127.0.0.1");
EOF
cat > /srv/lx-quiz2/lx-quiz2-ws.py <<'EOF'
# echo WebSocket server
import asyncio, websockets
async def echo(ws, path=None):
async for msg in ws:
await ws.send("echo: " + msg)
async def main():
async with websockets.serve(echo, "127.0.0.1", 8765):
await asyncio.Future()
asyncio.run(main())
EOF
(exec node /srv/lx-quiz2/lx-quiz2-api.js) > /dev/null 2>&1 &
(exec python3 /srv/lx-quiz2/lx-quiz2-ws.py) > /dev/null 2>&1 &
openssl req -x509 -newkey rsa:2048 -nodes -days 30 -keyout /etc/nginx/ssl/app.key -out /etc/nginx/ssl/app.crt \
-subj '/CN=app.test' -addext 'subjectAltName=DNS:app.test' 2>/dev/null
chmod 600 /etc/nginx/ssl/app.key
sed -i 's/^\(\s*\)# server_tokens off;/\1server_tokens off;/' /etc/nginx/nginx.conf
cat > /etc/nginx/snippets/lx-quiz2-headers.conf <<'EOF'
add_header Strict-Transport-Security "max-age=300" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
EOF
cat > /etc/nginx/sites-available/app.test <<'EOF'
server {
listen 80;
server_name app.test;
return 301 https://app.test$request_uri;
}
server {
listen 443 ssl;
server_name app.test;
ssl_certificate /etc/nginx/ssl/app.crt;
ssl_certificate_key /etc/nginx/ssl/app.key;
include snippets/lx-quiz2-headers.conf;
location /api/ {
proxy_pass http://127.0.0.1:3000;
include proxy_params;
proxy_read_timeout 10s;
}
location /ws/ {
proxy_pass http://127.0.0.1:8765;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 1h;
}
}
EOF
ln -sf /etc/nginx/sites-available/app.test /etc/nginx/sites-enabled/
sleep 1
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
c() { curl -s --cacert /etc/nginx/ssl/app.crt "$@"; }
echo "1) redirect: $(curl -s -o /dev/null -w '%{http_code} %{redirect_url}' http://app.test/api/whoami)"
echo "2) api: $(c --interface 127.0.0.42 https://app.test/api/whoami)"
echo "3) ws: $(c -i -m 2 -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: SGVsbG8gTG9vcFggMTIzNA==' https://app.test/ws/ | head -n 1 | tr -d '\r')"
python3 - <<'EOF'
import asyncio, ssl, websockets
async def main():
ctx = ssl.create_default_context(cafile="/etc/nginx/ssl/app.crt")
async with websockets.connect("wss://app.test/ws/", ssl=ctx) as ws:
await ws.send("hi")
print(" wss echo:", await ws.recv())
asyncio.run(main())
EOF
echo "4) 404 headers: $(c -I https://app.test/nope | grep -ciE '^(strict-transport|x-frame|x-content|referrer)') of 4 security headers, Server: $(c -I https://app.test/nope | grep -i '^server' | tr -d '\r' | cut -d' ' -f2)"
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
1) redirect: 301 https://app.test/api/whoami
2) api: {"ip":"127.0.0.42","host":"app.test","proto":"https"}
3) ws: HTTP/1.1 101 Switching Protocols
wss echo: echo: hi
4) 404 headers: 4 of 4 security headers, Server: nginx

هر قسمت ثابت شد: ریدایرکت با حفظ مسیر؛ اپ IP واقعی (127.0.0.42)، app.test و https را دید؛ دست‌دادن WebSocket از روی HTTPS با 101 جواب گرفت و یک کلاینت واقعی روی wss:// پیام echo گرفت؛ و صفحه‌ی ۴۰۴ هر چهار هدر امنیتی را دارد و Server بدون نسخه است. (max-age=300 برای آزمایش؛ در سایت واقعی یک سال.)