Skip to content

Cluster

Paco5687 edited this page Jul 13, 2026 · 5 revisions

Cluster

Run secwatch on several boxes and join them into a peer-to-peer cluster — there is no central hub to stand up or defend. Every node stays fully autonomous (it detects and bans itself); nodes gossip bans so a hit on one hardens all, and any peer can view the whole fleet from its own dashboard.

How it works

  • Explicit join, shared secret — like a Proxmox cluster. A node only joins if it has the secret; there's no auto-discovery, so a random box on the LAN can't join, read, or inject.
  • HMAC-signed peer traffic — the secret never crosses the wire; requests are timestamped to bound replay. Use TLS or a WireGuard tunnel if the inter-node path isn't trusted.
  • Ban gossip, two ways — a new ban is pushed to reachable peers immediately and every node periodically pulls each peer's blocklist, so convergence is robust and a firewalled node still hardens itself by pulling. Received bans are applied locally and are poison-safe: secwatch refuses to ban a trusted or shared-edge (e.g. Cloudflare) IP, so a bad entry can't take out a legit address.
  • Federated view — open any peer's Cluster tab and it assembles the fleet by querying its peers; no data is centralized.

Roles

Set cluster.role per node — nothing is hard-coded:

Role For Behavior
standalone not clustered (default) no cluster activity
peer internal / trusted boxes full member: shares bans, is queryable, reads peers
leaf exposed / less-trusted boxes push-only: contributes its bans + events and pulls the blocklist, but is not queryable and can't read peers

Why a leaf? Your internet-facing box is your riskiest one. As a leaf it still contributes to fleet hardening and stays visible, but a compromise of it can't be used to read your internal fleet's telemetry or poison the shared blocklist. A leaf also only makes outbound connections, so its port can stay firewalled — ideal for a box on an isolated VLAN.

Setup

On the first node:

# secwatch.yaml:  cluster: { role: peer, url: http://THIS-NODE:8931 }   → restart
python -m secwatch.cluster init          # prints the shared secret — copy it

On each other internal node (peer):

# secwatch.yaml:  cluster: { role: peer, url: http://THIS-NODE:8931 }   → restart
python -m secwatch.cluster join http://FIRST-NODE:8931 '<secret>'

On an exposed node (leaf):

# secwatch.yaml:  cluster: { role: leaf }   (no url — a leaf isn't queried)   → restart
python -m secwatch.cluster join http://A-PEER:8931 '<secret>'

CLI: python -m secwatch.cluster {init | join <url> <secret> | list | leave <name> | ping <url>}.

Networking

  • Peers reach each other on their secwatch port (default 8931).
  • A leaf needs only outbound reach to a peer's port — nothing inbound — so an exposed box can keep its port firewalled from everything, including the cluster.
  • Keep secwatch's port off the public internet.

Verify

Open a peer's dashboard → Cluster tab (it appears only when clustered) → all nodes are listed with liveness, role, event and ban counts. Ban an IP on one box and watch it appear on the others within a gossip cycle (cluster.gossip_secs, default 60s), attributed as cluster:<origin-node>.

Clone this wiki locally