docs: aktualizacja architektury po migracji na OVH VPS + e-deklaracja w roadmap

- ROADMAP: dodano funkcję #2 (e-deklaracja PZ) z analizą flow PDF + samodzielny podpis
- architecture/03,07,08,09,11 + flows/06: aktualizacja pod OVH VPS (IP, user maciejpi zamiast www-data, brak NPM dla prod)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-04-15 07:00:14 +02:00
parent 5893645c46
commit bfd48a6e20
7 changed files with 263 additions and 297 deletions

View File

@ -93,6 +93,35 @@ Najwyższy poziom (Enterprise) służy jako **kotwica cenowa** — sprawia że P
| # | Funkcjonalność | Opis | Zgłosił | Data | Priorytet | Status | | # | Funkcjonalność | Opis | Zgłosił | Data | Priorytet | Status |
|---|---------------|------|---------|------|-----------|--------| |---|---------------|------|---------|------|-----------|--------|
| 1 | Wiele lokalizacji firmy na mapie | Firmy z kilkoma oddziałami/punktami mogą dodać wiele adresów. Każdy wyświetlany na profilu z linkiem do Google Maps. Nowa tabela `company_locations` (label, adres, lat/lng na przyszłość), edycja w zakładce Kontakt w profilu firmy, wzorzec jak CompanyWebsite (dynamiczne wiersze, delete-and-reinsert). | Daniel Kochański (Stalpunkt) | 2026-04-09 | Średni | Planowane | | 1 | Wiele lokalizacji firmy na mapie | Firmy z kilkoma oddziałami/punktami mogą dodać wiele adresów. Każdy wyświetlany na profilu z linkiem do Google Maps. Nowa tabela `company_locations` (label, adres, lat/lng na przyszłość), edycja w zakładce Kontakt w profilu firmy, wzorzec jak CompanyWebsite (dynamiczne wiersze, delete-and-reinsert). | Daniel Kochański (Stalpunkt) | 2026-04-09 | Średni | Planowane |
| 2 | E-deklaracja członkowska z podpisem elektronicznym | Elektroniczny formularz deklaracji przystąpienia do Izby z możliwością podpisu Profilem Zaufanym. Szczegóły poniżej w sekcji dedykowanej. | Jacek Pomieczyński (Eura-Tech), Paweł Kwidziński | 2026-04-08 | Niski | Rozważane |
#### E-deklaracja członkowska — analiza (priorytet: niski)
**Kontekst:** Jacek Pomieczyński (Eura-Tech) i Paweł Kwidziński postulują elektroniczną deklarację przystąpienia do Izby zamiast papierowego formularza. Poprzednia próba wdrożenia zablokowana przez brak rozwiązania podpisu reprezentacji (kto jest uprawniony do podpisania w imieniu firmy).
**Rekomendowany flow (bez integracji API, zero kosztów):**
1. Użytkownik wypełnia formularz deklaracji na NordaBiznes.pl (dane firmy, NIP, KRS, dane osoby reprezentującej)
2. System generuje gotowy PDF deklaracji
3. Użytkownik pobiera PDF i podpisuje go **samodzielnie** Profilem Zaufanym na moj.gov.pl → "Podpisz dokument elektronicznie" (lub w aplikacji mObywatel)
4. Użytkownik uploaduje podpisany plik (.xml z podpisem XAdES) z powrotem na portal
5. Biuro Nordy weryfikuje podpis (moj.gov.pl → "Sprawdź podpis elektroniczny") i proceduje przyjęcie
**Co trzeba zbudować:** formularz deklaracji + generator PDF (np. WeasyPrint/ReportLab). Infrastruktura podpisu — darmowa, po stronie państwa.
**Wymagania prawne (do ustalenia z mec. Masiakiem):**
- Czy statut Izby wymaga formy pisemnej deklaracji?
- Jeśli tak — zmiana statutu dopuszczająca formę elektroniczną (możliwa na Walnym 28.05.2026)
- Podpis Zaufany w relacjach prywatnych nie jest automatycznie równoważny z podpisem ręcznym — statut musi go wprost dopuszczać
**Rozważane alternatywy (odrzucone lub odłożone):**
- Autenti/Signius (API podpisu) — działa, ale zbędny koszt i zależność od zewnętrznego dostawcy przy tak prostym flow
- Bezpośrednia integracja z API Podpisu Zaufanego (podpis.gov.pl) — niedostępne dla podmiotów prywatnych
- Integracja logowania przez Węzeł Krajowy (login.gov.pl) — możliwa technicznie (SAML 2.0), ale nadmiarowa dla samych deklaracji
**Uwaga:** Przy skali ~10-15 nowych członków rocznie pełna automatyzacja (API) jest nieuzasadniona. Prosty flow PDF + samodzielny podpis PZ rozwiązuje problem.
---
#### Funkcje strategiczne (ze slajdu 9, prezentacja Rada 13.02.2026) #### Funkcje strategiczne (ze slajdu 9, prezentacja Rada 13.02.2026)

View File

@ -7,7 +7,7 @@
--- ---
> **UWAGA (2026-04-04):** Produkcja przeniesiona z OVH VPS inpi-vps-waw01 (VM 249, 57.128.200.27) na **OVH VPS (57.128.200.27, hostname inpi-vps-waw01)**. Diagramy Mermaid poniżej odzwierciedlają starą architekturę — faktyczny stan to: DNS nordabiznes.pl -> 57.128.200.27 (bezpośrednio, bez NPM). NPM (10.22.68.250) obsługuje tylko staging. > **NOTE (2026-04-04):** Production migrated to OVH VPS (57.128.200.27, hostname inpi-vps-waw01). Traffic flow: Internet -> Nginx (57.128.200.27:443) -> Gunicorn (127.0.0.1:5000). No FortiGate or NPM for production. NPM (10.22.68.250) serves staging only.
## Overview ## Overview
@ -108,7 +108,7 @@ graph TB
|--------|-------|------------|----------|-----|------------| |--------|-------|------------|----------|-----|------------|
| **NORDABIZ-STAGING-01** | 248 | 10.22.68.248 | nordabiz-staging-01 | Ubuntu 22.04 | Proxmox VE | | **NORDABIZ-STAGING-01** | 248 | 10.22.68.248 | nordabiz-staging-01 | Ubuntu 22.04 | Proxmox VE |
**Note:** The old on-prem production VM 249 (57.128.200.27) and NPM reverse proxy (10.22.68.250) are no longer used for production. Staging still uses NPM + FortiGate path. **Note:** The old on-prem production VM 249 (10.22.68.249) and NPM reverse proxy (10.22.68.250) are no longer used for production. Staging still uses NPM + FortiGate path.
--- ---
@ -440,7 +440,7 @@ curl -I https://nordabiznes.pl/health
**API Key Storage:** **API Key Storage:**
- **Location:** `/var/www/nordabiznes/.env` (production) - **Location:** `/var/www/nordabiznes/.env` (production)
- **Permissions:** `chmod 600` (owner read/write only) - **Permissions:** `chmod 600` (owner read/write only)
- **Owner:** `www-data:www-data` - **Owner:** `maciejpi:maciejpi`
- **⚠️ NEVER commit to git!** (in `.gitignore`) - **⚠️ NEVER commit to git!** (in `.gitignore`)
--- ---
@ -816,7 +816,7 @@ sudo -u postgres psql -c "SELECT 1;"
# Expected: 1 # Expected: 1
# 3. Check application database access # 3. Check application database access
sudo -u www-data psql -h 127.0.0.1 -U nordabiz_app -d nordabiz -c "SELECT 1;" sudo -u maciejpi psql -h 127.0.0.1 -U nordabiz_app -d nordabiz -c "SELECT 1;"
# Expected: 1 (if password prompt, check .env) # Expected: 1 (if password prompt, check .env)
# 4. Check active connections # 4. Check active connections

View File

@ -7,7 +7,7 @@
--- ---
> **UWAGA (2026-04-04):** Produkcja przeniesiona z OVH VPS inpi-vps-waw01 (VM 249, 57.128.200.27) na **OVH VPS (57.128.200.27, hostname inpi-vps-waw01)**. Diagramy Mermaid poniżej odzwierciedlają starą architekturę — ruch produkcyjny nie przechodzi już przez Fortigate/NPM. Staging (10.22.68.248) i NPM (10.22.68.250) bez zmian. > **NOTE (2026-04-04):** Production migrated to OVH VPS (57.128.200.27). Production traffic no longer goes through FortiGate/NPM. Staging (10.22.68.248) and NPM (10.22.68.250) remain unchanged.
## Overview ## Overview
@ -422,8 +422,9 @@ App Server (57.128.200.27)
| Record Type | Name | Value | TTL | Purpose | | Record Type | Name | Value | TTL | Purpose |
|-------------|------|-------|-----|---------| |-------------|------|-------|-----|---------|
| A | nordabiznes.pl | 85.237.177.83 | 3600 | Main website | | A | nordabiznes.pl | 57.128.200.27 | 3600 | Main website (OVH VPS) |
| A | www.nordabiznes.pl | 85.237.177.83 | 3600 | WWW subdomain | | A | www.nordabiznes.pl | 57.128.200.27 | 3600 | WWW subdomain (OVH VPS) |
| A | staging.nordabiznes.pl | 85.237.177.83 | 3600 | Staging (via FortiGate) |
| MX | nordabiznes.pl | (Not configured) | - | No email hosting | | MX | nordabiznes.pl | (Not configured) | - | No email hosting |
| TXT | nordabiznes.pl | (SPF, DKIM if configured) | - | Email authentication | | TXT | nordabiznes.pl | (SPF, DKIM if configured) | - | Email authentication |
@ -431,11 +432,11 @@ App Server (57.128.200.27)
```bash ```bash
# Check DNS resolution # Check DNS resolution
dig nordabiznes.pl +short dig nordabiznes.pl +short
# Expected: 85.237.177.83 # Expected: 57.128.200.27
# Check from Google DNS # Check from Google DNS
dig @8.8.8.8 nordabiznes.pl +short dig @8.8.8.8 nordabiznes.pl +short
# Expected: 85.237.177.83 # Expected: 57.128.200.27
# Check WHOIS # Check WHOIS
whois nordabiznes.pl whois nordabiznes.pl
@ -499,23 +500,19 @@ default via 10.22.68.1 dev ens18 # All non-local traffic → Fortigate
``` ```
User Browser (Internet) User Browser (Internet)
↓ DNS query ↓ DNS query
OVH DNS: nordabiznes.pl → 85.237.177.83 OVH DNS: nordabiznes.pl → 57.128.200.27
↓ HTTPS :443 ↓ HTTPS :443
Fortigate WAN (85.237.177.83:443) Nginx @ OVH VPS (57.128.200.27:443)
↓ NAT translation ↓ SSL termination, proxy_pass
Fortigate LAN → NPM (10.22.68.250:443) Gunicorn (127.0.0.1:5000)
↓ SSL termination, proxy
NPM → Flask/Gunicorn (57.128.200.27:5000)
↓ HTTP request processing ↓ HTTP request processing
Flask → PostgreSQL (127.0.0.1:5432) Flask → PostgreSQL (127.0.0.1:5432)
↓ SQL query ↓ SQL query
PostgreSQL → Flask (result set) PostgreSQL → Flask (result set)
↓ HTML rendering ↓ HTML rendering
Flask → NPM (HTTP response) Flask → Nginx (HTTP response)
↓ SSL encryption ↓ SSL encryption
NPM → Fortigate LAN (HTTPS) Nginx → User Browser (57.128.200.27:443)
↓ NAT reverse
Fortigate WAN → User Browser (85.237.177.83:443)
``` ```
**Total Hops:** 6 (external) + 3 (internal) = 9 hops **Total Hops:** 6 (external) + 3 (internal) = 9 hops

