Network & Security Architecture
Phase 0 baseline. Adjust IPs and VLANs to the final deployment before production.
1. Zones
| Zone | Purpose | Trust |
|---|---|---|
| Internet / untrusted | Attack surface | Untrusted |
| Edge router/firewall | DNAT inbound, block outbound from DMZ | Managed |
Honeypot DMZ (honeypot) |
Public-facing personas only | Untrusted |
Management VLAN (mgmt) |
API, DB, orchestrator, SSH admin | Trusted (VPN-only) |
| Trusted LAN | Personal/business devices | Trusted — never reachable from honeypot |
2. Diagram
Rendered as it is actually built (Cowrie + OpenCanary sensors → ship.py
batched HTTP shippers → API/normalizer → PostgreSQL). Red dashed edges are the
containment boundaries enforced by nftables.conf + docker-egress.sh: the
honeypot DMZ has no route to the management plane, the admin VPN, or your LAN.
flowchart TD
ATK([Internet / attackers]):::untrusted
DNAT["Edge router / firewall<br/>DNAT: public IP → trap ports<br/>Egress: deny honeypot net"]:::managed
subgraph DMZ["Honeypot DMZ — VLAN 20 · 10.20.0.0/24 · no egress"]
direction LR
COW["Linux persona<br/>Cowrie · SSH 2222"]:::persona
WWS["Windows WS<br/>OpenCanary · RDP/SMB"]:::persona
WSRV["Windows Server<br/>OpenCanary · RDP/SMB"]:::persona
NAS["NAS<br/>OpenCanary · HTTP/FTP/SMB"]:::persona
end
subgraph MGMT["Management VLAN — VLAN 10 · 10.10.0.0/24 · VPN-only"]
SHIP["Log shippers<br/>ship.py · batched"]:::mgmt
API["Backend API<br/>FastAPI · loopback:8000"]:::mgmt
NORM["Normalizer"]:::mgmt
PG[("PostgreSQL<br/>events + audit_log")]:::mgmt
end
VPN{{"Admin VPN<br/>Tailscale serve HTTPS · or WireGuard"}}:::managed
IOS["iPhone<br/>Honeypot Monitor"]:::trusted
LAN["Trusted LAN<br/>your real devices"]:::trusted
ATK -->|trap ports only| DNAT
DNAT -->|DNAT| DMZ
COW -.->|read JSON logs| SHIP
WWS -.-> SHIP
WSRV -.-> SHIP
NAS -.-> SHIP
SHIP -->|POST /ingest| API
API --> NORM --> PG
PG --> API
API -->|stats · SSE · events| VPN
VPN <-->|HTTPS| IOS
API -->|APNs push| IOS
DMZ ==>|"⛔ no route"| MGMT
DMZ ==>|"⛔ denied"| VPN
DMZ ==>|"⛔ denied"| LAN
classDef untrusted fill:#fdecea,stroke:#d33,color:#611;
classDef managed fill:#fff4e5,stroke:#c80,color:#630;
classDef persona fill:#fdecea,stroke:#d33,color:#611;
classDef mgmt fill:#e8f0fe,stroke:#36c,color:#123;
classDef trusted fill:#e6f4ea,stroke:#2a7,color:#062;
linkStyle 13,14,15 stroke:#d33,stroke-width:2px,stroke-dasharray:6 4;
A standalone, shareable rendering of this diagram is published as an Artifact (see the project handoff); the source of truth is this Mermaid block.
3. IP plan (example)
| Network | CIDR | Notes |
|---|---|---|
| Management VLAN | 10.10.0.0/24 |
API/DB/orchestrator; WireGuard peer range 10.10.0.2–10.10.0.50 |
| Honeypot DMZ | 10.20.0.0/24 |
Persona containers; no default route |
| WireGuard tunnel | 10.99.0.0/24 |
Client→server tunnel subnet |
4. Port mapping (example)
| Public port | Service | Persona |
|---|---|---|
| 22 | SSH/Telnet | Linux server / appliance |
| 2222 | SSH (alt) | Additional Linux persona |
| 445 | SMB | Windows / NAS |
| 139 | NetBIOS | Windows |
| 3389 | RDP-style trap | Windows |
| 21 | FTP | NAS / FTP-SFTP |
| 80/443 | HTTP/HTTPS | Web server / NAS admin |
| 3306/5432 | DB | Database server |
| 23 | Telnet | Appliance / Linux |
The backend API is never published on the public IP. It listens on host loopback only and is reachable over the admin VPN. Two supported options:
- Tailscale (recommended).
tailscale servefronts the loopback API with an automatic HTTPS certificate athttps://<host>.<tailnet>.ts.net, so the iOS app gets real TLS with no ATS exception. SSO + ACLs restrict who can reach the host; SSH runs over the tailnet. Seeinfrastructure/tailscale/setup-tailscale.sh.- WireGuard. Classic peer VPN to the mgmt subnet (
udp/51820); the app reaches the API over the tunnel. Seeinfrastructure/wireguard/.Either way, the honeypot DMZ is firewalled off from the admin VPN interface — a compromised persona cannot ride the VPN to your other devices.
5. Firewall policy
- Host base (
nftables.conf): default-drop input/forward; allow loopback + established/related; allow SSH frommgmtand overtailscale0; allow WireGuardudp/51820and Tailscaleudp/41641. The honeypot subnet is explicitly dropped towardstailscale0(containment) in addition to the general egress drop. - Honeypot egress (
docker-egress.sh): DOCKER-USER chain drops all new connections originating from the honeypot subnet; an explicit allowlist permits only approved destinations (e.g., a logging sink if any). - Edge: inbound DNAT limited to published honeypot ports; outbound from honeypot VLAN denied by default.
6. Data flows
- Attacker → persona (public trap port) → sensor (Cowrie/OpenCanary) logs JSON.
ship.pysidecar tails the log, batches lines →POST /ingest?source=→ normalizer → PostgreSQL (events +audit_log). (The Phase-0 plan named Redis Streams as the bus; the shipped system uses the batched HTTP shipper instead — simpler, and it gives one grouped push per burst for free.)- API reads PostgreSQL → serves iOS over HTTPS (Tailscale-serve TLS, or WireGuard tunnel).
- API → APNs → push notification (minimal payload, grouped per burst).
- Admin (Tailscale/WireGuard) → API for safe management (persona CRUD, prefs).