From a7392de9f6d70375ec1a4f1a3830976945553a0e Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Wed, 4 Mar 2026 22:31:07 +0100 Subject: [PATCH] fix(security): close exposed PostgreSQL and Redis ports (BSI/CERT-Bund report) Docker bypasses UFW iptables, so bare port mappings like "5432:5432" exposed the database to the public internet. Removed port mappings for PostgreSQL and Redis (they only need Docker-internal networking), and bound the API port to 127.0.0.1 since only Caddy needs to reach it. Co-Authored-By: Claude Opus 4.6 --- docker-compose.yml | 6 +--- docs/deployment/hetzner-server-setup.md | 40 +++++++++++++++++++++---- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index d0d4b89c..e64a3d0f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,8 +9,6 @@ services: POSTGRES_PASSWORD: secure_password volumes: - postgres_data:/var/lib/postgresql/data - ports: - - "5432:5432" mem_limit: 256m healthcheck: test: ["CMD-SHELL", "pg_isready -U orion_user -d orion_db"] @@ -24,8 +22,6 @@ services: image: redis:7-alpine restart: always command: redis-server --maxmemory 100mb --maxmemory-policy allkeys-lru - ports: - - "6380:6379" # Use 6380 to avoid conflict with host Redis mem_limit: 128m healthcheck: test: ["CMD", "redis-cli", "ping"] @@ -41,7 +37,7 @@ services: profiles: - full # Only start with: docker compose --profile full up -d ports: - - "8001:8000" # Use 8001 to avoid conflict with local dev server + - "127.0.0.1:8001:8000" # Localhost only — Caddy reverse proxies to this env_file: .env environment: DATABASE_URL: postgresql://orion_user:secure_password@db:5432/orion_db diff --git a/docs/deployment/hetzner-server-setup.md b/docs/deployment/hetzner-server-setup.md index 889cde23..4d04d022 100644 --- a/docs/deployment/hetzner-server-setup.md +++ b/docs/deployment/hetzner-server-setup.md @@ -1706,6 +1706,34 @@ curl -s http://localhost:9090/api/v1/rules | python3 -m json.tool | grep -i redi Docker network segmentation, fail2ban configuration, and automatic security updates. +### 20.0 Docker Port Binding (Critical — Docker Bypasses UFW) + +!!! danger "Docker bypasses UFW firewall rules" + Docker manipulates iptables directly, bypassing UFW entirely. A port mapping like `"5432:5432"` exposes PostgreSQL to the **public internet** even if UFW only allows ports 22, 80, and 443. This was flagged by the German Federal Office for Information Security (BSI/CERT-Bund) in March 2026. + +**Rules for port mappings in `docker-compose.yml`:** + +1. **No port mapping** for services that only talk to other containers (PostgreSQL, Redis) — they communicate via Docker's internal network using service names (`db:5432`, `redis:6379`) +2. **Bind to `127.0.0.1`** for services that need host access but not internet access (API via Caddy, Flower, Prometheus, Grafana, etc.) +3. **Never use bare port mappings** like `"5432:5432"` or `"6380:6379"` — these bind to `0.0.0.0` (all interfaces) + +| Service | Correct | Wrong | +|---|---|---| +| PostgreSQL | *(no ports section)* | `"5432:5432"` | +| Redis | *(no ports section)* | `"6380:6379"` | +| API | `"127.0.0.1:8001:8000"` | `"8001:8000"` | +| Flower | `"127.0.0.1:5555:5555"` | `"5555:5555"` | + +**After deploying, verify no services are exposed:** + +```bash +# Should return nothing for 5432 and 6379 +sudo ss -tlnp | grep -E '0.0.0.0:(5432|6379|6380)' + +# Should show 127.0.0.1 only for app services +sudo ss -tlnp | grep -E '(8001|5555|9090|3001)' +``` + ### 20.1 Docker Network Segmentation Three isolated networks replace the default flat network: @@ -2340,12 +2368,12 @@ After Google Wallet is verified working: | Service | Internal Port | External Port | Domain (via Caddy) | |---|---|---|---| -| Orion API | 8000 | 8001 | `api.wizard.lu` | -| Main Platform | 8000 | 8001 | `wizard.lu` | -| OMS Platform | 8000 | 8001 | `omsflow.lu` | -| Loyalty+ Platform | 8000 | 8001 | `rewardflow.lu` | -| PostgreSQL | 5432 | 5432 | (internal only) | -| Redis | 6379 | 6380 | (internal only) | +| Orion API | 8000 | 127.0.0.1:8001 | `api.wizard.lu` | +| Main Platform | 8000 | 127.0.0.1:8001 | `wizard.lu` | +| OMS Platform | 8000 | 127.0.0.1:8001 | `omsflow.lu` | +| Loyalty+ Platform | 8000 | 127.0.0.1:8001 | `rewardflow.lu` | +| PostgreSQL | 5432 | none (Docker internal) | (internal only) | +| Redis | 6379 | none (Docker internal) | (internal only) | | Flower | 5555 | 5555 | `flower.wizard.lu` | | Gitea | 3000 | 3000 | `git.wizard.lu` | | Prometheus | 9090 | 9090 (localhost) | (internal only) |