Files
oc-sentinel/infra/debug-dashboard/app.py
OfficeCom Codex 6ceb29a07b
Some checks failed
OfficeCom Sentinel Client / validate-client (push) Successful in 23s
OfficeCom Sentinel Client / build-client-windows (push) Failing after 18s
Add read-only OCSentinel debug dashboard
2026-07-26 02:59:51 +02:00

110 lines
3.5 KiB
Python

import hmac
import json
import os
from functools import wraps
import psycopg
from flask import Flask, Response, abort, render_template
app = Flask(__name__)
def db_connection():
return psycopg.connect(
host=os.environ["DB_HOST"],
port=os.getenv("DB_PORT", "5432"),
dbname=os.environ["DB_NAME"],
user=os.environ["DB_USER"],
password=os.environ["DB_PASSWORD"],
connect_timeout=5,
)
def requires_auth(view):
@wraps(view)
def wrapped(*args, **kwargs):
auth = __import__("flask").request.authorization
expected_user = os.environ["DASHBOARD_USER"]
expected_password = os.environ["DASHBOARD_PASSWORD"]
valid = auth and hmac.compare_digest(auth.username or "", expected_user) and hmac.compare_digest(auth.password or "", expected_password)
if not valid:
return Response("Authentication required", 401, {"WWW-Authenticate": 'Basic realm="OCSentinel Debug"'})
return view(*args, **kwargs)
return wrapped
@app.get("/")
@requires_auth
def overview():
with db_connection() as connection, connection.cursor() as cursor:
cursor.execute("SELECT * FROM ocsentinel.organization_summary")
summary = cursor.fetchone()
cursor.execute(
"""
SELECT machine_name, organization_name, received_at, alert_state,
total_events, unique_ip_count, cve_total, cve_critical
FROM (
SELECT machine_name, received_at, alert_state, total_events,
unique_ip_count, cve_total, cve_critical,
payload #>> '{NinjaOne,OrganizationName}' AS organization_name
FROM ocsentinel.current_device_status
) AS status
ORDER BY received_at DESC NULLS LAST
LIMIT 100
"""
)
reports = cursor.fetchall()
cursor.execute(
"""
SELECT machine_name, alert_state, total_events, unique_ip_count,
received_at, payload
FROM ocsentinel.current_device_status
WHERE alert_state IN ('warning', 'critical')
ORDER BY CASE alert_state WHEN 'critical' THEN 0 ELSE 1 END, received_at DESC
"""
)
alerts = cursor.fetchall()
return render_template("overview.html", summary=summary, reports=reports, alerts=alerts)
@app.get("/device/<machine_name>")
@requires_auth
def device(machine_name):
with db_connection() as connection, connection.cursor() as cursor:
cursor.execute(
"""
SELECT machine_name, first_seen_at, last_seen_at, last_client_version,
generated_at_utc, received_at, alert_state, base_alert_state,
total_events, unique_ip_count, cve_total, cve_critical, payload
FROM ocsentinel.current_device_status
WHERE machine_name = %s
""",
(machine_name,),
)
report = cursor.fetchone()
if report is None:
abort(404)
payload = report[12]
return render_template("device.html", report=report, payload=payload, payload_pretty=json.dumps(payload, indent=2, ensure_ascii=False))
@app.get("/healthz")
def healthz():
try:
with db_connection() as connection, connection.cursor() as cursor:
cursor.execute("SELECT 1")
return {"status": "ok"}
except Exception:
return {"status": "unavailable"}, 503
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)