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.
This commit is contained in:
@@ -125,12 +125,54 @@ EXPORT_COLUMNS = [
|
||||
"system_manufacturer", "system_model", "system_serial",
|
||||
"drive_model", "drive_serial", "drive_capacity_gb", "drive_interface",
|
||||
"erasure_standard", "state", "start_time", "end_time", "elapsed_time",
|
||||
"total_errors", "license_consumption_id", "business_name",
|
||||
"total_errors", "failure_reason", "license_consumption_id",
|
||||
"business_name", "business_location", "cloud_upload",
|
||||
]
|
||||
|
||||
|
||||
def export_rows(filename: str, data: dict, when: str) -> list[dict]:
|
||||
"""One CSV row per erased drive; a report with no erasures still gets a row."""
|
||||
def _as_list(x):
|
||||
if x is None or x == "":
|
||||
return []
|
||||
return x if isinstance(x, list) else [x]
|
||||
|
||||
|
||||
def failure_reason(er: dict) -> str:
|
||||
"""Why an erasure did not succeed, from the report itself; "" if it did.
|
||||
|
||||
Blancco records exception messages under erasure_details (e.g.
|
||||
"NBP area content was not erased."), a state and error count per step,
|
||||
and total/read/mismatch error counts. Auditors (ENT-158/159) want the
|
||||
cause of failure next to each failed wipe.
|
||||
"""
|
||||
if (er.get("state") or "").strip().lower() == "successful":
|
||||
return ""
|
||||
parts = []
|
||||
details = er.get("erasure_details") or {}
|
||||
for exc in _as_list(details.get("exception")) + _as_list(details.get("messages")):
|
||||
msg = exc.get("message") if isinstance(exc, dict) else exc
|
||||
if msg and msg not in parts:
|
||||
parts.append(msg)
|
||||
steps = (er.get("steps") or {}).get("step")
|
||||
for i, st in enumerate(_as_list(steps), 1):
|
||||
if isinstance(st, dict) and (st.get("state") or "completed") != "completed":
|
||||
name = st.get("name") or st.get("step_name") or f"step {i}"
|
||||
parts.append(f"{name}: {st.get('state')}")
|
||||
for key in ("total_errors", "read_errors", "mismatch_errors"):
|
||||
try:
|
||||
n = int(er.get(key) or 0)
|
||||
except ValueError:
|
||||
n = 0
|
||||
if n:
|
||||
parts.append(f"{key.replace('_', ' ')}: {n}")
|
||||
return "; ".join(parts) or f"state: {er.get('state') or 'unknown'}"
|
||||
|
||||
|
||||
def export_rows(filename: str, data: dict, when: str, cloud: str = "") -> list[dict]:
|
||||
"""One CSV row per erased drive; a report with no erasures still gets a row.
|
||||
|
||||
cloud is the upload status recorded by the webapp for this report
|
||||
("sent", "failed" or "" when unknown).
|
||||
"""
|
||||
meta = data.get("meta") or {}
|
||||
system = (data.get("hardware") or {}).get("system") or {}
|
||||
if isinstance(system, list):
|
||||
@@ -145,6 +187,8 @@ def export_rows(filename: str, data: dict, when: str) -> list[dict]:
|
||||
"system_serial": system.get("serial", ""),
|
||||
"license_consumption_id": ";".join(data.get("license_consumption_ids") or []),
|
||||
"business_name": (data.get("company") or {}).get("business_name", ""),
|
||||
"business_location": (data.get("company") or {}).get("business_location", ""),
|
||||
"cloud_upload": cloud,
|
||||
}
|
||||
rows = []
|
||||
for er in data.get("erasures") or []:
|
||||
@@ -167,5 +211,6 @@ def export_rows(filename: str, data: dict, when: str) -> list[dict]:
|
||||
"end_time": er.get("end_time", ""),
|
||||
"elapsed_time": er.get("elapsed_time", ""),
|
||||
"total_errors": er.get("total_errors", ""),
|
||||
"failure_reason": failure_reason(er),
|
||||
})
|
||||
return rows or [base]
|
||||
|
||||
Reference in New Issue
Block a user