-
Notifications
You must be signed in to change notification settings - Fork 0
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.
- 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.
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.
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 itOn 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>}.
- Peers reach each other on their secwatch port (default
8931). - A
leafneeds 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.
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>.