This commit is contained in:
ilyastar9999
2025-12-03 10:31:37 +03:00
parent 49ccad4e37
commit 54c5dc9821
2 changed files with 126 additions and 3 deletions

View File

@@ -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}

View File

@@ -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,9 +303,18 @@ 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
# 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
# Cleanup
@@ -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)