View File

@ -364,7 +364,7 @@ curl -vI https://nordabiznes.pl 2>&1 | grep "expire date"
### Production Environment Variables ### Production Environment Variables
**Location:** `/var/www/nordabiznes/.env` **Location:** `/var/www/nordabiznes/.env`
**Owner:** `www-data:www-data` **Owner:** `maciejpi:maciejpi`
**Permissions:** `0600` (read/write owner only) **Permissions:** `0600` (read/write owner only)
**⚠️ WARNING:** Never commit `.env` to version control! **⚠️ WARNING:** Never commit `.env` to version control!
@ -419,7 +419,7 @@ openssl rand -base64 32
# Verify .env file permissions # Verify .env file permissions
ls -la /var/www/nordabiznes/.env ls -la /var/www/nordabiznes/.env
# Expected: -rw------- 1 www-data www-data ... .env # Expected: -rw------- 1 maciejpi maciejpi ... .env
``` ```
### Development Environment Variables ### Development Environment Variables
@ -470,7 +470,7 @@ postgresql://nordabiz_app:NordaBiz2025Secure@127.0.0.1:5433/nordabiz
**Direct psql (production):** **Direct psql (production):**
```bash ```bash
# As application user # As application user
sudo -u www-data psql -U nordabiz_app -d nordabiz -h 127.0.0.1 psql -U nordabiz_app -d nordabiz -h 127.0.0.1
# As postgres superuser # As postgres superuser
sudo -u postgres psql nordabiz sudo -u postgres psql nordabiz
@ -744,7 +744,7 @@ curl -I https://nordabiznes.pl/health
### SSH Keys for Git Access ### SSH Keys for Git Access
**Production Server:** **Production Server:**
- Location: `/home/www-data/.ssh/` - Location: `/home/maciejpi/.ssh/`
- Public key: `id_rsa.pub` - Public key: `id_rsa.pub`
- Private key: `id_rsa` - Private key: `id_rsa`
- Known hosts: `known_hosts` (includes Gitea fingerprint) - Known hosts: `known_hosts` (includes Gitea fingerprint)
@ -761,14 +761,14 @@ curl -I https://nordabiznes.pl/health
| Path | Description | Owner | Permissions | | Path | Description | Owner | Permissions |
|------|-------------|-------|-------------| |------|-------------|-------|-------------|
| `/var/www/nordabiznes/` | Application root directory | www-data:www-data | 0755 | | `/var/www/nordabiznes/` | Application root directory | maciejpi:maciejpi | 0755 |
| `/var/www/nordabiznes/app.py` | Main Flask application | www-data:www-data | 0644 | | `/var/www/nordabiznes/app.py` | Main Flask application | maciejpi:maciejpi | 0644 |
| `/var/www/nordabiznes/.env` | Environment variables (secrets) | www-data:www-data | 0600 | | `/var/www/nordabiznes/.env` | Environment variables (secrets) | maciejpi:maciejpi | 0600 |
| `/var/www/nordabiznes/venv/` | Python virtual environment | www-data:www-data | 0755 | | `/var/www/nordabiznes/venv/` | Python virtual environment | maciejpi:maciejpi | 0755 |
| `/var/www/nordabiznes/static/` | Static assets (CSS, JS, images) | www-data:www-data | 0755 | | `/var/www/nordabiznes/static/` | Static assets (CSS, JS, images) | maciejpi:maciejpi | 0755 |
| `/var/www/nordabiznes/templates/` | Jinja2 templates | www-data:www-data | 0755 | | `/var/www/nordabiznes/templates/` | Jinja2 templates | maciejpi:maciejpi | 0755 |
| `/var/www/nordabiznes/database.py` | SQLAlchemy models | www-data:www-data | 0644 | | `/var/www/nordabiznes/database.py` | SQLAlchemy models | maciejpi:maciejpi | 0644 |
| `/var/www/nordabiznes/scripts/` | Background scripts | www-data:www-data | 0755 | | `/var/www/nordabiznes/scripts/` | Background scripts | maciejpi:maciejpi | 0755 |
### Configuration Files ### Configuration Files
@ -782,8 +782,8 @@ curl -I https://nordabiznes.pl/health
| Path | Description | Rotation | Owner | | Path | Description | Rotation | Owner |
|------|-------------|----------|-------| |------|-------------|----------|-------|
| `/var/log/nordabiznes/gunicorn_access.log` | Gunicorn access log | Daily | www-data | | `/var/log/nordabiznes/gunicorn_access.log` | Gunicorn access log | Daily | maciejpi |
| `/var/log/nordabiznes/gunicorn_error.log` | Gunicorn error log | Daily | www-data | | `/var/log/nordabiznes/gunicorn_error.log` | Gunicorn error log | Daily | maciejpi |
| `/var/log/postgresql/postgresql-*.log` | PostgreSQL logs | Weekly | postgres | | `/var/log/postgresql/postgresql-*.log` | PostgreSQL logs | Weekly | postgres |
| `journalctl -u nordabiznes` | Systemd service logs | 30 days | root | | `journalctl -u nordabiznes` | Systemd service logs | 30 days | root |
@ -953,20 +953,20 @@ sudo chmod 600 /home/maciejpi/backups/.env.backup
4. **Clone application:** 4. **Clone application:**
```bash ```bash
sudo mkdir -p /var/www/nordabiznes sudo mkdir -p /var/www/nordabiznes
sudo chown www-data:www-data /var/www/nordabiznes sudo chown maciejpi:maciejpi /var/www/nordabiznes
sudo -u www-data git clone https://10.22.68.180:3000/maciejpi/nordabiz.git /var/www/nordabiznes sudo -u maciejpi git clone https://10.22.68.180:3000/maciejpi/nordabiz.git /var/www/nordabiznes
``` ```
5. **Restore .env file:** 5. **Restore .env file:**
```bash ```bash
sudo cp /path/to/.env.backup /var/www/nordabiznes/.env sudo cp /path/to/.env.backup /var/www/nordabiznes/.env
sudo chown www-data:www-data /var/www/nordabiznes/.env sudo chown maciejpi:maciejpi /var/www/nordabiznes/.env
sudo chmod 600 /var/www/nordabiznes/.env sudo chmod 600 /var/www/nordabiznes/.env
``` ```
6. **Install Python dependencies:** 6. **Install Python dependencies:**
```bash ```bash
cd /var/www/nordabiznes cd /var/www/nordabiznes
sudo -u www-data python3 -m venv venv sudo -u maciejpi python3 -m venv venv
sudo -u www-data venv/bin/pip install -r requirements.txt sudo -u maciejpi venv/bin/pip install -r requirements.txt
``` ```
7. **Configure systemd service:** 7. **Configure systemd service:**
```bash ```bash
@ -999,7 +999,7 @@ sudo chmod 600 /home/maciejpi/backups/.env.backup
# Application # Application
cd /var/www/nordabiznes cd /var/www/nordabiznes
sudo -u www-data git status # Ensure clean state sudo -u maciejpi git status # Ensure clean state
``` ```
2. **Test changes in development** 2. **Test changes in development**
@ -1022,7 +1022,7 @@ sudo chmod 600 /home/maciejpi/backups/.env.backup
# Pull changes # Pull changes
cd /var/www/nordabiznes cd /var/www/nordabiznes
sudo -u www-data git pull sudo -u maciejpi git pull
# Restart service # Restart service
sudo systemctl restart nordabiznes sudo systemctl restart nordabiznes
@ -1047,7 +1047,7 @@ sudo chmod 600 /home/maciejpi/backups/.env.backup
sudo systemctl stop nordabiznes sudo systemctl stop nordabiznes
# Rollback code # Rollback code
sudo -u www-data git reset --hard HEAD~1 sudo -u maciejpi git reset --hard HEAD~1
# Rollback database (if needed) # Rollback database (if needed)
sudo -u postgres psql nordabiz < /tmp/backup_before_change.sql sudo -u postgres psql nordabiz < /tmp/backup_before_change.sql
@ -1130,10 +1130,10 @@ curl -I https://nordabiznes.pl/health
```bash ```bash
# Check database connectivity # Check database connectivity
sudo -u www-data psql -U nordabiz_app -d nordabiz -h 127.0.0.1 -c "SELECT COUNT(*) FROM companies;" psql -U nordabiz_app -d nordabiz -h 127.0.0.1 -c "SELECT COUNT(*) FROM companies;"
# Check recent data # Check recent data
sudo -u www-data psql -U nordabiz_app -d nordabiz -h 127.0.0.1 -c \ psql -U nordabiz_app -d nordabiz -h 127.0.0.1 -c \
"SELECT name, slug FROM companies ORDER BY created_at DESC LIMIT 5;" "SELECT name, slug FROM companies ORDER BY created_at DESC LIMIT 5;"
# Check database size # Check database size
@ -1249,7 +1249,7 @@ curl -I https://nordabiznes.pl/health # Test health
ssh maciejpi@57.128.200.27 ssh maciejpi@57.128.200.27
cd /var/www/nordabiznes cd /var/www/nordabiznes
sudo systemctl stop nordabiznes sudo systemctl stop nordabiznes
sudo -u www-data git reset --hard HEAD~1 sudo -u maciejpi git reset --hard HEAD~1
sudo systemctl start nordabiznes sudo systemctl start nordabiznes
curl -I https://nordabiznes.pl/health curl -I https://nordabiznes.pl/health
``` ```

View File

