diff --git a/docker-compose.yaml b/docker-compose.yaml index c877d3b..2a8b294 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -49,6 +49,9 @@ services: DATABASE_URL: postgresql://${POSTGRES_USER:-adctrl}:${POSTGRES_PASSWORD:-adctrl_secure_password}@postgres:5432/${POSTGRES_DB:-adctrl} SECRET_TOKEN: ${SECRET_TOKEN} SCOREBOARD_WS_URL: ${SCOREBOARD_WS_URL:-ws://10.60.0.1:8080/api/events} + SCOREBOARD_API_URL: ${SCOREBOARD_API_URL:-http://10.60.0.1:8080/api} + USE_HTTP_POLLING: ${USE_HTTP_POLLING:-false} + POLLING_INTERVAL: ${POLLING_INTERVAL:-10} OUR_TEAM_ID: ${OUR_TEAM_ID:-1} ALERT_THRESHOLD_POINTS: ${ALERT_THRESHOLD_POINTS:-100} ALERT_THRESHOLD_TIME: ${ALERT_THRESHOLD_TIME:-300} diff --git a/scoreboard_injector/main.py b/scoreboard_injector/main.py index b85e6cc..51583bf 100644 --- a/scoreboard_injector/main.py +++ b/scoreboard_injector/main.py @@ -17,6 +17,9 @@ from contextlib import asynccontextmanager DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://adctrl:adctrl@postgres:5432/adctrl") SECRET_TOKEN = os.getenv("SECRET_TOKEN", "change-me-in-production") SCOREBOARD_WS_URL = os.getenv("SCOREBOARD_WS_URL", "ws://scoreboard:8080/api/events") +SCOREBOARD_API_URL = os.getenv("SCOREBOARD_API_URL", "http://10.60.0.1:8080/api") +USE_HTTP_POLLING = os.getenv("USE_HTTP_POLLING", "false").lower() == "true" +POLLING_INTERVAL = int(os.getenv("POLLING_INTERVAL", "10")) # seconds OUR_TEAM_ID = int(os.getenv("OUR_TEAM_ID", "1")) ALERT_THRESHOLD_POINTS = float(os.getenv("ALERT_THRESHOLD_POINTS", "100")) ALERT_THRESHOLD_TIME = int(os.getenv("ALERT_THRESHOLD_TIME", "300")) # seconds @@ -184,6 +187,58 @@ async def send_telegram_alert(message: str): except Exception as e: print(f"Error sending telegram alert: {e}") +async def http_polling_listener(): + """Poll scoreboard HTTP API for attacks as alternative to WebSocket""" + last_round = 0 + + while True: + try: + async with aiohttp.ClientSession() as session: + # Try to fetch scoreboard data + # Common ForcAD endpoints: /api/scoreboard, /api/teams, /api/attacks + endpoints_to_try = [ + f"{SCOREBOARD_API_URL}/attacks", + f"{SCOREBOARD_API_URL}/scoreboard", + f"{SCOREBOARD_API_URL}/flag_stats" + ] + + for endpoint in endpoints_to_try: + try: + async with session.get(endpoint, timeout=aiohttp.ClientTimeout(total=5)) as resp: + if resp.status == 200: + data = await resp.json() + print(f"✓ Fetched data from {endpoint}") + + # Process based on response structure + if isinstance(data, list): + # List of attacks/events + for item in data: + await process_attack_event(item) + elif isinstance(data, dict): + # Could be scoreboard with nested data + # Try common keys + for key in ['attacks', 'flags', 'events', 'data']: + if key in data and isinstance(data[key], list): + for item in data[key]: + await process_attack_event(item) + break + else: + # Might be a single event + await process_attack_event(data) + + break # Success, no need to try other endpoints + except aiohttp.ClientError as e: + print(f"Failed to fetch from {endpoint}: {e}") + continue + except json.JSONDecodeError: + print(f"Invalid JSON from {endpoint}") + continue + + except Exception as e: + print(f"HTTP polling error: {e}") + + await asyncio.sleep(POLLING_INTERVAL) + async def websocket_listener(): """Listen to scoreboard WebSocket for events""" reconnect_delay = 5 @@ -248,8 +303,17 @@ async def lifespan(app: FastAPI): global db_pool, ws_task db_pool = await asyncpg.create_pool(DATABASE_URL, min_size=2, max_size=10) - # Start WebSocket listener - ws_task = asyncio.create_task(websocket_listener()) + # Start listener based on configuration + if USE_HTTP_POLLING: + print(f"Starting HTTP polling mode (interval: {POLLING_INTERVAL}s)") + print(f"Scoreboard API: {SCOREBOARD_API_URL}") + ws_task = asyncio.create_task(http_polling_listener()) + else: + print(f"Starting WebSocket listener mode") + print(f"Scoreboard WS: {SCOREBOARD_WS_URL}") + ws_task = asyncio.create_task(websocket_listener()) + + print(f"Our team ID: {OUR_TEAM_ID}") yield @@ -268,7 +332,13 @@ app = FastAPI(title="Scoreboard Injector", lifespan=lifespan) # API Endpoints @app.get("/health") async def health_check(): - return {"status": "ok", "timestamp": datetime.utcnow().isoformat()} + return { + "status": "ok", + "timestamp": datetime.utcnow().isoformat(), + "team_id": OUR_TEAM_ID, + "mode": "http_polling" if USE_HTTP_POLLING else "websocket", + "scoreboard_url": SCOREBOARD_API_URL if USE_HTTP_POLLING else SCOREBOARD_WS_URL + } @app.get("/stats", dependencies=[Depends(verify_token)]) async def get_stats(): @@ -409,6 +479,56 @@ async def inject_test_attack(attacker_id: int, victim_id: int, service: str = "t await process_attack_event(test_event) return {"status": "injected", "event": test_event} +@app.get("/debug/scoreboard", dependencies=[Depends(verify_token)]) +async def debug_scoreboard(): + """Check if scoreboard is reachable and show raw data""" + results = { + "mode": "http_polling" if USE_HTTP_POLLING else "websocket", + "endpoints_tested": [] + } + + try: + async with aiohttp.ClientSession() as session: + # Test WebSocket + results["websocket_url"] = SCOREBOARD_WS_URL + try: + async with session.ws_connect(SCOREBOARD_WS_URL, timeout=aiohttp.ClientTimeout(total=5)) as ws: + results["websocket_status"] = "reachable" + except Exception as e: + results["websocket_status"] = f"unreachable: {str(e)}" + + # Test HTTP endpoints + results["http_api_url"] = SCOREBOARD_API_URL + endpoints = [ + "/attacks", + "/scoreboard", + "/flag_stats", + "/teams", + "/events" + ] + + for endpoint in endpoints: + url = f"{SCOREBOARD_API_URL}{endpoint}" + try: + async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as resp: + data = await resp.text() + results["endpoints_tested"].append({ + "url": url, + "status": resp.status, + "reachable": resp.status == 200, + "data_preview": data[:500] if resp.status == 200 else None + }) + except Exception as e: + results["endpoints_tested"].append({ + "url": url, + "reachable": False, + "error": str(e) + }) + except Exception as e: + results["error"] = str(e) + + return results + if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8002)