Network isolation: egress firewall + named bridge
Adds the host-level egress firewall recommended by the upstream DeerFlow team's "run in a VLAN" guidance, adapted to a Fritzbox-only home network where LAN VLANs are not available. - docker/docker-compose.override.yaml: pins the upstream deer-flow Docker network to a stable Linux bridge name br-deerflow so the firewall can address it without guessing Docker's auto-generated br-<hash>. Used as a -f overlay on top of the upstream compose file. - scripts/deerflow-firewall.sh: idempotent up/down/status wrapper that installs DOCKER-USER iptables rules. Allowlist for 10.67.67.1 (Searx) and 10.67.67.2 (XTTS/Whisper/Ollama-local), hard block for 192.168.3.0/24 (home LAN), 10.0.0.0/8, 172.16.0.0/12. Stateful return rule keeps inbound LAN access to published ports working. - scripts/deerflow-firewall.nix: NixOS module snippet defining a systemd unit ordered After=docker.service so the rules survive dockerd restarts and follow its lifecycle. Copy into configuration.nix and nixos-rebuild switch. - HARDENING.md: new section 2.7 "Network isolation (egress firewall)" with allow/block tables, bring-up steps, and smoke-test commands. Guarantees: rules match on -i br-deerflow, so if the bridge does not exist, the rules are no-ops and do not affect any other container (paperclip, telebrowser, openclaw-gateway, ...). Stopping the container leaves the rules in place but inert; stopping the systemd unit removes them.
This commit is contained in:
80
HARDENING.md
80
HARDENING.md
@@ -127,6 +127,82 @@ DeerFlow guardrails see only `tool.name` (e.g. `web_search`), and both the
|
||||
hardened and the native version export the same name. The real block is
|
||||
the import-time stub above.
|
||||
|
||||
### 2.7 Network isolation (egress firewall)
|
||||
|
||||
The DeerFlow team recommends running the agent in a dedicated VLAN. Our
|
||||
Fritzbox cannot do LAN VLANs, so instead we put the container behind an
|
||||
egress firewall on the Docker host. The container can reach the Internet
|
||||
plus a small whitelist of Wireguard hosts (Searx, local model servers),
|
||||
but cannot scan or attack any device on the home LAN. Inbound traffic
|
||||
from the LAN to the container's published ports is unaffected because the
|
||||
rules are stateful.
|
||||
|
||||
**Allow** (egress from container):
|
||||
|
||||
| Destination | Purpose |
|
||||
|---|---|
|
||||
| `1.0.0.0/8` ... `223.0.0.0/8` (public Internet) | Ollama Cloud, search backends |
|
||||
| `10.67.67.1` | Searx (Wireguard) |
|
||||
| `10.67.67.2` | XTTS / Whisper / Ollama-local (Wireguard) |
|
||||
|
||||
**Block** (egress from container):
|
||||
|
||||
| Destination | Reason |
|
||||
|---|---|
|
||||
| `192.168.3.0/24` | home LAN — no lateral movement |
|
||||
| `10.0.0.0/8` (except whitelisted /32) | other Wireguard subnets, RFC1918 |
|
||||
| `172.16.0.0/12` | other Docker bridges |
|
||||
|
||||
**Implementation:**
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `docker/docker-compose.override.yaml` | Pins the upstream `deer-flow` Docker network to a stable Linux bridge name `br-deerflow`, so the firewall can address it without guessing Docker's auto-generated `br-<hash>`. Used as a `-f` overlay on top of `deer-flow/docker/docker-compose.yaml`. |
|
||||
| `scripts/deerflow-firewall.sh` | Idempotent `up`/`down`/`status` wrapper that installs the iptables rules in the `DOCKER-USER` chain. Inserted in reverse order so the final chain order is: stateful return, allow Searx, allow Ollama-local, block LAN, block /8, block /12. |
|
||||
| `scripts/deerflow-firewall.nix` | NixOS module snippet defining `systemd.services.deerflow-firewall`. Ordered `After=docker.service`, `Requires=docker.service`, `PartOf=docker.service` so the rules survive `dockerd` restarts and follow its lifecycle. Copy into `configuration.nix` and `nixos-rebuild switch`. |
|
||||
|
||||
**Important guarantees:**
|
||||
|
||||
- The rules match on `-i br-deerflow`. If the bridge does not exist (e.g.
|
||||
DeerFlow has never been started), the rules are no-ops and **do not
|
||||
affect any other container** (paperclip, telebrowser, openclaw-gateway,
|
||||
...). They activate automatically the moment `docker compose ... up -d`
|
||||
creates the bridge.
|
||||
- Stopping or removing the DeerFlow container leaves the rules in place
|
||||
but inert. Stopping the systemd unit removes them.
|
||||
- The script is idempotent: `up` will never duplicate a rule, `down`
|
||||
removes all copies.
|
||||
|
||||
**Bring up:**
|
||||
|
||||
```bash
|
||||
cd /home/data/deerflow-factory
|
||||
docker compose \
|
||||
-f deer-flow/docker/docker-compose.yaml \
|
||||
-f docker/docker-compose.override.yaml \
|
||||
up -d
|
||||
|
||||
# Then either run the script directly:
|
||||
sudo scripts/deerflow-firewall.sh up
|
||||
|
||||
# ...or, on NixOS, copy scripts/deerflow-firewall.nix into configuration.nix
|
||||
# and:
|
||||
sudo nixos-rebuild switch
|
||||
systemctl status deerflow-firewall
|
||||
```
|
||||
|
||||
**Smoke tests** (run from inside the container, e.g. `docker exec -it <id> sh`):
|
||||
|
||||
```bash
|
||||
# allowed
|
||||
curl -s -o /dev/null -w "%{http_code}\n" --max-time 5 http://10.67.67.1:8888/ # Searx -> 200
|
||||
curl -s -o /dev/null -w "%{http_code}\n" --max-time 5 https://api.cloudflare.com/ # Internet -> 200/4xx
|
||||
|
||||
# blocked (should fail with "no route" / "host prohibited" / timeout)
|
||||
curl -s -o /dev/null -w "%{http_code}\n" --max-time 5 http://192.168.3.1/ # FAIL
|
||||
curl -s -o /dev/null -w "%{http_code}\n" --max-time 5 http://10.67.67.16/ # FAIL (blocked by 10/8 reject; .16 is not whitelisted)
|
||||
```
|
||||
|
||||
## 3. Verification
|
||||
|
||||
All checks below assume `PYTHONPATH=deer-flow/backend/packages/harness`.
|
||||
@@ -225,4 +301,8 @@ backend/tests/test_searx_tools.py (facto
|
||||
config.yaml (hardened runtime config, references only searx tools)
|
||||
.env.example (template, no secrets)
|
||||
HARDENING.md (this file)
|
||||
|
||||
docker/docker-compose.override.yaml (named bridge br-deerflow)
|
||||
scripts/deerflow-firewall.sh (egress firewall up/down/status)
|
||||
scripts/deerflow-firewall.nix (NixOS systemd unit snippet)
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user