@ -809,7 +809,7 @@ SMTP_PASSWORD=<password>
**Secrets Storage:** **Secrets Storage:**
- **Development:** `.env` file (gitignored) - **Development:** `.env` file (gitignored)
- **Production:** `.env` file on server (owned by www-data, mode 600) - **Production:** `.env` file on server (owned by maciejpi, mode 600)
- **Never committed to Git:** `.env` in `.gitignore` - **Never committed to Git:** `.env` in `.gitignore`
**Secret Rotation:** **Secret Rotation:**
@ -819,7 +819,7 @@ SMTP_PASSWORD=<password>
**Access Control:** **Access Control:**
```bash ```bash
# Production secrets file permissions # Production secrets file permissions
-rw------- 1 www-data www-data .env # Mode 600 (owner read/write only) -rw------- 1 maciejpi maciejpi .env # Mode 600 (owner read/write only)
``` ```
--- ---
@ -1151,7 +1151,7 @@ app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # CSRF protection
**Storage:** **Storage:**
- **Location:** `.env` file - **Location:** `.env` file
- **Permissions:** 600 (owner read/write only) - **Permissions:** 600 (owner read/write only)
- **Owner:** www-data (Flask app user) - **Owner:** maciejpi (Flask app user)
**Best Practices:** **Best Practices:**
- ✅ API keys in .env (not hardcoded) - ✅ API keys in .env (not hardcoded)
@ -1274,7 +1274,7 @@ deny all from 10.22.68.250 to ANY # NPM cannot initiate outbound
- **Port:** 22 (default) - **Port:** 22 (default)
- **Access:** Restricted to ADMIN_NET (firewall rule) - **Access:** Restricted to ADMIN_NET (firewall rule)
- **Authentication:** SSH key-based (password auth disabled) - **Authentication:** SSH key-based (password auth disabled)
- **Users:** maciejpi (primary admin), www-data (app deployment) - **Users:** maciejpi (admin + app user)
**SSH Hardening:** **SSH Hardening:**
```bash ```bash
@ -1283,7 +1283,7 @@ PermitRootLogin no # Disable root login
PasswordAuthentication no # Key-based auth only PasswordAuthentication no # Key-based auth only
PubkeyAuthentication yes # Allow SSH keys PubkeyAuthentication yes # Allow SSH keys
X11Forwarding no # Disable X11 X11Forwarding no # Disable X11
AllowUsers maciejpi www-data # Whitelist users AllowUsers maciejpi # Whitelist users
``` ```
**SSH Key Management:** **SSH Key Management:**
@ -1312,16 +1312,16 @@ AllowUsers maciejpi www-data # Whitelist users
**File Permissions:** **File Permissions:**
```bash ```bash
# Application directory # Application directory
/var/www/nordabiznes/ # Owner: www-data, Mode: 755 /var/www/nordabiznes/ # Owner: maciejpi, Mode: 755
/var/www/nordabiznes/.env # Owner: www-data, Mode: 600 (secrets) /var/www/nordabiznes/.env # Owner: maciejpi, Mode: 600 (secrets)
/var/www/nordabiznes/app.py # Owner: www-data, Mode: 644 /var/www/nordabiznes/app.py # Owner: maciejpi, Mode: 644
# Database directory # Database directory
/var/lib/postgresql/ # Owner: postgres, Mode: 700 /var/lib/postgresql/ # Owner: postgres, Mode: 700
``` ```
**User Separation:** **User Separation:**
- **www-data:** Flask application (limited privileges) - **maciejpi:** Flask application (limited privileges)
- **postgres:** PostgreSQL database (limited privileges) - **postgres:** PostgreSQL database (limited privileges)
- **maciejpi:** Admin user (sudo access) - **maciejpi:** Admin user (sudo access)
@ -1643,7 +1643,7 @@ docker restart <npm-container-id>
- ✅ Firewall (Fortigate) - ✅ Firewall (Fortigate)
- ✅ Network segmentation (DMZ, App, Data zones) - ✅ Network segmentation (DMZ, App, Data zones)
- ✅ SSH key-based authentication - ✅ SSH key-based authentication
- ✅ Principle of least privilege (www-data user) - ✅ Principle of least privilege (maciejpi user)
- ❌ IDS/IPS - ❌ IDS/IPS
- ❌ DDoS protection (beyond Fortigate) - ❌ DDoS protection (beyond Fortigate)

View File

