mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-27 09:32:28 +02:00
2e14e75a0e
Before this change, every peer-push HMAC was derived from the single fleet-shared MESH_PEER_PUSH_SECRET. The receiver could prove "this request was signed by someone who knows the fleet secret" but it could NOT prove which peer signed it. Any peer that knew the global secret could compute the expected HMAC for any other peer URL and forge a push pretending to be that peer. Fix: introduce MESH_PEER_SECRETS, an optional comma-separated url=secret map. When a peer URL appears in the map, only the listed per-peer secret is accepted for it -- the global secret is ignored for that specific URL. Peer A no longer knows peer B's secret, so peer A cannot forge a push claiming to be peer B. The new helper resolve_peer_key_for_url() in mesh_crypto.py wraps the lookup and is called from every existing peer-push call site: - backend/auth.py:_verify_peer_push_hmac (receiver) - backend/main.py:_http_peer_push_loop (Infonet event push) - backend/main.py:_http_gate_pull_loop (gate event pull) - backend/main.py:_http_gate_push_loop (gate event push) - backend/services/mesh/mesh_router.py (two transports, push) - backend/services/mesh/mesh_hashchain.py (gate wire ref key) - backend/services/mesh/mesh_wormhole_prekey.py (peer prekey lookup) Zero hostility, by design: - Single-peer installs leave MESH_PEER_SECRETS empty -> resolver falls back to MESH_PEER_PUSH_SECRET -> behavior is byte-for-byte unchanged. - Multi-peer installs that haven't migrated yet behave exactly as before. - Multi-peer installs that DO migrate set MESH_PEER_SECRETS on both ends of each peering and immediately close the impersonation surface for those URLs. Migration is incremental: unlisted peers keep using the global secret. Tests in backend/tests/test_per_peer_secret_resolver.py: - env parsing (default, override, whitespace, malformed entries, cache) - precedence: per-peer beats global - migration window: unlisted peer falls back to global - IMPERSONATION REFUSAL: peer A with global-secret-only cannot forge HMAC for peer B that has a per-peer secret configured - IMPERSONATION REFUSAL: peer A with its OWN per-peer secret cannot forge HMAC for peer B - positive control: legitimate peer B request verifies - zero-behavior-change: single-peer install produces the same key bytes as before the change Credit: tg12 (external security audit, P1/High/High confidence)
104 lines
4.8 KiB
YAML
104 lines
4.8 KiB
YAML
## Default registry is GHCR because the GitHub release workflow publishes:
|
|
## ghcr.io/bigbodycobain/shadowbroker-backend:latest
|
|
## ghcr.io/bigbodycobain/shadowbroker-frontend:latest
|
|
##
|
|
## GitLab mirror images can still be used by swapping the image lines to:
|
|
## registry.gitlab.com/bigbodycobain/shadowbroker/backend:latest
|
|
## registry.gitlab.com/bigbodycobain/shadowbroker/frontend:latest
|
|
|
|
services:
|
|
backend:
|
|
image: ghcr.io/bigbodycobain/shadowbroker-backend:latest
|
|
container_name: shadowbroker-backend
|
|
ports:
|
|
- "${BIND:-127.0.0.1}:${BACKEND_PORT:-8000}:8000"
|
|
environment:
|
|
- AIS_API_KEY=${AIS_API_KEY:-}
|
|
- OPENSKY_CLIENT_ID=${OPENSKY_CLIENT_ID:-}
|
|
- OPENSKY_CLIENT_SECRET=${OPENSKY_CLIENT_SECRET:-}
|
|
- LTA_ACCOUNT_KEY=${LTA_ACCOUNT_KEY:-}
|
|
- ADMIN_KEY=${ADMIN_KEY:-}
|
|
- FINNHUB_API_KEY=${FINNHUB_API_KEY:-}
|
|
# Override allowed CORS origins (comma-separated). Auto-detects LAN IPs if empty.
|
|
- CORS_ORIGINS=${CORS_ORIGINS:-}
|
|
# Private Infonet bootstrap seeds. Seeds are discovery hints, not fixed roots.
|
|
- MESH_BOOTSTRAP_SEED_PEERS=${MESH_BOOTSTRAP_SEED_PEERS:-http://gqpbunqbgtkcqilvclm3xrkt3zowjyl3s62kkktvojgvxzizamvbrqid.onion:8000}
|
|
- MESH_DEFAULT_SYNC_PEERS=${MESH_DEFAULT_SYNC_PEERS:-}
|
|
# Operator-trusted sync/push peers. Leave empty unless you control the peer secret on both sides.
|
|
- MESH_RELAY_PEERS=${MESH_RELAY_PEERS:-}
|
|
# Shared transport auth for operator peer push. Must be set to a unique secret per deployment.
|
|
- MESH_PEER_PUSH_SECRET=${MESH_PEER_PUSH_SECRET:-}
|
|
# Issue #256: optional per-peer HMAC secrets. Comma-separated
|
|
# `url=secret` pairs (no spaces). When a peer URL appears here, only
|
|
# the listed per-peer secret is accepted for it — the global
|
|
# MESH_PEER_PUSH_SECRET above is ignored for that specific URL. This
|
|
# closes the cross-peer impersonation surface for multi-peer fleets.
|
|
# Single-peer installs leave this empty (default) for unchanged
|
|
# behavior. Both sides of a peering must agree on the per-peer
|
|
# secret for a given URL.
|
|
- MESH_PEER_SECRETS=${MESH_PEER_SECRETS:-}
|
|
# Meshtastic MQTT is opt-in to avoid passive load on the public broker.
|
|
# Set MESH_MQTT_ENABLED=true in .env only when this node should join live MQTT.
|
|
- MESH_MQTT_ENABLED=${MESH_MQTT_ENABLED:-false}
|
|
- MESH_MQTT_BROKER=${MESH_MQTT_BROKER:-mqtt.meshtastic.org}
|
|
- MESH_MQTT_PORT=${MESH_MQTT_PORT:-1883}
|
|
- MESH_MQTT_USER=${MESH_MQTT_USER:-meshdev}
|
|
- MESH_MQTT_PASS=${MESH_MQTT_PASS:-large4cats}
|
|
- MESH_MQTT_PSK=${MESH_MQTT_PSK:-}
|
|
- MESH_MQTT_INCLUDE_DEFAULT_ROOTS=${MESH_MQTT_INCLUDE_DEFAULT_ROOTS:-true}
|
|
- MESH_MQTT_EXTRA_ROOTS=${MESH_MQTT_EXTRA_ROOTS:-}
|
|
- MESH_MQTT_EXTRA_TOPICS=${MESH_MQTT_EXTRA_TOPICS:-}
|
|
- MESHTASTIC_OPERATOR_CALLSIGN=${MESHTASTIC_OPERATOR_CALLSIGN:-}
|
|
# The bundled Docker UI talks to the backend across Docker's private bridge.
|
|
# Treat that bridge as local operator access while ports remain bound to 127.0.0.1 by default.
|
|
- SHADOWBROKER_TRUST_DOCKER_BRIDGE_LOCAL_OPERATOR=${SHADOWBROKER_TRUST_DOCKER_BRIDGE_LOCAL_OPERATOR:-1}
|
|
# Issue #250: bridge trust is now bound to specific container hostnames
|
|
# (default: 'frontend' compose service + 'shadowbroker-frontend' container
|
|
# name). If you rename the frontend service or run with a different
|
|
# container_name, list the hostnames here (comma-separated, no spaces).
|
|
- SHADOWBROKER_TRUSTED_FRONTEND_HOSTS=${SHADOWBROKER_TRUSTED_FRONTEND_HOSTS:-frontend,shadowbroker-frontend}
|
|
volumes:
|
|
- backend_data:/app/data
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost:8000/api/health"]
|
|
interval: 15s
|
|
timeout: 10s
|
|
retries: 5
|
|
start_period: 60s
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: ${BACKEND_MEMORY_LIMIT:-4G}
|
|
cpus: '2'
|
|
|
|
frontend:
|
|
image: ghcr.io/bigbodycobain/shadowbroker-frontend:latest
|
|
container_name: shadowbroker-frontend
|
|
ports:
|
|
- "${BIND:-127.0.0.1}:${FRONTEND_PORT:-3000}:3000"
|
|
environment:
|
|
# Points the Next.js server-side proxy at the backend container via Docker networking.
|
|
# Change this if your backend runs on a different host or port.
|
|
- BACKEND_URL=http://backend:8000
|
|
# Lets the server-side proxy authenticate protected local-node API calls.
|
|
- ADMIN_KEY=${ADMIN_KEY:-}
|
|
depends_on:
|
|
backend:
|
|
condition: service_healthy
|
|
restart: unless-stopped
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 20s
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 512M
|
|
cpus: '1'
|
|
|
|
volumes:
|
|
backend_data:
|