MosswartOverlord/generate_data.py
Erik a28b61511c security: enforce real plugin secret, fix proxy auth bypass, loopback DB ports, nightly backups
- SHARED_SECRET now read from env and fail-closed: unset/placeholder refuses
  ALL plugin connections (constant-time compare). The old hardcoded
  'your_shared_secret' in this public repo was no auth at all. Dockerfile
  default removed; generate_data.py reads the env var.
- SECRET_KEY fails closed at startup (main.py and agent/auth.py) instead of
  falling back to a publicly-known signing key; agent systemd unit now
  requires /etc/overlord/agent.env (no '-' prefix).
- AuthMiddleware + /ws/live: replace the 172.x source-IP trust (which every
  nginx-proxied internet request satisfied via docker-proxy — full session
  bypass and unauthenticated in-game command injection) with
  private-source AND no X-Forwarded-For, i.e. only genuinely internal
  callers (overlord-agent on the host, compose-network services). Invariant
  documented in nginx/overlord.conf: every tracker-bound location must set
  X-Forwarded-For.
- /character-stats/test endpoints gated behind admin (they upsert real rows).
- docker-compose: bind 5432/5433 to 127.0.0.1 (both DBs were internet-
  reachable; active brute-force observed in dereth-db logs).
- discord-rare-monitor: drop dead SHARED_SECRET constant.
- scripts/backup-databases.sh + docs/backups.md: nightly pg_dump of both DBs
  (telemetry/spawn hypertable data excluded), 10MB canary, umask 077,
  TimescaleDB restore procedure.
- Remove stray mangled-path css file from repo root.

Adversarially reviewed pre-deploy (3-lens workflow): ship verdict; deploy-
sequencing blockers addressed (secret staged before enforcement, exec bit
set, cron uses bash).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 17:02:47 +02:00

82 lines
3.3 KiB
Python

"""
generate_data.py - Standalone script to simulate plugin telemetry data.
This script connects to the plugin WebSocket at /ws/position and sends
fabricated TelemetrySnapshot payloads at regular intervals. Useful for:
- Functional testing of the telemetry ingestion pipeline
- Demonstrating real-time map updates without a live game client
"""
import asyncio # Async event loop and sleep support
import os
import websockets # WebSocket client for Python
import json # JSON serialization of payloads
from datetime import datetime, timedelta, timezone
from main import TelemetrySnapshot # Pydantic model matches plugin protocol
async def main() -> None:
"""
Continuously emit synthetic telemetry snapshots at fixed intervals.
Updates in-game coordinates (ew, ns) gradually and increments
an 'online_time' counter to mimic real gameplay progression.
Each iteration:
1. Build TelemetrySnapshot with current state
2. Serialize to JSON, set 'type' field
3. Send over WebSocket
4. Sleep for 'wait' seconds
"""
# Interval between snapshots (seconds)
wait = 10
# Simulated total online time in seconds (starting at 24h)
online_time = 24 * 3600
# Starting coordinates (E/W and N/S)
ew = 0.0
ns = 0.0
# WebSocket endpoint for plugin telemetry. The secret must match the
# backend's SHARED_SECRET env var (no insecure default anymore).
secret = os.environ["SHARED_SECRET"]
uri = f"ws://localhost:8000/ws/position?secret={secret}"
# Connect to the plugin WebSocket endpoint with authentication
# Establish WebSocket connection to the server
async with websockets.connect(uri) as websocket:
print(f"Connected to {uri}")
# Loop indefinitely, sending telemetry at each interval
while True:
# Construct a new TelemetrySnapshot dataclass instance
snapshot = TelemetrySnapshot(
character_name="Test name",
char_tag="test_tag",
session_id="test_session_id",
timestamp=datetime.now(tz=timezone.utc),
ew=ew,
ns=ns,
z=0,
kills=0,
kills_per_hour="kph_str",
onlinetime=str(timedelta(seconds=online_time)),
deaths=0,
# rares_found removed from telemetry payload; tracked via rare events
prismatic_taper_count=0,
vt_state="test state",
)
# Prepare payload dictionary:
# - Convert Pydantic model to dict
# - Remove any extraneous fields (e.g., 'rares_found')
# - Insert message 'type' for server routing
payload = snapshot.model_dump()
payload.pop("rares_found", None)
payload["type"] = "telemetry"
# Send JSON-encoded payload over WebSocket
# Transmit JSON payload (datetime serialized via default=str)
await websocket.send(json.dumps(payload, default=str))
print(f"Sent snapshot: EW={ew:.2f}, NS={ns:.2f}")
# Wait before next update, then increment simulated state
await asyncio.sleep(wait)
ew += 0.1
ns += 0.1
online_time += wait
if __name__ == "__main__":
asyncio.run(main())