SHA256-Pruefsumme fuer ISO-Builds
Siehe Fund #7 im heutigen Self-Service-Flow-Test (Memory). Migration 0023 (iso_builds.sha256), compute_file_sha256() haest die Datei einmalig direkt nach erfolgreichem Bau (gestreamt, nicht komplett im Speicher - ISOs sind mehrere GB), update_iso_build() nimmt den Wert jetzt entgegen. Ermoeglicht Integritaetspruefung unabhaengig vom Kundenplattform-Download-Proxy-Pfad. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
2f6ff909f9
commit
e88e0da0da
33
app.py
33
app.py
@ -2141,7 +2141,7 @@ def recharge_enrollment_session(session_id: str, additional_devices: int, expire
|
|||||||
|
|
||||||
def iso_build_row_to_dict(row) -> dict:
|
def iso_build_row_to_dict(row) -> dict:
|
||||||
(build_id, organization_id, status, output_filename, error_message,
|
(build_id, organization_id, status, output_filename, error_message,
|
||||||
created_at, started_at, finished_at, expires_at) = row
|
created_at, started_at, finished_at, expires_at, sha256) = row
|
||||||
return {
|
return {
|
||||||
"id": str(build_id),
|
"id": str(build_id),
|
||||||
"organization_id": str(organization_id),
|
"organization_id": str(organization_id),
|
||||||
@ -2152,6 +2152,7 @@ def iso_build_row_to_dict(row) -> dict:
|
|||||||
"started_at": started_at.isoformat() if started_at else None,
|
"started_at": started_at.isoformat() if started_at else None,
|
||||||
"finished_at": finished_at.isoformat() if finished_at else None,
|
"finished_at": finished_at.isoformat() if finished_at else None,
|
||||||
"expires_at": expires_at.isoformat() if expires_at else None,
|
"expires_at": expires_at.isoformat() if expires_at else None,
|
||||||
|
"sha256": sha256,
|
||||||
}
|
}
|
||||||
|
|
||||||
def fetch_iso_build(build_id: str):
|
def fetch_iso_build(build_id: str):
|
||||||
@ -2160,7 +2161,7 @@ def fetch_iso_build(build_id: str):
|
|||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
SELECT id, organization_id, status, output_filename, error_message,
|
SELECT id, organization_id, status, output_filename, error_message,
|
||||||
created_at, started_at, finished_at, expires_at
|
created_at, started_at, finished_at, expires_at, sha256
|
||||||
FROM iso_builds
|
FROM iso_builds
|
||||||
WHERE id = %s
|
WHERE id = %s
|
||||||
""",
|
""",
|
||||||
@ -2175,7 +2176,7 @@ def fetch_iso_builds(organization_id: str):
|
|||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
SELECT id, organization_id, status, output_filename, error_message,
|
SELECT id, organization_id, status, output_filename, error_message,
|
||||||
created_at, started_at, finished_at, expires_at
|
created_at, started_at, finished_at, expires_at, sha256
|
||||||
FROM iso_builds
|
FROM iso_builds
|
||||||
WHERE organization_id = %s
|
WHERE organization_id = %s
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
@ -2201,6 +2202,7 @@ def update_iso_build(
|
|||||||
status: str,
|
status: str,
|
||||||
output_filename: str | None = None,
|
output_filename: str | None = None,
|
||||||
error_message: str | None = None,
|
error_message: str | None = None,
|
||||||
|
sha256: str | None = None,
|
||||||
mark_started: bool = False,
|
mark_started: bool = False,
|
||||||
mark_finished: bool = False,
|
mark_finished: bool = False,
|
||||||
):
|
):
|
||||||
@ -2217,13 +2219,14 @@ def update_iso_build(
|
|||||||
UPDATE iso_builds
|
UPDATE iso_builds
|
||||||
SET status = %s,
|
SET status = %s,
|
||||||
output_filename = COALESCE(%s, output_filename),
|
output_filename = COALESCE(%s, output_filename),
|
||||||
error_message = %s
|
error_message = %s,
|
||||||
|
sha256 = COALESCE(%s, sha256)
|
||||||
{", started_at = CURRENT_TIMESTAMP" if mark_started else ""}
|
{", started_at = CURRENT_TIMESTAMP" if mark_started else ""}
|
||||||
{", finished_at = CURRENT_TIMESTAMP" if mark_finished else ""}
|
{", finished_at = CURRENT_TIMESTAMP" if mark_finished else ""}
|
||||||
{f", expires_at = CURRENT_TIMESTAMP + INTERVAL '{ISO_BUILD_EXPIRY_DAYS} days'" if setzt_expiry else ""}
|
{f", expires_at = CURRENT_TIMESTAMP + INTERVAL '{ISO_BUILD_EXPIRY_DAYS} days'" if setzt_expiry else ""}
|
||||||
WHERE id = %s
|
WHERE id = %s
|
||||||
""",
|
""",
|
||||||
(status, output_filename, error_message, build_id),
|
(status, output_filename, error_message, sha256, build_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
def cleanup_old_iso_builds(organization_id: str, keep_build_id) -> None:
|
def cleanup_old_iso_builds(organization_id: str, keep_build_id) -> None:
|
||||||
@ -2331,6 +2334,20 @@ def compute_iso_volid(organization_name: str) -> str:
|
|||||||
return f"{sanitized}_{date_suffix}"
|
return f"{sanitized}_{date_suffix}"
|
||||||
|
|
||||||
|
|
||||||
|
def compute_file_sha256(path) -> str:
|
||||||
|
"""
|
||||||
|
Streamt die Datei in 1-MB-Bloecken statt sie komplett in den Speicher zu
|
||||||
|
laden (ISOs sind mehrere GB gross) - laeuft im selben Background-Thread
|
||||||
|
wie der Bau selbst, blockiert also keinen HTTP-Request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
for block in iter(lambda: f.read(1024 * 1024), b""):
|
||||||
|
hasher.update(block)
|
||||||
|
|
||||||
|
return hasher.hexdigest()
|
||||||
|
|
||||||
def run_iso_build(build_id, organization_id: str, activation_code: str, wifi_ssid: str | None, wifi_psk: str | None) -> None:
|
def run_iso_build(build_id, organization_id: str, activation_code: str, wifi_ssid: str | None, wifi_psk: str | None) -> None:
|
||||||
output_path = ISO_BUILD_OUTPUT_DIR / f"{build_id}.iso"
|
output_path = ISO_BUILD_OUTPUT_DIR / f"{build_id}.iso"
|
||||||
|
|
||||||
@ -2371,7 +2388,11 @@ def run_iso_build(build_id, organization_id: str, activation_code: str, wifi_ssi
|
|||||||
update_iso_build(build_id, status="failed", error_message=error_tail, mark_finished=True)
|
update_iso_build(build_id, status="failed", error_message=error_tail, mark_finished=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
update_iso_build(build_id, status="completed", output_filename=output_path.name, mark_finished=True)
|
sha256 = compute_file_sha256(output_path)
|
||||||
|
|
||||||
|
update_iso_build(
|
||||||
|
build_id, status="completed", output_filename=output_path.name, sha256=sha256, mark_finished=True
|
||||||
|
)
|
||||||
cleanup_old_iso_builds(organization_id, build_id)
|
cleanup_old_iso_builds(organization_id, build_id)
|
||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
|
|||||||
6
migrations/0023_iso_build_sha256.sql
Normal file
6
migrations/0023_iso_build_sha256.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
-- SHA256-Pruefsumme fuer heruntergeladene ISOs (28.08.2026-Fund, Self-
|
||||||
|
-- Service-Flow-Test): Download laeuft ueber den Kundenplattform-Proxy zu
|
||||||
|
-- anode, ein Hash zur Integritaetspruefung unabhaengig von diesem Pfad war
|
||||||
|
-- bislang nicht vorhanden. Einmalig direkt nach erfolgreichem Bau berechnet
|
||||||
|
-- (nicht bei jedem Seitenaufruf - mehrere GB Datei).
|
||||||
|
ALTER TABLE iso_builds ADD COLUMN sha256 TEXT;
|
||||||
Loading…
x
Reference in New Issue
Block a user