149 lines
4.6 KiB
Python
149 lines
4.6 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("/reports")
|
|
@requires_auth
|
|
def reports():
|
|
with db_connection() as connection, connection.cursor() as cursor:
|
|
cursor.execute(
|
|
"""
|
|
SELECT id, organization_name, period_start_utc, period_end_utc,
|
|
generated_at, device_count, warning_count, critical_count,
|
|
total_events
|
|
FROM ocsentinel.weekly_organization_report
|
|
ORDER BY period_end_utc DESC, organization_name
|
|
"""
|
|
)
|
|
weekly_reports = cursor.fetchall()
|
|
|
|
return render_template("reports.html", reports=weekly_reports)
|
|
|
|
|
|
@app.get("/reports/<int:report_id>")
|
|
@requires_auth
|
|
def weekly_report(report_id):
|
|
with db_connection() as connection, connection.cursor() as cursor:
|
|
cursor.execute(
|
|
"""
|
|
SELECT organization_name, period_start_utc, period_end_utc,
|
|
generated_at, report_html
|
|
FROM ocsentinel.weekly_organization_report
|
|
WHERE id = %s
|
|
""",
|
|
(report_id,),
|
|
)
|
|
report = cursor.fetchone()
|
|
|
|
if report is None:
|
|
abort(404)
|
|
|
|
return render_template("weekly_report.html", report=report)
|
|
|
|
|
|
@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)
|