Files
tripz-llc/docs/20-tls.md
T
Hamza-AyedandClaude Opus 4.8 d2a34f753c feat: TLS — إغلاق المنافذ المكشوفة + الثقة بالبروكسي + دليل الإصدار
النطاق: tripz-api.intaleqapp.com (قرار المالك) — مملوك وجاهز، ومحايد عن
اسم العلامة الذي لم يُحسم فلا يحتاج تغييراً لاحقاً.

ثغرتان كانتا ستُبطلان TLS من أصله:
- API كان منشوراً على 0.0.0.0:4010 → صار 127.0.0.1. بلا هذا يبقى
  http://IP:4010 مفتوحاً فيتجاوز TLS كلياً وتصير الشهادة زينة
- **Postgres كان مكشوفاً على 0.0.0.0:55432** — القاعدة على الإنترنت مباشرة
  → صار 127.0.0.1. المنفذ غير القياسي إخفاء لا أمان. الوصول من الماك عبر
  نفق SSH (docs/20 §4)

trust proxy = 1: خلف Nginx كان req.ip يصير 127.0.0.1 للجميع فيمتلئ سجل
التدقيق المالي (I7) بعنوان البروكسي بدل السائق — أثرٌ بلا قيمة عند النزاع.
قفزة واحدة فقط: الثقة المفتوحة تسمح للعميل بتزوير X-Forwarded-For.

docs/20: DNS → موقع reverse proxy → شهادة → **إعداد ترقية WebSocket**
(بدونه يسقط التتبّع الحيّ) + مهلة 3600s للنبضات طويلة العمر + فحص تحقّق
أهمّه أن http://IP:4010 يجب أن **يفشل**.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:36:14 +03:00

92 lines
5.3 KiB
Markdown

# 20 — TLS: إنهاء الطبقة المشفّرة أمام الـAPI
> **النطاق المعتمد: `tripz-api.intaleqapp.com`** (قرار المالك 2026-07-17).
> السبب في اختيار نطاق فرعي على `intaleqapp.com`: مملوك وجاهز بلا انتظار DNS، ومحايد عن اسم العلامة التجارية الذي لم يُحسم — فلا يحتاج تغييراً لاحقاً.
---
## 1. لماذا هذا يسبق كل عمل مدفوعات
الـAPI كان يعمل على `http://194.163.173.157:4010`. من يلتقط الشبكة (واي-فاي عام) يقرأ توكن أي سائق ويسحب أرباحه. **لا OTP ولا HMAC ولا سجل تدقيق يمنع ذلك** — كلها تُبنى فوق قناة مكشوفة. TLS هو الأساس، لا طبقة إضافية.
## 2. ثغرتان أُصلحتا في نفس الشغل (بلا هذا يصير TLS زينة)
| كان | صار | لماذا |
|-----|-----|--------|
| `4010:4010` أي `0.0.0.0` | `127.0.0.1:4010` | بلا هذا يبقى `http://IP:4010` مفتوحاً **فيتجاوز TLS كلياً**. الشهادة على النطاق لا تُغلق المنفذ. |
| `55432:5432` أي `0.0.0.0` | `127.0.0.1:55432` | **Postgres كان على الإنترنت مباشرة.** المنفذ غير القياسي إخفاء لا أمان. |
بعد هذا لا يصل للـAPI إلا Nginx المحلي، ولا تصل القاعدة إلا من داخل السيرفر.
## 3. الخطوات على السيرفر
### 3.1 DNS
سجل `A` لـ`tripz-api.intaleqapp.com` → `194.163.173.157`. تحقّق قبل المتابعة:
```bash
dig +short tripz-api.intaleqapp.com # يجب أن يطبع 194.163.173.157
```
> Let's Encrypt يفشل إن لم ينتشر الـDNS بعد. لا تُصدر الشهادة قبل أن يطبع الأمر العنوان الصحيح.
### 3.2 موقع Reverse Proxy في CloudPanel
CloudPanel → **Add Site** → **Create Reverse Proxy**:
- Domain: `tripz-api.intaleqapp.com`
- Reverse Proxy URL: `http://127.0.0.1:4010`
### 3.3 الشهادة
CloudPanel → الموقع → **SSL/TLS** → **Actions → New Let's Encrypt Certificate**.
CloudPanel يجدّدها تلقائياً.
### 3.4 دعم WebSocket (إلزامي — بدونه يسقط التتبّع الحيّ)
Socket.IO يحتاج ترقية الاتصال. في **Vhost Editor** للموقع، تأكّد أن كتلة `location /` تحوي:
```nginx
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header 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;
# نبضات المواقع والسوكت طويلة العمر — الافتراضي 60s يقطعها
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
```
`X-Forwarded-For` **ضروري**: بدونه يسجّل التدقيق المالي (docs/17 — I7) عنوان البروكسي لكل سائق، فيصير الأثر بلا قيمة. الباك إند يثق بقفزة واحدة فقط (`trust proxy = 1`) — الثقة المفتوحة تسمح للعميل بتزوير العنوان.
### 3.5 النشر
```bash
cd /home/tripz-llc && git pull origin main
cd backend && docker compose up -d --build
docker image prune -f
```
### 3.6 التحقق
```bash
curl -I https://tripz-api.intaleqapp.com/api/health # 200 عبر TLS
curl -I http://194.163.173.157:4010/api/health # يجب أن **يفشل** الآن
docker compose exec api printenv | grep -c PLATFORM_SECRET # 1
```
**الفحص الثاني هو الأهم**: نجاحه يعني أن المنفذ ما زال مكشوفاً وأن TLS يُتجاوَز.
## 4. الوصول للقاعدة بعد الإغلاق
لم تعد `55432` مفتوحة. من الماك:
```bash
ssh -L 55432:127.0.0.1:55432 root@194.163.173.157
# ثم اتصل بـ localhost:55432 كالمعتاد
```
سكربتات التحقق لا تتأثر — تعمل داخل شبكة `tripz-net` وتنادي `http://tripz-api:4010` مباشرة.
## 5. بعد نجاح TLS — خطوات تابعة
| البند | التفصيل |
|-------|---------|
| **فلاتر: BASE_URL** | `--dart-define BASE_URL=https://tripz-api.intaleqapp.com/api` |
| **فلاتر: إغلاق النصّ الصريح** | `usesCleartextTraffic=false` في `AndroidManifest` — يمنع أي رجوع صامت لـhttp |
| **تفعيل التوقيع** | `PAYMENTS_REQUIRE_SIGNATURE=true` بعد أن يوقّع فلاتر (docs/17 — I6) |
| **HSTS** | بعد التأكد من استقرار TLS — يمنع الرجوع لـhttp |
| **بقية `intaleqapp.com`** | `map-saas` و`nabeh` على https أصلاً ✅ |
## 6. ما لا يحلّه TLS
- **لا يحمي من تطبيق مُفكَّك**: TLS يحمي القناة لا الطرف. الحماية من الطرف هي الاستحقاقات على السيرفر ([19-entitlements-licensing](19-entitlements-licensing.md)).
- **لا يمنع pinning bypass**: من يملك الجهاز يرى حركته. لذلك تبقى القاعدة: **السيرفر لا يثق بالعميل أبداً**.
---
← ذو صلة: [15-deploy-flow](15-deploy-flow.md) · [14-server-conventions](14-server-conventions.md) · [17-backend-backlog](17-backend-backlog.md)