Files
pxe-server/webapp/services/csrf.py
cproudlock 44a7eeae95 blancco: CMMC evidence pack, ServiceNow tick list, cloud-upload status
Automates the Blancco parts of the onsite-support CMMC playbook:

- ENT-056/158 evidence pack: /reports/evidence?month=YYYY-MM returns
  ENT-056_<Site>_<YYYY-MM>.zip (summary.csv, README, each report's XML
  and PDF). pxe-blancco-evidence.timer saves last month's pack to the
  reports share's evidence/ folder on the 1st (Persistent).
- ENT-045/159 ServiceNow tick list: /reports/servicenow lists recent
  reports as "Wiped" or "Blancco Wipe failed" with a pre-written
  destruction note, copy buttons and Done (CSRF-protected).
- Cloud upload: blancco-progress.py posts each report's upload result
  ("Sending report document id" -> "Report sent to MC") to
  /reports/cloud-status (CSRF-exempt like /imaging/status); the Reports
  page and the CSVs show it, so a wipe missing from BMP is visible.
- CSV gains failure_reason (exception messages, failed steps, error
  counts), business_location and cloud_upload.
- Preferences: business_location "West Jefferson" (config.BLANCCO_SITE)
  and pdf+xml reports; the report copier now copies the PDF too, named
  after its XML. .part and hidden files are not listed as reports.

State (cloud confirmations, ServiceNow ticks) is a JSON file at
/var/lib/pxe-webapp/blancco-state.json, off the report share.
2026-10-05 13:43:16 -04:00

39 lines
1.2 KiB
Python

"""Session-based CSRF token: generate per-session, double-submit on POST."""
import secrets
from flask import abort, request, session
# Endpoint paths exempt from CSRF (machine-to-machine POSTs from PXE clients
# that have no browser session to carry a token). Air-gapped LAN; treated as
# trust-by-network. Keep the list short and explicit.
CSRF_EXEMPT_PATHS = {
"/imaging/status",
"/reports/cloud-status",
}
def generate_csrf_token():
"""Return the CSRF token for the current session, creating one if needed."""
if "_csrf_token" not in session:
session["_csrf_token"] = secrets.token_hex(32)
return session["_csrf_token"]
def init_csrf(app):
"""Wire CSRF protection into a Flask app: validator + template helper."""
@app.before_request
def _validate_csrf():
if request.method != "POST":
return
if request.path in CSRF_EXEMPT_PATHS:
return
token = request.form.get("_csrf_token") or request.headers.get("X-CSRF-Token")
if not token or token != generate_csrf_token():
abort(403)
@app.context_processor
def _inject_csrf_token():
return {"csrf_token": generate_csrf_token}