Tripwires

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 serve fronts the loopback API with an automatic HTTPS certificate at https://<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. See infrastructure/tailscale/setup-tailscale.sh.
  • WireGuard. Classic peer VPN to the mgmt subnet (udp/51820); the app reaches the API over the tunnel. See infrastructure/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 from mgmt and over tailscale0; allow WireGuard udp/51820 and Tailscale udp/41641. The honeypot subnet is explicitly dropped towards tailscale0 (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

  1. Attacker → persona (public trap port) → sensor (Cowrie/OpenCanary) logs JSON.
  2. ship.py sidecar 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.)
  3. API reads PostgreSQL → serves iOS over HTTPS (Tailscale-serve TLS, or WireGuard tunnel).
  4. API → APNs → push notification (minimal payload, grouped per burst).
  5. Admin (Tailscale/WireGuard) → API for safe management (persona CRUD, prefs).