migrate
This commit is contained in:
@@ -0,0 +1,668 @@
|
||||
---
|
||||
tags:
|
||||
- Documentation
|
||||
- Bookstack
|
||||
- Notes
|
||||
---
|
||||
# Web Application Firewall (WAF)
|
||||
|
||||
# SOCFortess
|
||||
|
||||
# SOCFortress Docs
|
||||
|
||||
# WAF Management Platform
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk-">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#waf-management-platform)</div>Self-hosted Web Application Firewall with a modern admin UI. Powered by **Caddy + Coraza** (OWASP Core Rule Set v4) as the WAF engine, a **FastAPI** management API, and a **React** dashboard.
|
||||
|
||||
This repository contains everything you need to **run** the platform from prebuilt container images — no source build required.
|
||||
|
||||
## 📺 Watch the demo
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--2">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#-watch-the-demo)</div>Deploy the WAF, get automatic TLS, block live attacks, and tune a false positive — in about 10 minutes:
|
||||
|
||||
[](https://youtu.be/ZvjfPXP3X30)
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--6">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#architecture)</div>```
|
||||
Internet
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐ ┌──────────────────────────┐
|
||||
│ Caddy + Coraza │ ───► │ Your protected upstream │
|
||||
│ WAF engine (80/443) │ │ app(s) │
|
||||
└─────────────────────┘ └──────────────────────────┘
|
||||
▲ (Admin API 2019, container-internal only)
|
||||
│
|
||||
┌─────────────────────┐ ┌────────────┐ ┌─────────┐
|
||||
│ FastAPI Admin API │ ◄─►│ PostgreSQL │ │ Redis │
|
||||
│ (8000, internal) │ └────────────┘ └─────────┘
|
||||
└─────────────────────┘
|
||||
▲
|
||||
│
|
||||
┌─────────────────────┐
|
||||
│ React Admin UI │ ← you log in here: https://localhost:8443
|
||||
│ (Nginx, 8443 HTTPS) │
|
||||
└─────────────────────┘
|
||||
|
||||
```
|
||||
|
||||
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" id="bkmrk--8"><div class="zeroclipboard-container"><svg aria-hidden="true" class="octicon octicon-copy js-clipboard-copy-icon" data-component="Octicon" data-view-component="true" height="16" version="1.1" viewbox="0 0 16 16" width="16"></svg></div></div>---
|
||||
|
||||
## Features
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--10">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#features)</div>- **Caddy + Coraza WAF** with the OWASP Core Rule Set v4, in detection or blocking mode per site.
|
||||
- **Site management** — front any number of upstream apps behind the WAF.
|
||||
- **CRS & custom rules** — tune the ruleset, add custom rules, manage false-positive exclusions from the UI.
|
||||
- **Authentication** with TOTP 2FA and role-based access control.
|
||||
- **Log viewer** — searchable, PostgreSQL-backed request/blocking logs with GeoIP enrichment.
|
||||
- **Alerting** — email notifications on configurable conditions.
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--13">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#quick-start)</div>### Prerequisites
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--15">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#prerequisites)</div>- Docker ≥ 24 and Docker Compose ≥ 2.20
|
||||
- 2 GB RAM minimum
|
||||
- Ports **80**, **443**, and **8443** available on the host
|
||||
- A free **MaxMind GeoLite2** license key (see [GeoIP setup](https://github.com/socfortress/waf-platform-public/blob/main/README.md#geoip-setup))
|
||||
|
||||
### 1. Get this repo
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--17">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#1-get-this-repo)</div>```
|
||||
git clone https://github.com/socfortress/waf-platform-public.git waf-platform
|
||||
cd waf-platform
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
<div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" id="bkmrk--19"><div class="zeroclipboard-container"><svg aria-hidden="true" class="octicon octicon-copy js-clipboard-copy-icon" data-component="Octicon" data-view-component="true" height="16" version="1.1" viewbox="0 0 16 16" width="16"></svg></div></div>### 2. Configure `.env`
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--20">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#2-configure-env)</div>Edit `.env` and replace every `CHANGE_ME` value:
|
||||
|
||||
<table id="bkmrk-variable-purpose-how"><thead><tr><th>Variable</th><th>Purpose</th><th>How to generate</th></tr></thead><tbody><tr><td>`POSTGRES_PASSWORD`</td><td>Database password</td><td>`openssl rand -hex 32`</td></tr><tr><td>`SECRET_KEY`</td><td>JWT signing key</td><td>`python3 -c "import secrets; print(secrets.token_hex(32))"`</td></tr><tr><td>`TOTP_ENCRYPTION_KEY`</td><td>TOTP secret encryption</td><td>`python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"`</td></tr><tr><td>`BOOTSTRAP_ADMIN_EMAIL`</td><td>First superadmin email</td><td>Any valid email</td></tr><tr><td>`BOOTSTRAP_ADMIN_PASSWORD`</td><td>First superadmin password</td><td>Strong password — **change after first login**</td></tr><tr><td>`ALLOWED_ORIGINS`</td><td>CORS origins</td><td>`https://localhost:8443` for the default local deploy</td></tr></tbody></table>
|
||||
|
||||
Optional — email alerts: `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASSWORD`, `SMTP_FROM`.
|
||||
|
||||
Optional — pin a version: set `WAF_IMAGE_TAG` to a release tag (e.g. `v1.0.0`) instead of `latest`.
|
||||
|
||||
### 3. GeoIP setup
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--22">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#3-geoip-setup)</div>The log viewer enriches client IPs with country/city data using MaxMind's GeoLite2 database. MaxMind's license does not allow us to redistribute it, so you supply your own (it's free):
|
||||
|
||||
1. Create a free account at [https://www.maxmind.com/en/geolite2/signup](https://www.maxmind.com/en/geolite2/signup).
|
||||
2. Download **GeoLite2 City** (`.mmdb` format).
|
||||
3. Place the file in this directory as `GeoLite2-City.mmdb`, **or** set `GEOIP_DB_PATH` in `.env` to its full path.
|
||||
|
||||
> The stack will start without it, but GeoIP enrichment in logs will be disabled.
|
||||
|
||||
### 4. Start the stack
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--24">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#4-start-the-stack)</div>```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
<div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" id="bkmrk--26"><div class="zeroclipboard-container"><svg aria-hidden="true" class="octicon octicon-copy js-clipboard-copy-icon" data-component="Octicon" data-view-component="true" height="16" version="1.1" viewbox="0 0 16 16" width="16"></svg></div></div>Wait until all containers report healthy (typically 30–60 seconds):
|
||||
|
||||
```
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
<div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" id="bkmrk--27"><div class="zeroclipboard-container"><svg aria-hidden="true" class="octicon octicon-copy js-clipboard-copy-icon" data-component="Octicon" data-view-component="true" height="16" version="1.1" viewbox="0 0 16 16" width="16"></svg></div></div>### 5. First login
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--28">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#5-first-login)</div>1. Open **[https://localhost:8443](https://localhost:8443/)** (the UI uses a self-signed certificate on first boot — accept the browser warning, or upload your own cert in the UI).
|
||||
2. Log in with the `BOOTSTRAP_ADMIN_EMAIL` / `BOOTSTRAP_ADMIN_PASSWORD` from `.env`.
|
||||
3. **Change your password immediately** via Users → Edit.
|
||||
4. Optionally enroll TOTP under Settings → Security.
|
||||
|
||||
---
|
||||
|
||||
## Protecting your app
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--31">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#protecting-your-app)</div>A fresh install has **no sites configured** — Caddy listens on 80/443 but does not proxy anything yet (a bare request to `http://localhost/` returns an empty `200`). You configure protection from the admin UI:
|
||||
|
||||
1. Log in (see above) and add your upstream application as a **site**.
|
||||
2. Choose **detection** mode (log only) or **blocking** mode (reject attacks).
|
||||
3. The WAF then proxies and protects that site.
|
||||
|
||||
### Optional: verify against the bundled test upstream
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--33">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#optional-verify-against-the-bundled-test-upstream)</div>The stack includes a throwaway `http-echo` container you can use as a target. Add a test site in **blocking** mode pointing at `http-echo:5678`, then:
|
||||
|
||||
```
|
||||
curl http://localhost/ # → upstream-ok
|
||||
curl -s -o /dev/null -w "%{http_code}\n" "http://localhost/?id=1+OR+1%3D1" # → 403 (SQLi blocked)
|
||||
```
|
||||
|
||||
<div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" id="bkmrk--35"><div class="zeroclipboard-container"><svg aria-hidden="true" class="octicon octicon-copy js-clipboard-copy-icon" data-component="Octicon" data-view-component="true" height="16" version="1.1" viewbox="0 0 16 16" width="16"></svg></div></div>A benign request returns `200`; the SQLi probe returns `403` once the site is in blocking mode.
|
||||
|
||||
---
|
||||
|
||||
## Updating
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--37">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#updating)</div>```
|
||||
docker compose pull # fetch the latest images (or your pinned WAF_IMAGE_TAG)
|
||||
docker compose up -d # recreate changed containers
|
||||
```
|
||||
|
||||
<div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" id="bkmrk--39"><div class="zeroclipboard-container"><svg aria-hidden="true" class="octicon octicon-copy js-clipboard-copy-icon" data-component="Octicon" data-view-component="true" height="16" version="1.1" viewbox="0 0 16 16" width="16"></svg></div></div>Your data (Postgres, Redis, rules, certs) lives in named Docker volumes and survives updates.
|
||||
|
||||
---
|
||||
|
||||
## Managing the stack
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--41">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#managing-the-stack)</div>```
|
||||
docker compose ps # status
|
||||
docker compose logs -f # follow logs
|
||||
docker compose down # stop (volumes preserved)
|
||||
docker compose down -v # stop AND delete all data volumes — destructive
|
||||
```
|
||||
|
||||
<div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" id="bkmrk--43"><div class="zeroclipboard-container"><svg aria-hidden="true" class="octicon octicon-copy js-clipboard-copy-icon" data-component="Octicon" data-view-component="true" height="16" version="1.1" viewbox="0 0 16 16" width="16"></svg></div></div>---
|
||||
|
||||
## Security notes
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--45">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#security-notes)</div>- Passwords are bcrypt-hashed; TOTP secrets are encrypted at rest.
|
||||
- Secrets are read from environment variables only — never hardcoded. Keep `.env` private.
|
||||
- The Caddy Admin API (port 2019) is bound inside the Docker network only; it is not exposed to the host.
|
||||
- CORS is restricted to `ALLOWED_ORIGINS` — no wildcard.
|
||||
- Auth endpoints are rate-limited; refresh tokens rotate on every use.
|
||||
- For production, place the UI behind your own TLS termination / trusted certificate rather than the self-signed default.
|
||||
|
||||
---
|
||||
|
||||
## Support & licensing
|
||||
|
||||
<div class="markdown-heading" dir="auto" id="bkmrk--48">[<svg aria-hidden="true" class="octicon octicon-link" data-component="Octicon" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg>](https://github.com/socfortress/waf-platform-public/blob/main/README.md#support--licensing)</div>- **License:** see [LICENSE](https://github.com/socfortress/waf-platform-public/blob/main/LICENSE).
|
||||
- **Issues / questions:** open an issue on this repository.
|
||||
- Commercial support is available from [SOCFortress](https://www.socfortress.co/).
|
||||
|
||||
# EDGE WAF - SOCFortress and NPM
|
||||
|
||||
I have installed SOCFortress WAF to test and compare to my current proxy which is Nginx Proxy Manager. The one area of concern that I have and cannot locate in SOCFortress is the ability to add Custom Configuration for authentik for headers. In Nginx Proxy manager, I can add this information in Custom Nginx Configuration section. Does SOCFortress allow this and if so where?
|
||||
|
||||
The short answer is **no, not directly in the UI** the way you are used to with Nginx Proxy Manager (NPM).
|
||||
|
||||
The primary reason for this is a difference in the underlying architecture: **SOCFortress WAF does not use Nginx.** <span class="citation-9">Instead, the SOCFortress WAF platform is built on top of </span>**<span class="citation-9">Caddy</span>**<span class="citation-9"> as its reverse proxy engine, combined with </span>**<span class="citation-9">Coraza</span>**<span class="citation-9 citation-end-9"> (a Go-based WAF engine) running the OWASP Core Rule Set (CRS).</span> Because it uses Caddy instead of Nginx, any native Nginx syntax or snippet you used to paste into NPM will not work here.
|
||||
|
||||
<div _ngcontent-ng-c405365209="" chatcontentdimensions="" class="chat-history-scroll-container lm" id="bkmrk-socfortress---medium"><div _ngcontent-ng-c405365209="" class="conversation-container message-actions-hover-boundary ng-star-inserted"><div _ngcontent-ng-c3598951857=""><div _ngcontent-ng-c3060770435="" class="response-container response-container-with-gpi ng-tns-c3060770435-20 no-background" data-hveid="1" jslog="173900;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="presented-response-container ng-tns-c3060770435-20" data-hveid="2" jslog="283401;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="response-container-content ng-tns-c3060770435-20 has-thoughts"><div _ngcontent-ng-c3598951857="" class="response-content ng-tns-c3060770435-20"><div _ngcontent-ng-c1922256434="" class="container"><div _ngcontent-ng-c2066222059="" aria-busy="false" aria-live="off" class="markdown markdown-main-panel enable-luminous-fast-follows enable-updated-hr-color stronger tutor-markdown-rendering" dir="ltr" inline-copy-host=""><div _ngcontent-ng-c2854609078="" class="source-inline-chip-container luminous-sources hide-from-message-actions ng-star-inserted"><div _ngcontent-ng-c2854609078="" class="source-label-container gds-body-s ng-star-inserted" dir="auto"><span class="source-title">SOCFortress - Medium</span></div></div></div></div></div></div></div></div></div></div></div>To achieve what you are trying to do for your authentik integration (forwarding headers, establishing proxy providers, or setting up forward auth), you have to approach it differently.
|
||||
|
||||
### How to manage this in SOCFortress WAF
|
||||
|
||||
Because the SOCFortress WAF UI focuses entirely on the security layer (toggling rules, managing exclusions, and GeoIP blocking) rather than deep proxy customizations, you have two primary options:
|
||||
|
||||
#### 1. Use Caddy’s Native File System (The Backend Route)
|
||||
|
||||
If you need to inject custom proxy headers or set up an upstream forward-auth middleware for authentik, you cannot use the UI wrapper. You must modify the underlying **Caddyfile** configuration directly.
|
||||
|
||||
<div _ngcontent-ng-c405365209="" chatcontentdimensions="" class="chat-history-scroll-container lm" id="bkmrk-socfortress-spins-up"><div _ngcontent-ng-c405365209="" class="conversation-container message-actions-hover-boundary ng-star-inserted"><div _ngcontent-ng-c3598951857=""><div _ngcontent-ng-c3060770435="" class="response-container response-container-with-gpi ng-tns-c3060770435-20 no-background" data-hveid="1" jslog="173900;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="presented-response-container ng-tns-c3060770435-20" data-hveid="2" jslog="283401;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="response-container-content ng-tns-c3060770435-20 has-thoughts"><div _ngcontent-ng-c3598951857="" class="response-content ng-tns-c3060770435-20"><div _ngcontent-ng-c1922256434="" class="container"><div _ngcontent-ng-c2066222059="" aria-busy="false" aria-live="off" class="markdown markdown-main-panel enable-luminous-fast-follows enable-updated-hr-color stronger tutor-markdown-rendering" dir="ltr" inline-copy-host="">- <span class="citation-8 citation-end-8">SOCFortress spins up via Docker Compose.</span> You will need to locate the volume or directory where the WAF platform stores its generated Caddy configuration.
|
||||
|
||||
<div _ngcontent-ng-c2854609078="" class="source-inline-chip-container luminous-sources hide-from-message-actions ng-star-inserted"><div _ngcontent-ng-c2854609078="" class="source-label-container gds-body-s ng-star-inserted" dir="auto"><span class="source-title">SOCFortress - Medium</span></div></div>
|
||||
- Instead of Nginx directives (`proxy_set_header`), you will use Caddy’s `header_up` or `forward_auth` directives.
|
||||
|
||||
</div></div></div></div></div></div></div></div></div>#### 2. The Recommended Topology: Layer Both Tools
|
||||
|
||||
If you love the ease of Nginx Proxy Manager for advanced routing, SSL client certificates, and authentik header manipulation, you don't actually have to abandon it. Because SOCFortress is a dedicated WAF, it is highly common to chain them together:
|
||||
|
||||
`Internet` ➔ `SOCFortress WAF (Filters bad traffic)` ➔ `Nginx Proxy Manager (Handles Authentik, headers, & routing)` ➔ `Your Apps`
|
||||
|
||||
<span class="citation-7 citation-end-7">By placing SOCFortress at the edge, it strips out SQL injections, XSS, and bot traffic before it ever hits NPM.</span> NPM can then securely parse your custom headers and pass them back to your applications exactly as you have it configured right now.
|
||||
|
||||
<div _ngcontent-ng-c405365209="" chatcontentdimensions="" class="chat-history-scroll-container lm" id="bkmrk-socfortress---medium-1"><div _ngcontent-ng-c405365209="" class="conversation-container message-actions-hover-boundary ng-star-inserted"><div _ngcontent-ng-c3598951857=""><div _ngcontent-ng-c3060770435="" class="response-container response-container-with-gpi ng-tns-c3060770435-20 no-background" data-hveid="1" jslog="173900;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="presented-response-container ng-tns-c3060770435-20" data-hveid="2" jslog="283401;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="response-container-content ng-tns-c3060770435-20 has-thoughts"><div _ngcontent-ng-c3598951857="" class="response-content ng-tns-c3060770435-20"><div _ngcontent-ng-c1922256434="" class="container"><div _ngcontent-ng-c2066222059="" aria-busy="false" aria-live="off" class="markdown markdown-main-panel enable-luminous-fast-follows enable-updated-hr-color stronger tutor-markdown-rendering" dir="ltr" inline-copy-host=""><div _ngcontent-ng-c2854609078="" class="source-inline-chip-container luminous-sources hide-from-message-actions ng-star-inserted"><div _ngcontent-ng-c2854609078="" class="source-label-container gds-body-s ng-star-inserted" dir="auto"><span class="source-title">SOCFortress - Medium</span></div></div></div></div></div></div></div></div></div></div></div>Are you attempting to use authentik via standard proxy headers (like `X-Authentik-Username`) or are you trying to implement a full Forward Auth middleware block?
|
||||
|
||||
<div _ngcontent-ng-c405365209="" chatcontentdimensions="" class="chat-history-scroll-container lm" id="bkmrk-"><div _ngcontent-ng-c405365209="" class="conversation-container message-actions-hover-boundary ng-star-inserted" id="bkmrk--1"><div _ngcontent-ng-c3598951857=""><div _ngcontent-ng-c3060770435="" class="response-container response-container-with-gpi ng-tns-c3060770435-20 no-background" data-hveid="1" jslog="173900;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="presented-response-container ng-tns-c3060770435-20" data-hveid="2" jslog="283401;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="response-container-content ng-tns-c3060770435-20 has-thoughts"><div _ngcontent-ng-c3598951857="" class="response-content ng-tns-c3060770435-20"><div _ngcontent-ng-c1922256434="" class="container"><div _ngcontent-ng-c2066222059="" aria-busy="false" aria-live="off" class="markdown markdown-main-panel enable-luminous-fast-follows enable-updated-hr-color stronger tutor-markdown-rendering" dir="ltr" id="bkmrk--2" inline-copy-host=""></div></div><div _ngcontent-ng-c3598951857="" class="response-footer complete has-thoughts gap"><div _ngcontent-ng-c3537663079="" jslog="268370;track:generic_click,impression">
|
||||
</div></div></div></div></div><div _ngcontent-ng-c3060770435="" class="response-container-footer ng-tns-c3060770435-20 ng-star-inserted"><div _ngcontent-ng-c356251628="" class="actions-container-v2 ng-tns-c356251628-21"><div _ngcontent-ng-c356251628="" class="buttons-container-v2 ng-tns-c356251628-21 ng-star-inserted"><button aria-label="Good response" aria-pressed="false" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button><button aria-label="Bad response" aria-pressed="false" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button><button aria-label="Copy" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button><div _ngcontent-ng-c356251628="" class="ng-tns-c356251628-21 ng-star-inserted"><div _ngcontent-ng-c356251628="" class="menu-button-wrapper ng-tns-c356251628-21 ng-star-inserted"><div _ngcontent-ng-c356251628="" class="more-menu-button-container ng-tns-c356251628-21"><button aria-label="Show more options" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button>
|
||||
</div></div></div><div _ngcontent-ng-c356251628="" class="spacer ng-tns-c356251628-21 ng-star-inserted">
|
||||
</div></div></div></div></div></div><div _ngcontent-ng-c405365209="" class="restart-chat-button-scroll-placeholder ng-star-inserted">
|
||||
</div></div><div _ngcontent-ng-c405365209="" class="conversation-container message-actions-hover-boundary ng-star-inserted"><div _ngcontent-ng-c3824630338="" class="user-query-container"><div _ngcontent-ng-c3824630338="" class="file-preview-container ng-star-inserted">
|
||||
</div><div _ngcontent-ng-c3824630338="" class="query-content ng-star-inserted verticle-align-for-single-line-text" data-hveid="0" data-ved="0CAAQ3ucQahcKEwixkur_vK-VAxUAAAAAHQAAAAAQVA" decode-data-ved="1" jslog="275422;track:impression,attention"><div _ngcontent-ng-c3824630338="" aria-level="2" class="query-text gds-body-l" dir="ltr" role="heading">
|
||||
</div></div></div></div></div>I like this approach, how would you go about setting this up
|
||||
|
||||
Setting up this multi-layer structure is highly effective because it gives you the best of both worlds: enterprise-grade threat filtering at the front door, and easy web routing at the back.
|
||||
|
||||
To achieve this seamlessly, the most critical shift is moving **SSL termination** to the SOCFortress WAF layer. Because a WAF needs to inspect the actual HTTP payload for malicious activity (like SQL injections or cross-site scripting), it *must* hold your public SSL certificates.
|
||||
|
||||
Here is exactly how to sequence this setup:
|
||||
|
||||
## The Blueprint
|
||||
|
||||
<div _ngcontent-ng-c405365209="" chatcontentdimensions="" class="chat-history-scroll-container lm" id="bkmrk-1-configure-npm-to-a"><div _ngcontent-ng-c405365209="" class="conversation-container message-actions-hover-boundary ng-star-inserted"><div _ngcontent-ng-c3598951857=""><div _ngcontent-ng-c3060770435="" class="response-container response-container-with-gpi ng-tns-c3060770435-22 no-background" data-hveid="1" jslog="173900;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="presented-response-container ng-tns-c3060770435-22" data-hveid="2" jslog="283401;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="response-container-content ng-tns-c3060770435-22"><div _ngcontent-ng-c3598951857="" class="response-content ng-tns-c3060770435-22"><div _ngcontent-ng-c1922256434="" class="container"><div _ngcontent-ng-c2066222059="" aria-busy="false" aria-live="off" class="markdown markdown-main-panel enable-luminous-fast-follows enable-updated-hr-color stronger tutor-markdown-rendering" dir="ltr" inline-copy-host=""><div class="attachment-container unknown"><div class="sequence-container" data-hveid="3" jslog="308913;track:impression,attention"><div class="sequence-event ng-star-inserted"><div class="sequence-event-marker-container hide-from-message-actions" hide-from-message-actions=""><div class="sequence-event-marker gds-body-s ng-star-inserted">1</div><div class="sequence-event-line ng-star-inserted">
|
||||
</div></div><div class="sequence-event-content"><div class="hide-from-message-actions" hide-from-message-actions=""><div class="sequence-event-title gds-emphasized-body-l">Configure NPM to accept HTTP traffic</div><div class="sequence-event-subtitle gds-extended-caption ng-star-inserted">Nginx Proxy Manager Side</div></div><div class="sequence-event-description gds-body-l">
|
||||
</div></div></div></div></div></div></div></div></div></div></div></div></div></div>Keep your internal host configurations inside NPM exactly as they are right now, including your custom headers for authentik. However, **disable SSL** inside NPM for these hosts. NPM will now listen for unencrypted HTTP traffic from the internal network, specifically from the SOCFortress container.
|
||||
|
||||
<div _ngcontent-ng-c405365209="" chatcontentdimensions="" class="chat-history-scroll-container lm" id="bkmrk-2-establish-docker-n"><div _ngcontent-ng-c405365209="" class="conversation-container message-actions-hover-boundary ng-star-inserted"><div _ngcontent-ng-c3598951857=""><div _ngcontent-ng-c3060770435="" class="response-container response-container-with-gpi ng-tns-c3060770435-22 no-background" data-hveid="1" jslog="173900;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="presented-response-container ng-tns-c3060770435-22" data-hveid="2" jslog="283401;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="response-container-content ng-tns-c3060770435-22"><div _ngcontent-ng-c3598951857="" class="response-content ng-tns-c3060770435-22"><div _ngcontent-ng-c1922256434="" class="container"><div _ngcontent-ng-c2066222059="" aria-busy="false" aria-live="off" class="markdown markdown-main-panel enable-luminous-fast-follows enable-updated-hr-color stronger tutor-markdown-rendering" dir="ltr" inline-copy-host=""><div class="attachment-container unknown"><div class="sequence-container" data-hveid="3" jslog="308913;track:impression,attention"><div class="sequence-event ng-star-inserted"><div class="sequence-event-content"><div class="sequence-event-description gds-body-l">
|
||||
</div></div></div><div class="sequence-event ng-star-inserted"><div class="sequence-event-marker-container hide-from-message-actions" hide-from-message-actions=""><div class="sequence-event-marker gds-body-s ng-star-inserted">2</div><div class="sequence-event-line ng-star-inserted">
|
||||
</div></div><div class="sequence-event-content"><div class="hide-from-message-actions" hide-from-message-actions=""><div class="sequence-event-title gds-emphasized-body-l">Establish Docker network communication</div><div class="sequence-event-subtitle gds-extended-caption ng-star-inserted">Networking Side</div></div><div class="sequence-event-description gds-body-l">
|
||||
</div></div></div></div></div></div></div></div></div></div></div></div></div></div>Ensure the SOCFortress WAF container can reach your NPM container. If they are on the same machine, the cleanest method is to join them to a shared custom Docker network:
|
||||
|
||||
<div _ngcontent-ng-c1958104536="" class="code-block ng-tns-c1958104536-26 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="4" id="bkmrk-bash" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_30b702b296852146","c_4a90e1c3e176c650",null,"rc_c6288839219dd582",null,null,"en",null,1,null,null,1,0]]"><div _ngcontent-ng-c1958104536="" class="formatted-code-block-internal-container ng-tns-c1958104536-26"><div _ngcontent-ng-c1958104536="" class="animated-opacity ng-tns-c1958104536-26"><div _ngcontent-ng-c1958104536="" class="code-block-decoration header-formatted gds-emphasized-body-m ng-tns-c1958104536-26 ng-star-inserted"><span class="ng-tns-c1958104536-26">Bash</span><div _ngcontent-ng-c1958104536="" class="buttons ng-tns-c1958104536-26 ng-star-inserted"><button aria-label="Download code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button><button aria-label="Copy code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button>
|
||||
</div></div></div></div></div>```
|
||||
docker network connect your_shared_network_name socfortress-waf-container
|
||||
|
||||
```
|
||||
|
||||
<div _ngcontent-ng-c405365209="" chatcontentdimensions="" class="chat-history-scroll-container lm" id="bkmrk-3-point-the-public-d"><div _ngcontent-ng-c405365209="" class="conversation-container message-actions-hover-boundary ng-star-inserted"><div _ngcontent-ng-c3598951857=""><div _ngcontent-ng-c3060770435="" class="response-container response-container-with-gpi ng-tns-c3060770435-22 no-background" data-hveid="1" jslog="173900;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="presented-response-container ng-tns-c3060770435-22" data-hveid="2" jslog="283401;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="response-container-content ng-tns-c3060770435-22"><div _ngcontent-ng-c3598951857="" class="response-content ng-tns-c3060770435-22"><div _ngcontent-ng-c1922256434="" class="container"><div _ngcontent-ng-c2066222059="" aria-busy="false" aria-live="off" class="markdown markdown-main-panel enable-luminous-fast-follows enable-updated-hr-color stronger tutor-markdown-rendering" dir="ltr" inline-copy-host=""><div class="attachment-container unknown"><div class="sequence-container" data-hveid="3" jslog="308913;track:impression,attention"><div class="sequence-event ng-star-inserted"><div class="sequence-event-content"><div class="sequence-event-description gds-body-l">
|
||||
</div></div></div><div class="sequence-event ng-star-inserted"><div class="sequence-event-marker-container hide-from-message-actions" hide-from-message-actions=""><div class="sequence-event-marker gds-body-s ng-star-inserted">3</div><div class="sequence-event-line ng-star-inserted">
|
||||
</div></div><div class="sequence-event-content"><div class="hide-from-message-actions" hide-from-message-actions=""><div class="sequence-event-title gds-emphasized-body-l">Point the public DNS to SOCFortress</div><div class="sequence-event-subtitle gds-extended-caption ng-star-inserted">DNS Side</div></div><div class="sequence-event-description gds-body-l">
|
||||
</div></div></div></div></div></div></div></div></div></div></div></div></div></div>Update your firewall, port forwarding, or public DNS records so that ports **80** and **443** point directly to the host machine running SOCFortress WAF, rather than Nginx Proxy Manager.
|
||||
|
||||
<div _ngcontent-ng-c405365209="" chatcontentdimensions="" class="chat-history-scroll-container lm" id="bkmrk-4-add-the-applicatio"><div _ngcontent-ng-c405365209="" class="conversation-container message-actions-hover-boundary ng-star-inserted"><div _ngcontent-ng-c3598951857=""><div _ngcontent-ng-c3060770435="" class="response-container response-container-with-gpi ng-tns-c3060770435-22 no-background" data-hveid="1" jslog="173900;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="presented-response-container ng-tns-c3060770435-22" data-hveid="2" jslog="283401;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="response-container-content ng-tns-c3060770435-22"><div _ngcontent-ng-c3598951857="" class="response-content ng-tns-c3060770435-22"><div _ngcontent-ng-c1922256434="" class="container"><div _ngcontent-ng-c2066222059="" aria-busy="false" aria-live="off" class="markdown markdown-main-panel enable-luminous-fast-follows enable-updated-hr-color stronger tutor-markdown-rendering" dir="ltr" inline-copy-host=""><div class="attachment-container unknown"><div class="sequence-container" data-hveid="3" jslog="308913;track:impression,attention"><div class="sequence-event ng-star-inserted"><div class="sequence-event-content"><div class="sequence-event-description gds-body-l">
|
||||
</div></div></div><div class="sequence-event ng-star-inserted"><div class="sequence-event-marker-container hide-from-message-actions" hide-from-message-actions=""><div class="sequence-event-marker gds-body-s ng-star-inserted">4</div></div><div class="sequence-event-content"><div class="hide-from-message-actions" hide-from-message-actions=""><div class="sequence-event-title gds-emphasized-body-l">Add the application in SOCFortress WAF</div><div class="sequence-event-subtitle gds-extended-caption ng-star-inserted">SOCFortress Side</div></div><div class="sequence-event-description gds-body-l">
|
||||
</div></div></div></div></div></div></div></div></div></div></div></div></div></div>Open the SOCFortress UI and create a new protected application asset:
|
||||
|
||||
<div _ngcontent-ng-c405365209="" chatcontentdimensions="" class="chat-history-scroll-container lm" id="bkmrk-domain%2Fhost%3A-your-pu"><div _ngcontent-ng-c405365209="" class="conversation-container message-actions-hover-boundary ng-star-inserted"><div _ngcontent-ng-c3598951857=""><div _ngcontent-ng-c3060770435="" class="response-container response-container-with-gpi ng-tns-c3060770435-22 no-background" data-hveid="1" jslog="173900;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="presented-response-container ng-tns-c3060770435-22" data-hveid="2" jslog="283401;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="response-container-content ng-tns-c3060770435-22"><div _ngcontent-ng-c3598951857="" class="response-content ng-tns-c3060770435-22"><div _ngcontent-ng-c1922256434="" class="container"><div _ngcontent-ng-c2066222059="" aria-busy="false" aria-live="off" class="markdown markdown-main-panel enable-luminous-fast-follows enable-updated-hr-color stronger tutor-markdown-rendering" dir="ltr" inline-copy-host=""><div class="attachment-container unknown"><div class="sequence-container" data-hveid="3" jslog="308913;track:impression,attention"><div class="sequence-event ng-star-inserted"><div class="sequence-event-content"><div class="sequence-event-description gds-body-l">- **Domain/Host:** Your public domain (e.g., `app.domain.com`).
|
||||
- **Upstream/Backend URL:** Point this directly to your NPM container. Use NPM's container name or its IP on the shared network (e.g., `http://nginx-proxy-manager:80` or `[http://172.20.0.5:80](http://172.20.0.5:80)`).
|
||||
- **Cert Mode:** Enable Let's Encrypt or upload your custom certificates here. SOCFortress (via Caddy) will handle the HTTPS handshake with the internet.
|
||||
|
||||
</div></div></div></div></div></div></div></div></div></div></div></div></div></div>### Understanding the Traffic Flow
|
||||
|
||||
Once active, a user request flows through your network using this lifecycle:
|
||||
|
||||
```
|
||||
[User Browser]
|
||||
│
|
||||
▼ (HTTPS - TLS Encrypted)
|
||||
[SOCFortress WAF] <-- Decrypts traffic & runs Coraza OWASP CRS rules
|
||||
│
|
||||
▼ (HTTP - Internal Network)
|
||||
[Nginx Proxy Manager] <-- Matches domain, executes your Custom Authentik Config, injects headers
|
||||
│
|
||||
▼ (HTTP/HTTPS)
|
||||
[Your Application Backend]
|
||||
|
||||
```
|
||||
|
||||
> ⚠️ **Important Note on Client IPs:** Because NPM now sits behind SOCFortress, NPM will think *every* request is coming from the SOCFortress WAF IP. To ensure your authentik logs show the real user's location, make sure you have `Real IP` or `X-Forwarded-For` headers enabled in your NPM advanced config to pass through the original client IP.
|
||||
|
||||
<div _ngcontent-ng-c405365209="" chatcontentdimensions="" class="chat-history-scroll-container lm" id="bkmrk--3"><div _ngcontent-ng-c405365209="" class="conversation-container message-actions-hover-boundary ng-star-inserted" id="bkmrk--4"><div _ngcontent-ng-c3598951857=""><div _ngcontent-ng-c3060770435="" class="response-container response-container-with-gpi ng-tns-c3060770435-22 no-background" data-hveid="1" jslog="173900;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="presented-response-container ng-tns-c3060770435-22" data-hveid="2" jslog="283401;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="response-container-content ng-tns-c3060770435-22"><div _ngcontent-ng-c3598951857="" class="response-content ng-tns-c3060770435-22"><div _ngcontent-ng-c1922256434="" class="container"><div _ngcontent-ng-c2066222059="" aria-busy="false" aria-live="off" class="markdown markdown-main-panel enable-luminous-fast-follows enable-updated-hr-color stronger tutor-markdown-rendering" dir="ltr" id="bkmrk--5" inline-copy-host=""></div></div><div _ngcontent-ng-c3598951857="" class="response-footer contains-extensions-response complete gap"><div _ngcontent-ng-c3537663079="" jslog="268370;track:generic_click,impression">
|
||||
</div></div></div></div></div><div _ngcontent-ng-c3060770435="" class="response-container-footer ng-tns-c3060770435-22 ng-star-inserted"><div _ngcontent-ng-c356251628="" class="actions-container-v2 ng-tns-c356251628-23"><div _ngcontent-ng-c356251628="" class="buttons-container-v2 ng-tns-c356251628-23 ng-star-inserted"><button aria-label="Good response" aria-pressed="false" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button><button aria-label="Bad response" aria-pressed="false" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button><button aria-label="Copy" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button><div _ngcontent-ng-c356251628="" class="ng-tns-c356251628-23 ng-star-inserted"><div _ngcontent-ng-c356251628="" class="menu-button-wrapper ng-tns-c356251628-23 ng-star-inserted"><div _ngcontent-ng-c356251628="" class="more-menu-button-container ng-tns-c356251628-23"><button aria-label="Show more options" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button>
|
||||
</div></div></div><div _ngcontent-ng-c356251628="" class="spacer ng-tns-c356251628-23 ng-star-inserted">
|
||||
</div></div></div></div></div></div><div _ngcontent-ng-c405365209="" class="restart-chat-button-scroll-placeholder ng-star-inserted">
|
||||
</div></div><div _ngcontent-ng-c405365209="" class="conversation-container message-actions-hover-boundary ng-star-inserted"><div _ngcontent-ng-c3824630338="" class="user-query-container"><div _ngcontent-ng-c3824630338="" class="file-preview-container ng-star-inserted">
|
||||
</div><div _ngcontent-ng-c3824630338="" class="query-content ng-star-inserted" data-hveid="0" data-ved="0CAAQ3ucQahcKEwixkur_vK-VAxUAAAAAHQAAAAAQVw" decode-data-ved="1" jslog="275422;track:impression,attention"><div _ngcontent-ng-c3824630338="" aria-level="2" class="query-text gds-body-l" dir="ltr" role="heading">
|
||||
</div></div></div></div></div>So the only site entry in SOCFortress is pointing to my NPM and the NPM hold all my site forwarding minus the SSL certs, correct?
|
||||
|
||||
Yes, you have it **100% correct**.
|
||||
|
||||
You are essentially reducing SOCFortress down to a smart "security shield" at your front door, while letting NPM continue to act as the traffic controller that knows exactly which application gets what headers.
|
||||
|
||||
To visualize how clean this makes your configuration, here is how the responsibilities are split:
|
||||
|
||||
### What SOCFortress Does:
|
||||
|
||||
<div _ngcontent-ng-c405365209="" chatcontentdimensions="" class="chat-history-scroll-container lm" id="bkmrk-listens-on-public-po"><div _ngcontent-ng-c405365209="" class="conversation-container message-actions-hover-boundary ng-star-inserted"><div _ngcontent-ng-c3598951857=""><div _ngcontent-ng-c3060770435="" class="response-container response-container-with-gpi ng-tns-c3060770435-24 no-background response-container-has-multiple-responses" data-hveid="1" jslog="173900;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="presented-response-container ng-tns-c3060770435-24" data-hveid="2" jslog="283401;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="response-container-content ng-tns-c3060770435-24"><div _ngcontent-ng-c3598951857="" class="response-content ng-tns-c3060770435-24"><div _ngcontent-ng-c1922256434="" class="container"><div _ngcontent-ng-c2066222059="" aria-busy="false" aria-live="polite" class="markdown markdown-main-panel enable-luminous-fast-follows enable-updated-hr-color stronger tutor-markdown-rendering" dir="ltr" inline-copy-host="">- Listens on public ports `80` and `443`.
|
||||
- Handles the **SSL/TLS Certificates** for your domains.
|
||||
- Inspects incoming requests for malicious payloads (WAF).
|
||||
- Has **one job** for routing: Forward *all* clean traffic for your domains directly to NPM's HTTP port.
|
||||
|
||||
</div></div></div></div></div></div></div></div></div>### What Nginx Proxy Manager Does:
|
||||
|
||||
<div _ngcontent-ng-c405365209="" chatcontentdimensions="" class="chat-history-scroll-container lm" id="bkmrk-listens-internally-o"><div _ngcontent-ng-c405365209="" class="conversation-container message-actions-hover-boundary ng-star-inserted"><div _ngcontent-ng-c3598951857=""><div _ngcontent-ng-c3060770435="" class="response-container response-container-with-gpi ng-tns-c3060770435-24 no-background response-container-has-multiple-responses" data-hveid="1" jslog="173900;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="presented-response-container ng-tns-c3060770435-24" data-hveid="2" jslog="283401;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="response-container-content ng-tns-c3060770435-24"><div _ngcontent-ng-c3598951857="" class="response-content ng-tns-c3060770435-24"><div _ngcontent-ng-c1922256434="" class="container"><div _ngcontent-ng-c2066222059="" aria-busy="false" aria-live="polite" class="markdown markdown-main-panel enable-luminous-fast-follows enable-updated-hr-color stronger tutor-markdown-rendering" dir="ltr" inline-copy-host="">- Listens internally on port `80` (no SSL configured inside NPM).
|
||||
- Looks at the incoming domain name (e.g., `app1.yourdomain.com`).
|
||||
- Executes your **Custom Nginx Configuration** for authentik (injecting user headers, checking auth states).
|
||||
- Forwards the final, authenticated request to your actual backend application container.
|
||||
|
||||
</div></div></div></div></div></div></div></div></div>### A Quick Tip for a Smooth Cutover
|
||||
|
||||
When you remove the SSL certificates from NPM, Nginx will expect plain HTTP traffic. In the SOCFortress asset configuration, make sure your upstream/backend destination is explicitly set to use **`http://`** and not `https://` when pointing to NPM.
|
||||
|
||||
If you use a common Docker network, you can just point SOCFortress to `http://nginx-proxy-manager-container-name:80` and it will map perfectly.
|
||||
|
||||
To handle a setup where **SOCFortress (Caddy+Coraza)** sits at the edge and **Nginx Proxy Manager (NPM)** sits behind it, you have to break the port binding conflict on the host machine.
|
||||
|
||||
Only **one** service can bind to ports `80` and `443` on your host's network interface. Since SOCFortress is your WAF edge, it gets the privilege of claiming those host ports.
|
||||
|
||||
## 1. The Strategy
|
||||
|
||||
1. **SOCFortress (`caddy-waf`)** binds to the host's `80:80` and `443:443`. It catches all incoming public internet traffic first.
|
||||
2. **Nginx Proxy Manager (`nginx-proxy-manager`)** removes its public host port bindings for `80` and `443`.
|
||||
3. **Internal Routing:** Because both services share the `waf-internal` Docker network, the Caddy WAF can route traffic directly to NPM using NPM's container name (`nginx-proxy-manager`) on internal ports `80` and `443`.
|
||||
|
||||
## 2. Updated Docker Compose
|
||||
|
||||
Here is how your `app` (NPM) and `caddy-waf` services should look in the file.
|
||||
|
||||
Notice that NPM's ports `80` and `443` have been removed from the host mapping, but port `81` is kept so you can still log into the NPM Admin UI from your local network.
|
||||
|
||||
<div _ngcontent-ng-c1958104536="" class="code-block ng-tns-c1958104536-32 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="3" id="bkmrk-yaml" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_d832160a9ba7de36","c_26bc2a2bba230e76",null,"rc_c711e786aee4e246",null,null,"en",null,1,null,null,1,0]]"><div _ngcontent-ng-c1958104536="" class="formatted-code-block-internal-container ng-tns-c1958104536-32"><div _ngcontent-ng-c1958104536="" class="animated-opacity ng-tns-c1958104536-32"><div _ngcontent-ng-c1958104536="" class="code-block-decoration header-formatted gds-emphasized-body-m ng-tns-c1958104536-32 ng-star-inserted"><span class="ng-tns-c1958104536-32">YAML</span><div _ngcontent-ng-c1958104536="" class="buttons ng-tns-c1958104536-32 ng-star-inserted"><button aria-label="Download code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button><button aria-label="Copy code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button></div></div></div></div></div>```
|
||||
app:
|
||||
image: 'jc21/nginx-proxy-manager:latest'
|
||||
container_name: nginx-proxy-manager
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- DB_MYSQL_HOST=db
|
||||
- DB_MYSQL_PORT=3306
|
||||
- DB_MYSQL_USER=${MYSQL_USER}
|
||||
- DB_MYSQL_PASSWORD=${MYSQL_PASSWORD}
|
||||
- DB_MYSQL_NAME=${MYSQL_DATABASE}
|
||||
ports:
|
||||
- '81:81' # Kept so you can access the NPM dashboard on host-ip:81
|
||||
# '80:80' and '443:443' REMOVED here. They are reachable internally via the network.
|
||||
volumes:
|
||||
- ./data/npm:/data
|
||||
- ./data/letsencrypt:/etc/letsencrypt
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
- proxy
|
||||
- proxydb
|
||||
- waf-internal
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:81/api/status"]
|
||||
interval: 20s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 40s
|
||||
|
||||
caddy-waf:
|
||||
image: ghcr.io/socfortress/waf-caddy:${WAF_IMAGE_TAG:-latest}
|
||||
container_name: caddy-waf
|
||||
ports:
|
||||
- "80:80" # Claims the host ports
|
||||
- "443:443" # Claims the host ports
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
- coraza-rules:/etc/coraza/rules
|
||||
- caddy-config:/etc/caddy
|
||||
- coraza-custom:/etc/coraza/custom
|
||||
- crs-data:/etc/coraza/crs-rules
|
||||
- coraza-logs:/var/log/coraza
|
||||
networks:
|
||||
- waf-internal
|
||||
depends_on:
|
||||
http-echo:
|
||||
condition: service_started
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O-", "http://localhost:2019/config/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 512M
|
||||
restart: unless-stopped
|
||||
|
||||
```
|
||||
|
||||
<div _ngcontent-ng-c1958104536="" class="code-block ng-tns-c1958104536-32 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="3" id="bkmrk--7" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_d832160a9ba7de36","c_26bc2a2bba230e76",null,"rc_c711e786aee4e246",null,null,"en",null,1,null,null,1,0]]"><div _ngcontent-ng-c1958104536="" class="formatted-code-block-internal-container ng-tns-c1958104536-32"><div _ngcontent-ng-c1958104536="" class="animated-opacity ng-tns-c1958104536-32"></div></div></div>## 3. How to Route Traffic in the WAF
|
||||
|
||||
When you configure your backend pools or proxy routes inside the SOCFortress Admin UI, you will point the upstream traffic to NPM.
|
||||
|
||||
Instead of an IP address, use Docker's internal DNS resolver:
|
||||
|
||||
- **Upstream Target:** `http://nginx-proxy-manager:80` (or port `443` if passing through SSL)
|
||||
|
||||
From there, NPM will receive the clean, inspected traffic and can handle the final routing to your other services (like Gitea, Mailcow, or homepage) living on your `proxy` network.
|
||||
|
||||
<div _ngcontent-ng-c405365209="" chatcontentdimensions="" class="chat-history-scroll-container lm" id="bkmrk--9"><div _ngcontent-ng-c405365209="" class="conversation-container message-actions-hover-boundary ng-star-inserted" id="bkmrk--10"><div _ngcontent-ng-c3598951857=""><div _ngcontent-ng-c3060770435="" class="response-container response-container-with-gpi ng-tns-c3060770435-24 no-background response-container-has-multiple-responses" data-hveid="1" jslog="173900;track:impression,attention"><div _ngcontent-ng-c3060770435="" class="response-container-footer ng-tns-c3060770435-24 ng-star-inserted"><div _ngcontent-ng-c356251628="" class="actions-container-v2 ng-tns-c356251628-25"><div _ngcontent-ng-c356251628="" class="buttons-container-v2 ng-tns-c356251628-25 ng-star-inserted"><button aria-label="Good response" aria-pressed="false" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button><button aria-label="Bad response" aria-pressed="false" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button><div _ngcontent-ng-c356251628="" class="ng-tns-c356251628-25 ng-star-inserted"><button aria-label="Redo" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button>
|
||||
</div><button aria-label="Copy" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button><div _ngcontent-ng-c356251628="" class="ng-tns-c356251628-25 ng-star-inserted"><div _ngcontent-ng-c356251628="" class="menu-button-wrapper ng-tns-c356251628-25 ng-star-inserted"><div _ngcontent-ng-c356251628="" class="more-menu-button-container ng-tns-c356251628-25"><button aria-label="Show more options" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-badge mat-unthemed mat-badge-overlap mat-badge-above mat-badge-after mat-badge-small mat-badge-hidden ng-star-inserted"></button>
|
||||
</div></div></div><div _ngcontent-ng-c356251628="" class="spacer ng-tns-c356251628-25 ng-star-inserted">
|
||||
</div></div></div></div></div></div><div _ngcontent-ng-c405365209="" class="restart-chat-button-scroll-placeholder ng-star-inserted">
|
||||
</div></div><div _ngcontent-ng-c405365209="" class="autosuggest-scrim">
|
||||
</div></div><fieldset class="input-area-container ng-trigger ng-trigger-inputAreaAnimation" id="bkmrk-%C2%A0-4"></fieldset>
|
||||
|
||||
# Docker Compose
|
||||
|
||||
```
|
||||
---
|
||||
services:
|
||||
db:
|
||||
image: 'jc21/mariadb-aria:latest'
|
||||
container_name: npm-db
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE}
|
||||
- MYSQL_USER=${MYSQL_USER}
|
||||
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
|
||||
- MARIADB_AUTO_UPGRADE=1
|
||||
volumes:
|
||||
- ./data/mysql:/var/lib/mysql
|
||||
networks:
|
||||
- proxydb
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u${MYSQL_USER}", "-p${MYSQL_PASSWORD}"]
|
||||
interval: 20s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
app:
|
||||
image: 'jc21/nginx-proxy-manager:latest'
|
||||
container_name: nginx-proxy-manager
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
environment:
|
||||
- TZ=${TZ}
|
||||
- DB_MYSQL_HOST=db
|
||||
- DB_MYSQL_PORT=3306
|
||||
- DB_MYSQL_USER=${MYSQL_USER}
|
||||
- DB_MYSQL_PASSWORD=${MYSQL_PASSWORD}
|
||||
- DB_MYSQL_NAME=${MYSQL_DATABASE}
|
||||
ports:
|
||||
- '81:81' # Kept so you can access the NPM dashboard on host-ip:81
|
||||
# '80:80' and '443:443' REMOVED here. They are reachable internally via the network.
|
||||
volumes:
|
||||
- ./data/npm:/data
|
||||
- ./data/letsencrypt:/etc/letsencrypt
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
- proxy
|
||||
- proxydb
|
||||
- waf-internal
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:81/api/status"]
|
||||
interval: 20s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 40s
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Caddy + Coraza: WAF engine and reverse proxy
|
||||
# ---------------------------------------------------------------------------
|
||||
caddy-waf:
|
||||
image: ghcr.io/socfortress/waf-caddy:${WAF_IMAGE_TAG:-latest}
|
||||
container_name: caddy-waf
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
# Lets the WAF proxy to apps running on the Docker host (e.g. an nginx
|
||||
# upstream) via http://host.docker.internal:<port>. See demo/README.md.
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
- coraza-rules:/etc/coraza/rules
|
||||
- caddy-config:/etc/caddy
|
||||
- coraza-custom:/etc/coraza/custom
|
||||
- crs-data:/etc/coraza/crs-rules # live CRS dir (seeded by admin-api)
|
||||
- coraza-logs:/var/log/coraza
|
||||
networks:
|
||||
- waf-internal
|
||||
depends_on:
|
||||
http-echo:
|
||||
condition: service_started
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O-", "http://localhost:2019/config/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 512M
|
||||
restart: unless-stopped
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dummy upstream — replace with your real protected app(s)
|
||||
# ---------------------------------------------------------------------------
|
||||
http-echo:
|
||||
image: hashicorp/http-echo:0.2.3
|
||||
container_name: http-echo
|
||||
command: ["-text=upstream-ok"]
|
||||
user: "65534:65534"
|
||||
networks:
|
||||
- waf-internal
|
||||
healthcheck:
|
||||
test: ["NONE"]
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.25"
|
||||
memory: 64M
|
||||
restart: unless-stopped
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# FastAPI Admin API
|
||||
# ---------------------------------------------------------------------------
|
||||
admin-api:
|
||||
image: ghcr.io/socfortress/waf-admin-api:${WAF_IMAGE_TAG:-latest}
|
||||
container_name: admin-api
|
||||
env_file: .env
|
||||
volumes:
|
||||
- tls-certs:/certs # shared TLS cert volume (see admin-ui)
|
||||
# GeoLite2 DB is user-supplied — MaxMind licensing forbids redistribution.
|
||||
# Point GEOIP_DB_PATH at your downloaded GeoLite2-City.mmdb (see README).
|
||||
- ./GeoLite2-City/GeoLite2-City.mmdb}:/etc/geoip-bundle/GeoLite2-City.mmdb:ro
|
||||
- geoip-data:/etc/geoip
|
||||
- coraza-rules:/etc/coraza/rules
|
||||
- caddy-config:/etc/caddy
|
||||
- coraza-custom:/etc/coraza/custom
|
||||
- crs-data:/etc/coraza/crs-rules # live CRS dir (seeded from image bundle)
|
||||
- coraza-logs:/var/log/coraza
|
||||
networks:
|
||||
- waf-internal
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O-", "http://localhost:8000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 512M
|
||||
restart: unless-stopped
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# React Admin UI (served by Nginx, HTTPS on 8443)
|
||||
# ---------------------------------------------------------------------------
|
||||
admin-ui:
|
||||
image: ghcr.io/socfortress/waf-admin-ui:${WAF_IMAGE_TAG:-latest}
|
||||
container_name: admin-ui
|
||||
ports:
|
||||
- "8443:8080"
|
||||
volumes:
|
||||
- tls-certs:/etc/nginx/certs # shared with admin-api for cert upload + reload
|
||||
networks:
|
||||
- waf-internal
|
||||
depends_on:
|
||||
admin-api:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--no-check-certificate", "-O-", "https://localhost:8080/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.5"
|
||||
memory: 128M
|
||||
restart: unless-stopped
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PostgreSQL 16
|
||||
# ---------------------------------------------------------------------------
|
||||
postgres:
|
||||
image: postgres:16.3-alpine
|
||||
container_name: postgres
|
||||
env_file: .env
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- waf-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 512M
|
||||
restart: unless-stopped
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Redis 7
|
||||
# ---------------------------------------------------------------------------
|
||||
redis:
|
||||
image: redis:7.2.5-alpine
|
||||
container_name: redis
|
||||
user: redis
|
||||
command: ["redis-server", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
networks:
|
||||
- waf-internal
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 5s
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "0.5"
|
||||
memory: 256M
|
||||
restart: unless-stopped
|
||||
|
||||
# =============================================================================
|
||||
# Networks
|
||||
# =============================================================================
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
proxydb:
|
||||
external: true
|
||||
waf-internal:
|
||||
driver: bridge
|
||||
|
||||
# =============================================================================
|
||||
# Named volumes
|
||||
# =============================================================================
|
||||
volumes:
|
||||
coraza-rules:
|
||||
coraza-custom:
|
||||
coraza-logs:
|
||||
caddy-config:
|
||||
crs-data:
|
||||
geoip-data:
|
||||
postgres-data:
|
||||
redis-data:
|
||||
tls-certs:
|
||||
|
||||
```
|
||||
Reference in New Issue
Block a user