@ -71,48 +71,36 @@ graph TD
## 2. Infrastructure & Network Issues ## 2. Infrastructure & Network Issues
### 2.1 ERR_TOO_MANY_REDIRECTS ### 2.1 Site Not Accessible / ERR_TOO_MANY_REDIRECTS
**Severity:** CRITICAL **Severity:** CRITICAL
**Incident History:** 2026-01-02 (30 min outage)
> **Note:** The ERR_TOO_MANY_REDIRECTS issue from 2026-01-02 was specific to the old NPM+on-prem setup. With the current OVH VPS architecture, redirect loops are unlikely since nginx and Gunicorn run on the same host.
#### Symptoms #### Symptoms
- Browser error: `ERR_TOO_MANY_REDIRECTS` - Browser error or timeout accessing https://nordabiznes.pl
- Portal completely inaccessible via https://nordabiznes.pl - Portal completely inaccessible
- Internal access works fine (http://57.128.200.27:5000)
- Affects 100% of external users
#### Root Cause
Nginx Proxy Manager (NPM) configured to forward to **port 80** instead of **port 5000**.
**Why this causes redirect loop:**
1. NPM forwards HTTPS → HTTP to backend port 80
2. Nginx on port 80 sees HTTP and redirects to HTTPS
3. Request goes back to NPM, creating infinite loop
4. Browser aborts after ~20 redirects
#### Diagnosis #### Diagnosis
```bash ```bash
# 1. Check NPM proxy configuration # 1. Check nginx status on OVH VPS
ssh maciejpi@10.22.68.250 ssh maciejpi@57.128.200.27 "sudo systemctl status nginx"
docker exec nginx-proxy-manager_app_1 \
sqlite3 /data/database.sqlite \
"SELECT id, domain_names, forward_host, forward_port FROM proxy_host WHERE id = 27;"
# Expected output: # 2. Check Gunicorn status
# 27|["nordabiznes.pl","www.nordabiznes.pl"]|57.128.200.27|5000 ssh maciejpi@57.128.200.27 "sudo systemctl status nordabiznes"
# If forward_port shows 80 → PROBLEM FOUND! # 3. Test backend directly
ssh maciejpi@57.128.200.27 "curl -I http://localhost:5000/health"
# 2. Test backend directly
curl -I http://57.128.200.27:80/
# If this returns 301 redirect → confirms issue
curl -I http://57.128.200.27:5000/health
# Should return 200 OK if Flask is running # Should return 200 OK if Flask is running
# 4. Check DNS resolution
dig nordabiznes.pl +short
# Expected: 57.128.200.27
# 5. Test nginx config
ssh maciejpi@57.128.200.27 "sudo nginx -t"
``` ```
#### Solution #### Solution
@ -127,54 +115,30 @@ open http://10.22.68.250:81
# 3. Edit configuration: # 3. Edit configuration:
# - Forward Hostname/IP: 57.128.200.27 # - Forward Hostname/IP: 57.128.200.27
# - Forward Port: 5000 (CRITICAL!) # - Forward Port: 5000 (CRITICAL!)
# - Scheme: http #### Solution
# 4. Save and test
```
**Option B: Fix via NPM API** ```bash
# If nginx is down:
ssh maciejpi@57.128.200.27 "sudo systemctl restart nginx"
```python # If Gunicorn is down:
import requests ssh maciejpi@57.128.200.27 "sudo systemctl restart nordabiznes"
NPM_URL = "http://10.22.68.250:81/api" # If nginx config is broken:
# Login to get token first (see NPM API docs) ssh maciejpi@57.128.200.27 "sudo nginx -t && sudo systemctl reload nginx"
data = {
"domain_names": ["nordabiznes.pl", "www.nordabiznes.pl"],
"forward_scheme": "http",
"forward_host": "57.128.200.27",
"forward_port": 5000, # CRITICAL: Must be 5000!
"certificate_id": 27,
"ssl_forced": True,
"http2_support": True
}
response = requests.put(
f"{NPM_URL}/nginx/proxy-hosts/27",
headers={"Authorization": f"Bearer {token}"},
json=data
)
``` ```
#### Verification #### Verification
```bash ```bash
# 1. External test (from outside INPI network)
curl -I https://nordabiznes.pl/health curl -I https://nordabiznes.pl/health
# Expected: HTTP/2 200 # Expected: HTTP/2 200
# 2. Check NPM logs
ssh maciejpi@10.22.68.250
docker logs nginx-proxy-manager_app_1 --tail 20
# Should show 200 responses, not 301
``` ```
#### Prevention #### Prevention
- **ALWAYS verify port 5000 after ANY NPM configuration change** - Monitor /health endpoint for non-200 responses
- Add monitoring alert for non-200 responses on /health - Test nginx config before reload: `sudo nginx -t`
- Document NPM configuration in change requests
- Test from external network before marking changes complete
--- ---
@ -185,14 +149,13 @@ docker logs nginx-proxy-manager_app_1 --tail 20
#### Symptoms #### Symptoms
- Browser shows "502 Bad Gateway" error - Browser shows "502 Bad Gateway" error
- NPM logs show "upstream connection failed" - nginx error log shows "upstream connection failed"
- Site completely inaccessible - Site completely inaccessible
#### Root Causes #### Root Causes
1. Flask/Gunicorn service stopped 1. Flask/Gunicorn service stopped
2. Backend server (57.128.200.27) unreachable 2. Gunicorn not listening on 127.0.0.1:5000
3. Firewall blocking port 5000
#### Diagnosis #### Diagnosis
@ -233,28 +196,26 @@ curl http://localhost:5000/health
```bash ```bash
# Check for syntax errors # Check for syntax errors
cd /var/www/nordabiznes cd /var/www/nordabiznes
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 -m py_compile app.py /var/www/nordabiznes/venv/bin/python3 -m py_compile app.py
# Check for missing dependencies # Check for missing dependencies
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 -c "import flask; import sqlalchemy" /var/www/nordabiznes/venv/bin/python3 -c "import flask; import sqlalchemy"
# Check environment variables # Check environment variables
sudo -u www-data cat /var/www/nordabiznes/.env | grep -v "PASSWORD\|SECRET\|KEY" cat /var/www/nordabiznes/.env | grep -v "PASSWORD\|SECRET\|KEY"
# Try running manually (for debugging) # Try running manually (for debugging)
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 app.py /var/www/nordabiznes/venv/bin/python3 app.py
``` ```
**If network issue:** **If Gunicorn not responding:**
```bash ```bash
# Test connectivity from NPM to backend # Check if Gunicorn is listening
ssh maciejpi@10.22.68.250 ssh maciejpi@57.128.200.27 "ss -tlnp | grep 5000"
curl -I http://57.128.200.27:5000/health
# Check firewall rules # Check nginx error log
ssh maciejpi@57.128.200.27 ssh maciejpi@57.128.200.27 "sudo tail -20 /var/log/nginx/error.log"
sudo iptables -L -n | grep 5000
``` ```
#### Verification #### Verification
@ -292,11 +253,11 @@ ps aux | grep gunicorn
# Look for zombie workers or high CPU usage # Look for zombie workers or high CPU usage
# 2. Check database connections # 2. Check database connections
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c \ psql -h localhost -U nordabiz_app -d nordabiz -c \
"SELECT count(*) FROM pg_stat_activity WHERE datname = 'nordabiz';" "SELECT count(*) FROM pg_stat_activity WHERE datname = 'nordabiz';"
# 3. Check for long-running queries # 3. Check for long-running queries
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c \ psql -h localhost -U nordabiz_app -d nordabiz -c \
"SELECT pid, now() - query_start AS duration, query "SELECT pid, now() - query_start AS duration, query
FROM pg_stat_activity FROM pg_stat_activity
WHERE state = 'active' AND now() - query_start > interval '5 seconds';" WHERE state = 'active' AND now() - query_start > interval '5 seconds';"
@ -316,7 +277,7 @@ sudo journalctl -u nordabiznes -n 100 --no-pager | grep -E "slow|timeout|took"
```bash ```bash
# Identify and kill long-running query # Identify and kill long-running query
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
# Find problematic query # Find problematic query
SELECT pid, query FROM pg_stat_activity SELECT pid, query FROM pg_stat_activity
@ -383,11 +344,8 @@ echo | openssl s_client -servername nordabiznes.pl -connect nordabiznes.pl:443 2
# 2. Check certificate details # 2. Check certificate details
curl -vI https://nordabiznes.pl 2>&1 | grep -E "SSL|certificate" curl -vI https://nordabiznes.pl 2>&1 | grep -E "SSL|certificate"
# 3. Check NPM certificate status # 3. Check certbot certificate status
ssh maciejpi@10.22.68.250 ssh maciejpi@57.128.200.27 "sudo certbot certificates"
docker exec nginx-proxy-manager_app_1 \
sqlite3 /data/database.sqlite \
"SELECT id, nice_name, expires_on FROM certificate WHERE id = 27;"
``` ```
#### Solution #### Solution
@ -395,18 +353,12 @@ docker exec nginx-proxy-manager_app_1 \
**If certificate expired:** **If certificate expired:**
```bash ```bash
# NPM auto-renews Let's Encrypt certificates # certbot auto-renews Let's Encrypt certificates
# Force renewal via NPM UI or API # Force renewal:
ssh maciejpi@57.128.200.27 "sudo certbot renew --force-renewal"
# Via UI: # Reload nginx after renewal:
# 1. Access http://10.22.68.250:81 ssh maciejpi@57.128.200.27 "sudo systemctl reload nginx"
# 2. SSL Certificates → nordabiznes.pl
# 3. Click "Renew" button
# Via CLI (if auto-renewal failed):
ssh maciejpi@10.22.68.250
docker exec nginx-proxy-manager_app_1 \
node /app/index.js certificate renew 27
``` ```
**If mixed content warnings:** **If mixed content warnings:**
@ -445,9 +397,9 @@ nslookup nordabiznes.inpi.local 10.22.68.1
# 3. Test from different locations # 3. Test from different locations
curl -I -H "Host: nordabiznes.pl" http://85.237.177.83/health curl -I -H "Host: nordabiznes.pl" http://85.237.177.83/health
# 4. Check Fortigate NAT rules # 4. Check DNS points to OVH VPS
# Access Fortigate admin panel and verify NAT entry: dig nordabiznes.pl +short
# External: 85.237.177.83:443 → Internal: 10.22.68.250:443 # Expected: 57.128.200.27
``` ```
#### Solution #### Solution
@ -494,7 +446,7 @@ sudo journalctl -u nordabiznes -n 100 --no-pager
# 3. Try manual start for detailed error # 3. Try manual start for detailed error
cd /var/www/nordabiznes cd /var/www/nordabiznes
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 app.py /var/www/nordabiznes/venv/bin/python3 app.py
# Read the traceback carefully # Read the traceback carefully
``` ```
@ -507,12 +459,12 @@ sudo -u www-data /var/www/nordabiznes/venv/bin/python3 app.py
# Cause: Recent code change introduced syntax error # Cause: Recent code change introduced syntax error
# Fix: Check syntax # Fix: Check syntax
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 -m py_compile app.py /var/www/nordabiznes/venv/bin/python3 -m py_compile app.py
# Rollback if necessary # Rollback if necessary
cd /var/www/nordabiznes cd /var/www/nordabiznes
sudo -u www-data git log --oneline -5 git log --oneline -5
sudo -u www-data git revert HEAD # or specific commit git revert HEAD # or specific commit
sudo systemctl restart nordabiznes sudo systemctl restart nordabiznes
``` ```
@ -523,8 +475,8 @@ sudo systemctl restart nordabiznes
# Cause: .env file missing or incomplete # Cause: .env file missing or incomplete
# Fix: Check .env exists and has required variables # Fix: Check .env exists and has required variables
sudo -u www-data ls -la /var/www/nordabiznes/.env ls -la /var/www/nordabiznes/.env
sudo -u www-data cat /var/www/nordabiznes/.env | grep -E "^[A-Z_]+=" | wc -l cat /var/www/nordabiznes/.env | grep -E "^[A-Z_]+=" | wc -l
# Should have ~20 environment variables # Should have ~20 environment variables
# Required variables (add if missing): # Required variables (add if missing):
@ -547,7 +499,7 @@ sudo -u www-data cat /var/www/nordabiznes/.env | grep -E "^[A-Z_]+=" | wc -l
sudo systemctl status postgresql sudo systemctl status postgresql
# Test connection # Test connection
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;" psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;"
# If password wrong, update .env and restart # If password wrong, update .env and restart
``` ```
@ -560,10 +512,10 @@ sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;"
# Fix: Reinstall dependencies # Fix: Reinstall dependencies
cd /var/www/nordabiznes cd /var/www/nordabiznes
sudo -u www-data /var/www/nordabiznes/venv/bin/pip install -r requirements.txt /var/www/nordabiznes/venv/bin/pip install -r requirements.txt
# Verify specific package # Verify specific package
sudo -u www-data /var/www/nordabiznes/venv/bin/pip show flask /var/www/nordabiznes/venv/bin/pip show flask
``` ```
**E. Port 5000 Already in Use** **E. Port 5000 Already in Use**
@ -635,7 +587,7 @@ sudo journalctl -u nordabiznes -n 100 | grep -i jinja
# Test template syntax # Test template syntax
cd /var/www/nordabiznes cd /var/www/nordabiznes
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 -c " /var/www/nordabiznes/venv/bin/python3 -c "
from jinja2 import Template from jinja2 import Template
with open('templates/index.html') as f: with open('templates/index.html') as f:
Template(f.read()) Template(f.read())
@ -669,7 +621,7 @@ with open('templates/index.html') as f:
sudo journalctl -u nordabiznes -n 50 | grep -i "sqlalchemy\|database" sudo journalctl -u nordabiznes -n 50 | grep -i "sqlalchemy\|database"
# Check database connectivity # Check database connectivity
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;" psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;"
``` ```
--- ---
@ -695,7 +647,7 @@ ssh maciejpi@57.128.200.27
sudo journalctl -u nordabiznes -n 100 | grep -i search sudo journalctl -u nordabiznes -n 100 | grep -i search
# 3. Test database FTS # 3. Test database FTS
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
# Test FTS query # Test FTS query
SELECT name, ts_rank(search_vector, to_tsquery('polish', 'web')) AS score SELECT name, ts_rank(search_vector, to_tsquery('polish', 'web')) AS score
@ -716,7 +668,7 @@ SELECT * FROM pg_extension WHERE extname = 'pg_trgm';
# Cause: search_vector not updated # Cause: search_vector not updated
# Fix: Rebuild FTS index # Fix: Rebuild FTS index
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
UPDATE companies SET search_vector = UPDATE companies SET search_vector =
to_tsvector('polish', to_tsvector('polish',
@ -750,7 +702,7 @@ grep -A 20 "SYNONYM_EXPANSION" search_service.py
# Cause: Missing database indexes # Cause: Missing database indexes
# Fix: Add indexes # Fix: Add indexes
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
CREATE INDEX IF NOT EXISTS idx_companies_search_vector ON companies USING gin(search_vector); CREATE INDEX IF NOT EXISTS idx_companies_search_vector ON companies USING gin(search_vector);
CREATE INDEX IF NOT EXISTS idx_companies_name_trgm ON companies USING gin(name gin_trgm_ops); CREATE INDEX IF NOT EXISTS idx_companies_name_trgm ON companies USING gin(name gin_trgm_ops);
@ -787,7 +739,7 @@ Quick check:
```bash ```bash
# 1. Verify Gemini API key # 1. Verify Gemini API key
ssh maciejpi@57.128.200.27 ssh maciejpi@57.128.200.27
sudo -u www-data cat /var/www/nordabiznes/.env | grep GEMINI_API_KEY cat /var/www/nordabiznes/.env | grep GEMINI_API_KEY
# Should not be empty # Should not be empty
# 2. Test Gemini API directly # 2. Test Gemini API directly
@ -877,7 +829,7 @@ sudo systemctl restart nordabiznes
#### Verification #### Verification
```bash ```bash
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT count(*) FROM companies;" psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT count(*) FROM companies;"
# Should return count # Should return count
``` ```
@ -897,7 +849,7 @@ sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT count(
```bash ```bash
# 1. Check for slow queries # 1. Check for slow queries
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
SELECT pid, now() - query_start AS duration, query SELECT pid, now() - query_start AS duration, query
FROM pg_stat_activity FROM pg_stat_activity
@ -927,7 +879,7 @@ ALTER DATABASE nordabiz SET log_min_duration_statement = 1000;
```bash ```bash
# Add appropriate indexes based on queries # Add appropriate indexes based on queries
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
-- Example: Index on foreign key -- Example: Index on foreign key
CREATE INDEX idx_company_news_company_id ON company_news(company_id); CREATE INDEX idx_company_news_company_id ON company_news(company_id);
@ -945,7 +897,7 @@ VACUUM ANALYZE;
```bash ```bash
# Run vacuum # Run vacuum
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
VACUUM ANALYZE; VACUUM ANALYZE;
# For severe cases # For severe cases
@ -955,7 +907,7 @@ VACUUM FULL companies; -- Locks table!
**If table statistics outdated:** **If table statistics outdated:**
```bash ```bash
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
ANALYZE companies; ANALYZE companies;
ANALYZE users; ANALYZE users;
ANALYZE ai_chat_messages; ANALYZE ai_chat_messages;
@ -991,7 +943,7 @@ df -h
# Check /var/lib/postgresql usage # Check /var/lib/postgresql usage
# 2. Check database size # 2. Check database size
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
SELECT pg_size_pretty(pg_database_size('nordabiz')); SELECT pg_size_pretty(pg_database_size('nordabiz'));
@ -1038,7 +990,7 @@ sudo -u postgres pg_archivecleanup /var/lib/postgresql/*/main/pg_wal/ 0000000100
```bash ```bash
# Archive old data before deletion # Archive old data before deletion
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
-- Example: Archive old AI chat messages (>6 months) -- Example: Archive old AI chat messages (>6 months)
CREATE TABLE ai_chat_messages_archive AS CREATE TABLE ai_chat_messages_archive AS
@ -1067,7 +1019,7 @@ VACUUM FULL ai_chat_messages;
```bash ```bash
# 1. Check current schema version # 1. Check current schema version
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
\dt \dt
# List all tables # List all tables
@ -1079,7 +1031,7 @@ ls -la /var/www/nordabiznes/database/migrations/
# 3. Check Flask-Migrate status (if using Alembic) # 3. Check Flask-Migrate status (if using Alembic)
cd /var/www/nordabiznes cd /var/www/nordabiznes
sudo -u www-data /var/www/nordabiznes/venv/bin/flask db current /var/www/nordabiznes/venv/bin/flask db current
``` ```
#### Solution #### Solution
@ -1089,14 +1041,14 @@ sudo -u www-data /var/www/nordabiznes/venv/bin/flask db current
```bash ```bash
# Re-run migration script # Re-run migration script
cd /var/www/nordabiznes cd /var/www/nordabiznes
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz < database/schema.sql psql -h localhost -U nordabiz_app -d nordabiz < database/schema.sql
``` ```
**If column added but missing:** **If column added but missing:**
```bash ```bash
# Add column manually # Add column manually
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
ALTER TABLE companies ADD COLUMN IF NOT EXISTS new_column VARCHAR(255); ALTER TABLE companies ADD COLUMN IF NOT EXISTS new_column VARCHAR(255);
@ -1108,16 +1060,16 @@ GRANT ALL ON TABLE companies TO nordabiz_app;
```bash ```bash
# Rollback last migration # Rollback last migration
sudo -u www-data /var/www/nordabiznes/venv/bin/flask db downgrade /var/www/nordabiznes/venv/bin/flask db downgrade
# Re-apply # Re-apply
sudo -u www-data /var/www/nordabiznes/venv/bin/flask db upgrade /var/www/nordabiznes/venv/bin/flask db upgrade
``` ```
#### Verification #### Verification
```bash ```bash
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
\d companies \d companies
# Verify schema matches expected structure # Verify schema matches expected structure
``` ```
@ -1141,7 +1093,7 @@ sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz
```bash ```bash
# 1. Check API usage in database # 1. Check API usage in database
ssh maciejpi@57.128.200.27 ssh maciejpi@57.128.200.27
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
-- Gemini API usage today -- Gemini API usage today
SELECT COUNT(*), SUM(input_tokens), SUM(output_tokens) SELECT COUNT(*), SUM(input_tokens), SUM(output_tokens)
@ -1211,7 +1163,7 @@ pkill -f seo_audit.py
# Create script: /var/www/nordabiznes/scripts/check_api_quotas.sh # Create script: /var/www/nordabiznes/scripts/check_api_quotas.sh
#!/bin/bash #!/bin/bash
GEMINI_COUNT=$(sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -t -c \ GEMINI_COUNT=$(psql -h localhost -U nordabiz_app -d nordabiz -t -c \
"SELECT COUNT(*) FROM ai_api_costs WHERE DATE(created_at) = CURRENT_DATE;") "SELECT COUNT(*) FROM ai_api_costs WHERE DATE(created_at) = CURRENT_DATE;")
if [ "$GEMINI_COUNT" -gt 1400 ]; then if [ "$GEMINI_COUNT" -gt 1400 ]; then
@ -1240,7 +1192,7 @@ fi
```bash ```bash
# 1. Test Gemini API directly # 1. Test Gemini API directly
GEMINI_KEY=$(sudo -u www-data grep GEMINI_API_KEY /var/www/nordabiznes/.env | cut -d= -f2) GEMINI_KEY=$( grep GEMINI_API_KEY /var/www/nordabiznes/.env | cut -d= -f2)
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent" \ curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent" \
-H "x-goog-api-key: $GEMINI_KEY" \ -H "x-goog-api-key: $GEMINI_KEY" \
@ -1252,7 +1204,7 @@ ssh maciejpi@57.128.200.27
sudo journalctl -u nordabiznes -n 100 | grep -i gemini sudo journalctl -u nordabiznes -n 100 | grep -i gemini
# 3. Check conversation ownership # 3. Check conversation ownership
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
SELECT id, user_id, created_at SELECT id, user_id, created_at
FROM ai_chat_conversations FROM ai_chat_conversations
@ -1268,7 +1220,7 @@ WHERE id = 123; -- Replace with conversation ID
# OR context too long # OR context too long
# Check last message for safety filter # Check last message for safety filter
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
SELECT message, ai_response, error_message SELECT message, ai_response, error_message
FROM ai_chat_messages FROM ai_chat_messages
@ -1325,7 +1277,7 @@ AND created_at < (
# Symptom: 401 Unauthorized or 403 Forbidden # Symptom: 401 Unauthorized or 403 Forbidden
# Verify API key # Verify API key
sudo -u www-data grep GEMINI_API_KEY /var/www/nordabiznes/.env grep GEMINI_API_KEY /var/www/nordabiznes/.env
# Test key directly # Test key directly
curl -H "x-goog-api-key: YOUR_KEY" \ curl -H "x-goog-api-key: YOUR_KEY" \
@ -1363,7 +1315,7 @@ curl -X POST https://nordabiznes.pl/api/chat \
```bash ```bash
# 1. Test PageSpeed API directly # 1. Test PageSpeed API directly
PAGESPEED_KEY=$(sudo -u www-data grep GOOGLE_PAGESPEED_API_KEY /var/www/nordabiznes/.env | cut -d= -f2) PAGESPEED_KEY=$( grep GOOGLE_PAGESPEED_API_KEY /var/www/nordabiznes/.env | cut -d= -f2)
curl "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://nordabiznes.pl&key=$PAGESPEED_KEY" curl "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://nordabiznes.pl&key=$PAGESPEED_KEY"
@ -1372,7 +1324,7 @@ ssh maciejpi@57.128.200.27
sudo journalctl -u nordabiznes -n 100 | grep -i pagespeed sudo journalctl -u nordabiznes -n 100 | grep -i pagespeed
# 3. Check recent audits # 3. Check recent audits
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
SELECT company_id, url, seo_score, performance_score, SELECT company_id, url, seo_score, performance_score,
audited_at, error_message audited_at, error_message
@ -1435,13 +1387,13 @@ python seo_audit.py --batch 1-10
```bash ```bash
# 1. Test Brave API directly # 1. Test Brave API directly
BRAVE_KEY=$(sudo -u www-data grep BRAVE_SEARCH_API_KEY /var/www/nordabiznes/.env | cut -d= -f2) BRAVE_KEY=$( grep BRAVE_SEARCH_API_KEY /var/www/nordabiznes/.env | cut -d= -f2)
curl -H "X-Subscription-Token: $BRAVE_KEY" \ curl -H "X-Subscription-Token: $BRAVE_KEY" \
"https://api.search.brave.com/res/v1/news/search?q=test&count=5" "https://api.search.brave.com/res/v1/news/search?q=test&count=5"
# 2. Check usage this month # 2. Check usage this month
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
SELECT COUNT(*) AS searches_this_month SELECT COUNT(*) AS searches_this_month
FROM company_news FROM company_news
@ -1496,7 +1448,7 @@ sudo systemctl restart nordabiznes
```bash ```bash
# 1. Check user exists and is active # 1. Check user exists and is active
ssh maciejpi@57.128.200.27 ssh maciejpi@57.128.200.27
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
SELECT id, email, is_active, email_verified, failed_login_attempts SELECT id, email, is_active, email_verified, failed_login_attempts
FROM users FROM users
@ -1519,7 +1471,7 @@ curl -c /tmp/cookies.txt -X POST http://localhost:5000/login \
```bash ```bash
# Check failed attempts # Check failed attempts
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
SELECT email, failed_login_attempts, last_failed_login SELECT email, failed_login_attempts, last_failed_login
FROM users FROM users
@ -1618,7 +1570,7 @@ curl -c /tmp/cookies.txt -X POST http://localhost:5000/login \
```bash ```bash
# 1. Check user role # 1. Check user role
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
SELECT id, email, is_admin, is_norda_member SELECT id, email, is_admin, is_norda_member
FROM users FROM users
@ -1639,7 +1591,7 @@ sudo journalctl -u nordabiznes -n 50 | grep -i "forbidden\|unauthorized"
```bash ```bash
# Grant admin role # Grant admin role
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
UPDATE users SET is_admin = TRUE UPDATE users SET is_admin = TRUE
WHERE email = 'admin@nordabiznes.pl'; WHERE email = 'admin@nordabiznes.pl';
@ -1691,7 +1643,7 @@ WHERE email = 'user@example.com';
```bash ```bash
# 1. Check user reset token # 1. Check user reset token
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
SELECT email, reset_token, reset_token_expiry SELECT email, reset_token, reset_token_expiry
FROM users FROM users
@ -1702,7 +1654,7 @@ sudo journalctl -u nordabiznes -n 100 | grep -i "email\|smtp"
# 3. Test MS Graph API (email service) # 3. Test MS Graph API (email service)
# Check if AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID set # Check if AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID set
sudo -u www-data grep AZURE /var/www/nordabiznes/.env grep AZURE /var/www/nordabiznes/.env
``` ```
#### Solution #### Solution
@ -1722,7 +1674,7 @@ WHERE email = 'user@example.com';
```bash ```bash
# Check MS Graph credentials # Check MS Graph credentials
sudo -u www-data python3 << 'EOF' python3 << 'EOF'
import os import os
from email_service import EmailService from email_service import EmailService
@ -1743,14 +1695,14 @@ EOF
```bash ```bash
# Generate new password hash # Generate new password hash
sudo -u www-data python3 << 'EOF' python3 << 'EOF'
from werkzeug.security import generate_password_hash from werkzeug.security import generate_password_hash
password = "NewPassword123" password = "NewPassword123"
print(generate_password_hash(password)) print(generate_password_hash(password))
EOF EOF
# Update database # Update database
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
UPDATE users SET password_hash = 'HASH_FROM_ABOVE' UPDATE users SET password_hash = 'HASH_FROM_ABOVE'
WHERE email = 'user@example.com'; WHERE email = 'user@example.com';
@ -1786,7 +1738,7 @@ top -n 1
# Look at: CPU usage, memory usage, load average # Look at: CPU usage, memory usage, load average
# 4. Check database query times # 4. Check database query times
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
SELECT calls, mean_exec_time, query SELECT calls, mean_exec_time, query
FROM pg_stat_statements FROM pg_stat_statements
@ -1830,7 +1782,7 @@ ALTER SYSTEM SET shared_preload_libraries = 'pg_stat_statements';
sudo systemctl restart postgresql sudo systemctl restart postgresql
# Check slow queries # Check slow queries
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
SELECT calls, mean_exec_time, query SELECT calls, mean_exec_time, query
FROM pg_stat_statements FROM pg_stat_statements
@ -2016,7 +1968,7 @@ uptime
# Load should be < number of CPU cores # Load should be < number of CPU cores
# 3. Identify CPU-heavy queries # 3. Identify CPU-heavy queries
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
SELECT pid, now() - query_start AS duration, state, query SELECT pid, now() - query_start AS duration, state, query
FROM pg_stat_activity FROM pg_stat_activity
@ -2030,7 +1982,7 @@ ORDER BY now() - query_start DESC;
```bash ```bash
# Kill long-running query # Kill long-running query
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
SELECT pg_terminate_backend(PID); SELECT pg_terminate_backend(PID);
# Add index to optimize query # Add index to optimize query
@ -2085,11 +2037,10 @@ curl https://nordabiznes.pl/health
} }
# Database health # Database health
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;" psql -h localhost -U nordabiz_app -d nordabiz -c "SELECT 1;"
# NPM health (from proxy server) # Nginx health (on OVH VPS)
ssh maciejpi@10.22.68.250 ssh maciejpi@57.128.200.27 "sudo systemctl status nginx"
docker ps | grep nginx-proxy-manager
# Should show: Up X hours # Should show: Up X hours
# Flask service health # Flask service health
@ -2112,9 +2063,8 @@ sudo journalctl -u nordabiznes -f
# PostgreSQL logs # PostgreSQL logs
sudo journalctl -u postgresql -n 50 sudo journalctl -u postgresql -n 50
# NPM logs # Nginx logs (OVH VPS)
ssh maciejpi@10.22.68.250 ssh maciejpi@57.128.200.27 "sudo tail -50 /var/log/nginx/error.log"
docker logs nginx-proxy-manager_app_1 --tail 50 -f
# System logs # System logs
sudo journalctl -n 100 sudo journalctl -n 100
@ -2145,7 +2095,7 @@ fi
```bash ```bash
# Database performance # Database performance
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
-- Connection count -- Connection count
SELECT count(*) FROM pg_stat_activity; SELECT count(*) FROM pg_stat_activity;
@ -2254,7 +2204,7 @@ sudo systemctl restart nordabiznes
# If restart fails, check manually # If restart fails, check manually
cd /var/www/nordabiznes cd /var/www/nordabiznes
sudo -u www-data /var/www/nordabiznes/venv/bin/python3 app.py /var/www/nordabiznes/venv/bin/python3 app.py
# Read error message # Read error message
# 3. Common quick fixes: # 3. Common quick fixes:
@ -2303,7 +2253,7 @@ sudo systemctl stop nordabiznes
sudo -u postgres pg_dump nordabiz > /tmp/nordabiz_emergency_$(date +%Y%m%d_%H%M%S).sql sudo -u postgres pg_dump nordabiz > /tmp/nordabiz_emergency_$(date +%Y%m%d_%H%M%S).sql
# 3. Assess damage # 3. Assess damage
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
-- Check table counts -- Check table counts
SELECT 'companies' AS table, count(*) FROM companies SELECT 'companies' AS table, count(*) FROM companies
@ -2341,7 +2291,7 @@ sudo systemctl start nordabiznes
```bash ```bash
# Identify missing/corrupted records # Identify missing/corrupted records
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
-- Example: Find companies with NULL required fields -- Example: Find companies with NULL required fields
SELECT id, slug, name FROM companies WHERE name IS NULL; SELECT id, slug, name FROM companies WHERE name IS NULL;
@ -2372,7 +2322,7 @@ sudo -u postgres pg_dump nordabiz > /tmp/forensic_$(date +%Y%m%d_%H%M%S).sql
sudo tar czf /tmp/www_forensic.tar.gz /var/www/nordabiznes/ sudo tar czf /tmp/www_forensic.tar.gz /var/www/nordabiznes/
# 3. Check for unauthorized access # 3. Check for unauthorized access
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
-- Check for new admin users -- Check for new admin users
SELECT id, email, created_at, is_admin SELECT id, email, created_at, is_admin
@ -2406,7 +2356,7 @@ sudo find /var/www/nordabiznes/ -type f -mtime -1 -ls
sudo grep -r "eval\|exec\|system\|subprocess" /var/www/nordabiznes/*.py sudo grep -r "eval\|exec\|system\|subprocess" /var/www/nordabiznes/*.py
# Check database for malicious data # Check database for malicious data
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
SELECT * FROM users WHERE email LIKE '%<script%'; SELECT * FROM users WHERE email LIKE '%<script%';
SELECT * FROM companies WHERE description LIKE '%<script%'; SELECT * FROM companies WHERE description LIKE '%<script%';
@ -2421,7 +2371,7 @@ SELECT * FROM companies WHERE description LIKE '%<script%';
# - API keys # - API keys
# 2. Revoke compromised sessions # 2. Revoke compromised sessions
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz psql -h localhost -U nordabiz_app -d nordabiz
DELETE FROM flask_sessions; -- Force all users to re-login DELETE FROM flask_sessions; -- Force all users to re-login
# 3. Update all API keys # 3. Update all API keys
@ -2449,7 +2399,7 @@ curl -I https://nordabiznes.pl/health && \
echo -e "\n=== Service Status ===" && \ echo -e "\n=== Service Status ===" && \
ssh maciejpi@57.128.200.27 "sudo systemctl status nordabiznes --no-pager | head -5" && \ ssh maciejpi@57.128.200.27 "sudo systemctl status nordabiznes --no-pager | head -5" && \
echo -e "\n=== Database Connection ===" && \ echo -e "\n=== Database Connection ===" && \
ssh maciejpi@57.128.200.27 "sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz -c 'SELECT count(*) FROM companies;'" && \ ssh maciejpi@57.128.200.27 "psql -h localhost -U nordabiz_app -d nordabiz -c 'SELECT count(*) FROM companies;'" && \
echo -e "\n=== Server Load ===" && \ echo -e "\n=== Server Load ===" && \
ssh maciejpi@57.128.200.27 "uptime" ssh maciejpi@57.128.200.27 "uptime"
``` ```
@ -2476,7 +2426,7 @@ ssh maciejpi@10.22.68.250 "curl -I http://57.128.200.27:5000/health"
```bash ```bash
# Database quick stats # Database quick stats
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz << 'EOF' psql -h localhost -U nordabiz_app -d nordabiz << 'EOF'
SELECT 'Companies' AS metric, count(*) AS value FROM companies SELECT 'Companies' AS metric, count(*) AS value FROM companies
UNION ALL SELECT 'Users', count(*) FROM users UNION ALL SELECT 'Users', count(*) FROM users
UNION ALL SELECT 'Active sessions', count(*) FROM pg_stat_activity UNION ALL SELECT 'Active sessions', count(*) FROM pg_stat_activity
@ -2484,7 +2434,7 @@ UNION ALL SELECT 'DB size (MB)', pg_database_size('nordabiz')/1024/1024;
EOF EOF
# Find slow queries # Find slow queries
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz << 'EOF' psql -h localhost -U nordabiz_app -d nordabiz << 'EOF'
SELECT pid, now() - query_start AS duration, query SELECT pid, now() - query_start AS duration, query
FROM pg_stat_activity FROM pg_stat_activity
WHERE state = 'active' AND now() - query_start > interval '2 seconds' WHERE state = 'active' AND now() - query_start > interval '2 seconds'
@ -2492,7 +2442,7 @@ ORDER BY duration DESC;
EOF EOF
# Check locks # Check locks
sudo -u www-data psql -h localhost -U nordabiz_app -d nordabiz << 'EOF' psql -h localhost -U nordabiz_app -d nordabiz << 'EOF'
SELECT relation::regclass, mode, granted SELECT relation::regclass, mode, granted
FROM pg_locks FROM pg_locks
WHERE NOT granted; WHERE NOT granted;
@ -2528,16 +2478,16 @@ echo | openssl s_client -servername nordabiznes.pl -connect nordabiznes.pl:443 2
ssh maciejpi@57.128.200.27 ssh maciejpi@57.128.200.27
# Gemini API # Gemini API
GEMINI_KEY=$(sudo -u www-data grep GEMINI_API_KEY .env | cut -d= -f2) GEMINI_KEY=$( grep GEMINI_API_KEY .env | cut -d= -f2)
curl -s -H "x-goog-api-key: $GEMINI_KEY" \ curl -s -H "x-goog-api-key: $GEMINI_KEY" \
"https://generativelanguage.googleapis.com/v1beta/models" | jq '.models[0].name' "https://generativelanguage.googleapis.com/v1beta/models" | jq '.models[0].name'
# PageSpeed API # PageSpeed API
PAGESPEED_KEY=$(sudo -u www-data grep GOOGLE_PAGESPEED_API_KEY .env | cut -d= -f2) PAGESPEED_KEY=$( grep GOOGLE_PAGESPEED_API_KEY .env | cut -d= -f2)
curl -s "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://nordabiznes.pl&key=$PAGESPEED_KEY" | jq '.lighthouseResult.categories.performance.score' curl -s "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://nordabiznes.pl&key=$PAGESPEED_KEY" | jq '.lighthouseResult.categories.performance.score'
# Brave Search API # Brave Search API
BRAVE_KEY=$(sudo -u www-data grep BRAVE_SEARCH_API_KEY .env | cut -d= -f2) BRAVE_KEY=$( grep BRAVE_SEARCH_API_KEY .env | cut -d= -f2)
curl -s -H "X-Subscription-Token: $BRAVE_KEY" \ curl -s -H "X-Subscription-Token: $BRAVE_KEY" \
"https://api.search.brave.com/res/v1/web/search?q=test&count=1" | jq '.web.results[0].title' "https://api.search.brave.com/res/v1/web/search?q=test&count=1" | jq '.web.results[0].title'

View File

@ -1,34 +1,28 @@
# HTTP Request Flow # HTTP Request Flow
**Document Version:** 1.0 **Document Version:** 1.0
**Last Updated:** 2026-01-10 **Last Updated:** 2026-04-04
**Status:** Production LIVE **Status:** Production LIVE (OVH VPS)
**Flow Type:** HTTP Request Handling & Response Cycle **Flow Type:** HTTP Request Handling & Response Cycle
--- ---
## Overview ## Overview
This document describes the **complete HTTP request flow** for the Norda Biznes Partner application, from external user through reverse proxy to Flask application and back. It covers: This document describes the **complete HTTP request flow** for the Norda Biznes Partner application, from external user through nginx reverse proxy to Flask application and back. It covers:
- **Complete request path** (Internet → Fortigate → NPM → Flask → Response) - **Complete request path** (Internet → Nginx → Flask → Response)
- **Critical port configurations** and routing decisions
- **SSL/TLS termination** and security boundaries - **SSL/TLS termination** and security boundaries
- **Request/response transformation** at each layer - **Request/response transformation** at each layer
- **Common failure scenarios** and troubleshooting - **Common failure scenarios** and troubleshooting
**Key Infrastructure:** **Key Infrastructure:**
- **Public Entry:** 85.237.177.83:443 (Fortigate NAT) - **Public Entry:** 57.128.200.27:443 (OVH VPS, direct)
- **Reverse Proxy:** NPM on 10.22.68.250:443 (SSL termination) - **Reverse Proxy:** Nginx on 57.128.200.27:443 (SSL termination)
- **Backend Application:** Flask/Gunicorn on 57.128.200.27:5000 - **Backend Application:** Flask/Gunicorn on 127.0.0.1:5000
- **Protocol Flow:** HTTPS → NPM → HTTP → Flask → HTTP → NPM → HTTPS - **Protocol Flow:** HTTPS → Nginx → HTTP (localhost) → Flask → HTTP → Nginx → HTTPS
**⚠️ CRITICAL CONFIGURATION:** > **Note:** The old on-prem setup used FortiGate NAT (85.237.177.83) and NPM (10.22.68.250) as intermediate layers. Production now runs directly on OVH VPS without FortiGate or NPM.
```
NPM MUST forward to port 5000, NOT port 80!
Port 80 on NORDABIZ-01 runs nginx that redirects to HTTPS
Forwarding to port 80 causes infinite redirect loop
```
**Related Documentation:** **Related Documentation:**
- Incident Report: `docs/INCIDENT_REPORT_20260102.md` (ERR_TOO_MANY_REDIRECTS) - Incident Report: `docs/INCIDENT_REPORT_20260102.md` (ERR_TOO_MANY_REDIRECTS)
@ -45,24 +39,19 @@ Forwarding to port 80 causes infinite redirect loop
sequenceDiagram sequenceDiagram
actor User actor User
participant Browser participant Browser
participant Fortigate as 🛡️ Fortigate Firewall<br/>85.237.177.83 participant Nginx as 🔒 Nginx Reverse Proxy<br/>57.128.200.27:443
participant NPM as 🔒 NPM Reverse Proxy<br/>10.22.68.250:443 participant Flask as 🌐 Flask/Gunicorn<br/>127.0.0.1:5000
participant Flask as 🌐 Flask/Gunicorn<br/>57.128.200.27:5000
participant DB as 💾 PostgreSQL<br/>localhost:5432 participant DB as 💾 PostgreSQL<br/>localhost:5432
Note over User,DB: SUCCESSFUL REQUEST FLOW Note over User,DB: SUCCESSFUL REQUEST FLOW
User->>Browser: Navigate to https://nordabiznes.pl/ User->>Browser: Navigate to https://nordabiznes.pl/
Browser->>Fortigate: HTTPS GET / (Port 443) Browser->>Nginx: HTTPS GET / (Port 443)
Note over Fortigate: NAT Translation<br/>85.237.177.83:443 → 10.22.68.250:443 Note over Nginx: SSL/TLS Termination<br/>Let's Encrypt Certificate (certbot)
Fortigate->>NPM: HTTPS GET / (Port 443) Note over Nginx: Request Processing<br/>• Validate certificate<br/>• Decrypt HTTPS<br/>• Extract headers<br/>• proxy_pass to localhost:5000
Note over NPM: SSL/TLS Termination<br/>Let's Encrypt Certificate<br/>nordabiznes.pl (Cert ID: 27)
Note over NPM: Request Processing<br/>• Validate certificate<br/>• Decrypt HTTPS<br/>• Extract headers<br/>• Check routing rules Nginx->>Flask: HTTP GET / (127.0.0.1:5000)
NPM->>Flask: HTTP GET / (Port 5000) ✓
Note over NPM,Flask: ⚠️ CRITICAL: Port 5000<br/>NOT port 80!
Note over Flask: Flask Request Handling<br/>• WSGI via Gunicorn<br/>• Route matching (app.py)<br/>• Session validation<br/>• CSRF check (if POST) Note over Flask: Flask Request Handling<br/>• WSGI via Gunicorn<br/>• Route matching (app.py)<br/>• Session validation<br/>• CSRF check (if POST)
@ -71,26 +60,27 @@ sequenceDiagram
Note over Flask: Template Rendering<br/>• Jinja2 template: index.html<br/>• Inject company data<br/>• Apply filters & sorting Note over Flask: Template Rendering<br/>• Jinja2 template: index.html<br/>• Inject company data<br/>• Apply filters & sorting
Flask->>NPM: HTTP 200 OK<br/>Content-Type: text/html<br/>Set-Cookie: session=...<br/>HTML content Flask->>Nginx: HTTP 200 OK<br/>Content-Type: text/html<br/>Set-Cookie: session=...<br/>HTML content
Note over NPM: Response Processing<br/>• Encrypt response (HTTPS)<br/>• Add security headers<br/>• HSTS, CSP, X-Frame-Options Note over Nginx: Response Processing<br/>• Encrypt response (HTTPS)<br/>• Add security headers<br/>• HSTS, CSP, X-Frame-Options
NPM->>Fortigate: HTTPS 200 OK (Port 443) Nginx->>Browser: HTTPS 200 OK
Fortigate->>Browser: HTTPS 200 OK
Browser->>User: Display page Browser->>User: Display page
``` ```
### 1.2 Failed Request (Wrong Port Configuration) ### 1.2 Historical: Failed Request (Old NPM Setup - Port Misconfiguration)
> **Note:** This failure scenario applied to the old on-prem setup with NPM. It is no longer applicable to the current OVH VPS production setup.
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
actor User actor User
participant Browser participant Browser
participant NPM as 🔒 NPM Reverse Proxy<br/>10.22.68.250:443 participant NPM as 🔒 NPM Reverse Proxy<br/>10.22.68.250:443
participant NginxSys as ⚠️ Nginx System<br/>57.128.200.27:80 participant NginxSys as ⚠️ Nginx System<br/>10.22.68.249:80
participant Flask as 🌐 Flask/Gunicorn<br/>57.128.200.27:5000 participant Flask as 🌐 Flask/Gunicorn<br/>10.22.68.249:5000
Note over User,Flask: FAILED REQUEST FLOW (REDIRECT LOOP) Note over User,Flask: FAILED REQUEST FLOW (REDIRECT LOOP) — HISTORICAL
User->>Browser: Navigate to https://nordabiznes.pl/ User->>Browser: Navigate to https://nordabiznes.pl/
Browser->>NPM: HTTPS GET / (Port 443) Browser->>NPM: HTTPS GET / (Port 443)
@ -120,11 +110,11 @@ sequenceDiagram
## 2. Layer-by-Layer Request Processing ## 2. Layer-by-Layer Request Processing
### 2.1 Layer 1: Fortigate Firewall (NAT Gateway) ### 2.1 Layer 1: DNS + Direct Connection (OVH VPS)
**Server:** Fortigate Firewall **Server:** OVH VPS
**Public IP:** 85.237.177.83 **Public IP:** 57.128.200.27
**Function:** Network Address Translation (NAT) + Firewall **Function:** Direct internet-facing server (no NAT, no FortiGate for production)
**Processing Steps:** **Processing Steps:**
@ -160,12 +150,12 @@ Firewall: ALLOW from any to 85.237.177.83:443 (state: NEW,ESTABLISHED)
--- ---
### 2.2 Layer 2: NPM Reverse Proxy (SSL Termination) ### 2.2 Layer 2: Nginx Reverse Proxy (SSL Termination)
**Server:** R11-REVPROXY-01 (VM 119) **Server:** OVH VPS (inpi-vps-waw01)
**IP:** 10.22.68.250 **IP:** 57.128.200.27
**Port:** 443 (HTTPS) **Port:** 443 (HTTPS)
**Technology:** Nginx Proxy Manager (Docker) **Technology:** Nginx with Let's Encrypt (certbot)
**Processing Steps:** **Processing Steps:**
@ -230,7 +220,7 @@ Firewall: ALLOW from any to 85.237.177.83:443 (state: NEW,ESTABLISHED)
|-----------|-------|-------| |-----------|-------|-------|
| Domain Names | nordabiznes.pl, www.nordabiznes.pl | Primary + www alias | | Domain Names | nordabiznes.pl, www.nordabiznes.pl | Primary + www alias |
| Forward Scheme | http | NPM→Backend uses HTTP (secure internal network) | | Forward Scheme | http | NPM→Backend uses HTTP (secure internal network) |
| Forward Host | 57.128.200.27 | NORDABIZ-01 backend server | | Forward Host | 127.0.0.1 | Localhost (same OVH VPS) |
| **Forward Port** | **5000** | **Flask/Gunicorn port (CRITICAL!)** | | **Forward Port** | **5000** | **Flask/Gunicorn port (CRITICAL!)** |
| SSL Certificate | 27 (Let's Encrypt) | Auto-renewal enabled | | SSL Certificate | 27 (Let's Encrypt) | Auto-renewal enabled |
| SSL Forced | Yes | Redirect HTTP→HTTPS | | SSL Forced | Yes | Redirect HTTP→HTTPS |
@ -254,8 +244,8 @@ ssh maciejpi@10.22.68.250 "docker exec nginx-proxy-manager_app_1 \
### 2.3 Layer 3: Flask/Gunicorn Application (Request Processing) ### 2.3 Layer 3: Flask/Gunicorn Application (Request Processing)
**Server:** NORDABIZ-01 (VM 249) **Server:** OVH VPS (inpi-vps-waw01)
**IP:** 57.128.200.27 **IP:** 127.0.0.1 (localhost, via nginx proxy_pass)
**Port:** 5000 **Port:** 5000
**Technology:** Gunicorn 20.1.0 + Flask 3.0 **Technology:** Gunicorn 20.1.0 + Flask 3.0
@ -263,7 +253,7 @@ ssh maciejpi@10.22.68.250 "docker exec nginx-proxy-manager_app_1 \
1. **Gunicorn Receives HTTP Request:** 1. **Gunicorn Receives HTTP Request:**
``` ```
Binding: 0.0.0.0:5000 (all interfaces) Binding: 127.0.0.1:5000 (all interfaces)
Workers: 4 (Gunicorn worker processes) Workers: 4 (Gunicorn worker processes)
Worker Class: sync (synchronous workers) Worker Class: sync (synchronous workers)
Timeout: 120 seconds Timeout: 120 seconds
@ -382,11 +372,11 @@ ssh maciejpi@10.22.68.250 "docker exec nginx-proxy-manager_app_1 \
```ini ```ini
# /etc/systemd/system/nordabiznes.service # /etc/systemd/system/nordabiznes.service
[Service] [Service]
User=www-data User=maciejpi
Group=www-data Group=maciejpi
WorkingDirectory=/var/www/nordabiznes WorkingDirectory=/var/www/nordabiznes
ExecStart=/var/www/nordabiznes/venv/bin/gunicorn \ ExecStart=/var/www/nordabiznes/venv/bin/gunicorn \
--bind 0.0.0.0:5000 \ --bind 127.0.0.1:5000 \
--workers 4 \ --workers 4 \
--timeout 120 \ --timeout 120 \
--access-logfile /var/log/nordabiznes/access.log \ --access-logfile /var/log/nordabiznes/access.log \
@ -410,7 +400,7 @@ ssh maciejpi@57.128.200.27 "ps aux | grep gunicorn"
### 2.4 Layer 4: PostgreSQL Database (Data Retrieval) ### 2.4 Layer 4: PostgreSQL Database (Data Retrieval)
**Server:** NORDABIZ-01 (same server as Flask) **Server:** OVH VPS (same server as Flask)
**IP:** 127.0.0.1 (localhost only) **IP:** 127.0.0.1 (localhost only)
**Port:** 5432 **Port:** 5432
**Technology:** PostgreSQL 14 **Technology:** PostgreSQL 14
@ -490,7 +480,7 @@ sequenceDiagram
Note over Flask: Response Construction Note over Flask: Response Construction
Flask->>Flask: Render Jinja2 template<br/>HTML content (50 KB) Flask->>Flask: Render Jinja2 template<br/>HTML content (50 KB)
Flask->>NPM: HTTP/1.1 200 OK<br/>Content-Type: text/html; charset=utf-8<br/>Content-Length: 51234<br/>Set-Cookie: session=...<br/><br/>[HTML content] Flask->>Nginx: HTTP/1.1 200 OK<br/>Content-Type: text/html; charset=utf-8<br/>Content-Length: 51234<br/>Set-Cookie: session=...<br/><br/>[HTML content]
Note over NPM: Response Processing Note over NPM: Response Processing
NPM->>NPM: Add security headers:<br/>• Strict-Transport-Security<br/>• X-Frame-Options: SAMEORIGIN<br/>• X-Content-Type-Options: nosniff<br/>• Referrer-Policy: strict-origin NPM->>NPM: Add security headers:<br/>• Strict-Transport-Security<br/>• X-Frame-Options: SAMEORIGIN<br/>• X-Content-Type-Options: nosniff<br/>• Referrer-Policy: strict-origin
@ -499,7 +489,7 @@ sequenceDiagram
NPM->>NPM: Encrypt response (TLS 1.3)<br/>Certificate: Let's Encrypt NPM->>NPM: Encrypt response (TLS 1.3)<br/>Certificate: Let's Encrypt
NPM->>Fortigate: HTTPS 200 OK<br/>Encrypted response<br/>Size: 12 KB (gzip) Nginx->>Browser: HTTPS 200 OK<br/>Encrypted response<br/>Size: 12 KB (gzip)
Note over Fortigate: NAT reverse translation Note over Fortigate: NAT reverse translation
Fortigate->>Browser: HTTPS 200 OK<br/>Source: 85.237.177.83 Fortigate->>Browser: HTTPS 200 OK<br/>Source: 85.237.177.83
@ -579,27 +569,27 @@ x-request-id: abc123def456
│ HTTP (Port 5000) ✓ │ HTTP (Port 5000) ✓
┌─────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────┐
│ FLASK/GUNICORN (NORDABIZ-01) │ │ FLASK/GUNICORN (OVH VPS) │
│ IP: 57.128.200.27:5000 │ │ IP: 57.128.200.27:5000 │
│ Binding: 0.0.0.0:5000 │ │ Binding: 127.0.0.1:5000 │
│ Workers: 4 (Gunicorn) │ │ Workers: 4 (Gunicorn) │
│ Function: Application logic, template rendering │ │ Function: Application logic, template rendering │
└────────────────────────────┬────────────────────────────────────┘ └────────────────────────────┬────────────────────────────────────┘
│ SQL (localhost:5432) │ SQL (localhost:5432)
┌─────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────┐
│ POSTGRESQL (NORDABIZ-01) │ │ POSTGRESQL (OVH VPS) │
│ IP: 127.0.0.1:5432 (localhost only) │ │ IP: 127.0.0.1:5432 (localhost only) │
│ Database: nordabiz │ │ Database: nordabiz │
│ Function: Data storage │ │ Function: Data storage │
└─────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────┘
``` ```
### 4.2 Port Table (NORDABIZ-01) ### 4.2 Port Table (OVH VPS)
| Port | Service | Binding | User | Purpose | NPM Should Use? | | Port | Service | Binding | User | Purpose | NPM Should Use? |
|------|---------|---------|------|---------|-----------------| |------|---------|---------|------|---------|-----------------|
| **5000** | **Gunicorn/Flask** | **0.0.0.0** | **www-data** | **Main Application** | **✓ YES** | | **5000** | **Gunicorn/Flask** | **0.0.0.0** | **maciejpi** | **Main Application** | **✓ YES** |
| 80 | Nginx (system) | 0.0.0.0 | root | HTTP→HTTPS redirect | ❌ NO (causes loop!) | | 80 | Nginx (system) | 0.0.0.0 | root | HTTP→HTTPS redirect | ❌ NO (causes loop!) |
| 443 | Nginx (system) | 0.0.0.0 | root | HTTPS redirect | ❌ NO (NPM handles SSL) | | 443 | Nginx (system) | 0.0.0.0 | root | HTTPS redirect | ❌ NO (NPM handles SSL) |
| 5432 | PostgreSQL | 127.0.0.1 | postgres | Database | ❌ NO (localhost only) | | 5432 | PostgreSQL | 127.0.0.1 | postgres | Database | ❌ NO (localhost only) |
@ -607,7 +597,7 @@ x-request-id: abc123def456
**⚠️ CRITICAL WARNING:** **⚠️ CRITICAL WARNING:**
``` ```
Port 80 and 443 on NORDABIZ-01 run a system nginx that: Port 80 and 443 on OVH VPS run a system nginx that:
1. Redirects ALL HTTP requests to HTTPS 1. Redirects ALL HTTP requests to HTTPS
2. Redirects ALL HTTPS requests to https://nordabiznes.pl 2. Redirects ALL HTTPS requests to https://nordabiznes.pl
@ -629,7 +619,7 @@ SOLUTION: NPM must ALWAYS forward to port 5000!
**Flow:** **Flow:**
``` ```
User → NPM → Flask → Static file handler → Return CSS User → Nginx → Flask → Static file handler → Return CSS
``` ```
**Flask Handling:** **Flask Handling:**
@ -650,7 +640,7 @@ def static_files(filename):
**Flow:** **Flow:**
``` ```
User → NPM → Flask → API route → Database → JSON response User → Nginx → Flask → API route → Database → JSON response
``` ```
**Response:** **Response:**
@ -682,7 +672,7 @@ Access-Control-Allow-Origin: * (if CORS enabled)
**Flow:** **Flow:**
``` ```
User → NPM → Flask → CSRF validation → Auth check → Database → Redirect User → Nginx → Flask → CSRF validation → Auth check → Database → Redirect
``` ```
**Additional Processing:** **Additional Processing:**
@ -707,7 +697,7 @@ Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax
**Flow:** **Flow:**
``` ```
Monitor → NPM → Flask → Simple response (no DB query) Monitor → Nginx → Flask → Simple response (no DB query)
``` ```
**Response:** **Response:**
@ -802,7 +792,7 @@ graph TB
|-------|-----------------|-------| |-------|-----------------|-------|
| Fortigate NAT | < 1 ms | Hardware NAT, negligible latency | | Fortigate NAT | < 1 ms | Hardware NAT, negligible latency |
| NPM SSL Termination | 10-20 ms | TLS handshake + decryption | | NPM SSL Termination | 10-20 ms | TLS handshake + decryption |
| NPM → Flask Network | < 1 ms | Internal 10 Gbps network | | Nginx → Flask Network | < 1 ms | Internal 10 Gbps network |
| Flask Request Handling | 50-150 ms | Route matching, template rendering | | Flask Request Handling | 50-150 ms | Route matching, template rendering |
| Database Query | 10-30 ms | Indexed queries, connection pool | | Database Query | 10-30 ms | Indexed queries, connection pool |
| Template Rendering | 20-50 ms | Jinja2 template compilation | | Template Rendering | 20-50 ms | Jinja2 template compilation |
@ -898,7 +888,7 @@ ssh maciejpi@57.128.200.27 "sudo systemctl status nordabiznes"
# 2. Check if port 5000 is listening # 2. Check if port 5000 is listening
ssh maciejpi@57.128.200.27 "sudo netstat -tlnp | grep 5000" ssh maciejpi@57.128.200.27 "sudo netstat -tlnp | grep 5000"
# Expected: 0.0.0.0:5000 ... gunicorn # Expected: 127.0.0.1:5000 ... gunicorn
# 3. Test direct connection # 3. Test direct connection
curl -I http://57.128.200.27:5000/health curl -I http://57.128.200.27:5000/health
@ -1026,7 +1016,7 @@ ssh maciejpi@57.128.200.27 "sudo -u postgres psql -c \
**Network Connectivity:** **Network Connectivity:**
```bash ```bash
# Test NPM → Flask connectivity # Test Nginx → Flask connectivity
ssh maciejpi@10.22.68.250 "curl -I http://57.128.200.27:5000/health" ssh maciejpi@10.22.68.250 "curl -I http://57.128.200.27:5000/health"
# Test Flask → Database connectivity # Test Flask → Database connectivity
@ -1103,12 +1093,12 @@ After=network.target postgresql.service
[Service] [Service]
Type=notify Type=notify
User=www-data User=maciejpi
Group=www-data Group=maciejpi
WorkingDirectory=/var/www/nordabiznes WorkingDirectory=/var/www/nordabiznes
Environment="PATH=/var/www/nordabiznes/venv/bin" Environment="PATH=/var/www/nordabiznes/venv/bin"
ExecStart=/var/www/nordabiznes/venv/bin/gunicorn \ ExecStart=/var/www/nordabiznes/venv/bin/gunicorn \
--bind 0.0.0.0:5000 \ --bind 127.0.0.1:5000 \
--workers 4 \ --workers 4 \
--worker-class sync \ --worker-class sync \
--timeout 120 \ --timeout 120 \
@ -1127,7 +1117,7 @@ WantedBy=multi-user.target
``` ```
**Parameters Explained:** **Parameters Explained:**
- `--bind 0.0.0.0:5000` - Listen on all interfaces, port 5000 - `--bind 127.0.0.1:5000` - Listen on all interfaces, port 5000
- `--workers 4` - 4 worker processes (matches CPU cores) - `--workers 4` - 4 worker processes (matches CPU cores)
- `--worker-class sync` - Synchronous workers (default) - `--worker-class sync` - Synchronous workers (default)
- `--timeout 120` - 120 second request timeout - `--timeout 120` - 120 second request timeout
@ -1221,7 +1211,7 @@ ssh maciejpi@57.128.200.27 "sudo tail -f /var/log/postgresql/postgresql-14-main.
- Database size growth - Database size growth
- Table bloat - Table bloat
**System Metrics (NORDABIZ-01):** **System Metrics (OVH VPS):**
- CPU usage (should be < 80%) - CPU usage (should be < 80%)
- Memory usage (should be < 6 GB / 8 GB) - Memory usage (should be < 6 GB / 8 GB)
- Disk I/O (should be low) - Disk I/O (should be low)