webapp: fix review findings (imaging types, NIC status, iPXE gates)

- imaging: coerce client payload and stored session fields to str/int,
  drop lists/dicts; tiles template guards pctype/current_stage; POST
  rejects non-object bodies and non-string serials.
- nic_status: normalise every check-file entry; load nic.conf as-is with
  a [nic] fallback; split interface= lists; keep the check's binding while
  fresh, resolve wildcard sockets from the dnsmasq pin (never True for an
  excluded NIC); mark vanished NICs down; include role bridges; stale or
  future-dated files downgrade to warnings and drop evidence.
- Overview renders the network pill in its own try and shows
  'Network: unknown' when status fails.
- ipxe_update: read every Authenticode signature (all WIN_CERTIFICATE
  entries plus nested SPC_NESTED_SIGNATURE) with openssl; shim swap and
  install need both Microsoft UEFI CA 2011 and 2023; install re-reads
  each file (snponly ipxe.efi only); staged.json schema version, older
  records shown stale and refused; uhci counts as native USB.
- backups describe their files; risky rollbacks need a confirm flag.
- mac_folders: guarded listdir, cached describe, configured TFTP root,
  warning for a shim without ipxe.efi.
- evidence pack: years 2000..2100, 400 days inclusive.
- a11y role=group on quick ranges; progress bar stays full during 'shim'.
- tests/test_review_fixes.py: 29 unittest cases incl. both real shims.
This commit is contained in:
cproudlock
2026-10-15 14:20:24 -04:00
parent 53d39bfc50
commit 606659bf07
11 changed files with 1185 additions and 166 deletions

View File

@@ -101,6 +101,27 @@ app.register_blueprint(images_unattend_views.bp) # Unattend review/save/undo
# Routes - pages
# ---------------------------------------------------------------------------
def _nic_parts():
"""(pill, notice) HTML for the Overview's network status. Rendered here,
each inside a try, so a bad status file or a template slip shows
'Network: unknown' and never takes the Overview down."""
from markupsafe import Markup
try:
nic = nic_status.status()
except Exception as ex: # the page must render even if this breaks
app.logger.exception("network status failed")
nic = nic_status.unknown(ex)
parts = []
for part in ("pill", "notice"):
try:
parts.append(Markup(render_template("_nic_status.html", nic=nic, part=part)))
except Exception as ex:
app.logger.exception("network status render failed")
parts.append(Markup(render_template("_nic_status.html", nic=nic_status.unknown(ex), part=part))
if part == "pill" else Markup(""))
return parts[0], parts[1]
@app.route("/")
def dashboard():
image_list = [images.image_status(it) for it in config.IMAGE_TYPES]
@@ -114,11 +135,7 @@ def dashboard():
img["os_label"], img["driver_count"] = "", 0
img["lint"] = image_pages._lint_counts(img["image_type"])
services = [system.service_status(s) for s in ("dnsmasq", "apache2", "smbd")]
try:
nic = nic_status.status()
except Exception: # the page must render even if this breaks
app.logger.exception("network status failed")
nic = None
nic_pill, nic_notice = _nic_parts()
sessions, _hidden = imaging_status.hide_stale(imaging_status.list_sessions())
floor = imaging_status.floor_order(sessions)
real = [x for x in floor if x["floor_state"] != "logs"]
@@ -127,7 +144,8 @@ def dashboard():
"dashboard.html",
images=image_list,
services=services,
nic=nic,
nic_pill=nic_pill,
nic_notice=nic_notice,
image_types=config.IMAGE_TYPES,
friendly_names=config.FRIENDLY_NAMES,
sessions=real[:6],
@@ -810,7 +828,11 @@ def blancco_evidence_pack():
return redirect(url_for("blancco_reports"))
if last < first:
first, last = last, first
if (last - first).days > 400:
if first.year < 2000 or last.year > 2100:
# 9999-12-31 + 1 day overflows the date maths further down.
flash("Pick dates between the years 2000 and 2100.", "danger")
return redirect(url_for("blancco_reports"))
if (last - first).days + 1 > 400: # both ends count
flash("An evidence pack covers at most about a year (400 days).", "danger")
return redirect(url_for("blancco_reports"))
out = tempfile.SpooledTemporaryFile(max_size=64 * 1024 * 1024)
@@ -1113,8 +1135,12 @@ def imaging_stream():
def imaging_status_post():
# CSRF-exempt machine-to-machine endpoint; see services/csrf.py exempt list.
payload = request.get_json(silent=True) or {}
if not isinstance(payload, dict):
return jsonify({"error": "body must be a JSON object"}), 400
if not payload.get("serial"):
return jsonify({"error": "missing serial"}), 400
if not isinstance(payload.get("serial"), str):
return jsonify({"error": "serial must be a string"}), 400
try:
state = imaging_status.update_session(payload)
except ValueError as ex: