feat(golden-images): unauthentifizierte Hosting-Route fuer Golden Images

Analog zur bestehenden /installers/fedora-workstation/ks.cfg-Route:
GET /golden-images/{filename} liefert eine Datei aus data/golden-images/
(Env-Override TUXFLOTTE_GOLDEN_IMAGES_DIR), ohne Auth-Token - das
Zielgeraet hat beim Laden des Golden Image in backend_launch() noch
keins. Kein personalisierter Inhalt (anders als die ISO-Build-Downloads),
deshalb bewusst keine Authentifizierung noetig. Path-Traversal ueber den
Dateinamen wird per Path(...).name-Vergleich abgelehnt (400).

Vorbereitung fuer ADR-0024 Phase 3 (backends/mint-image/backend.sh
GOLDEN_IMAGE_URL zeigt als naechstes hierher).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Thomas Stallinger 2026-08-31 09:56:15 +02:00
parent e88e0da0da
commit 0acb1b6b81

24
app.py
View File

@ -15,7 +15,7 @@ from uuid import UUID, uuid4
import psycopg
from psycopg.types.json import Jsonb
from fastapi import FastAPI, Header, Request
from fastapi import FastAPI, Header, HTTPException, Request
from pydantic import BaseModel
from fastapi.responses import FileResponse
@ -54,6 +54,12 @@ MINT_ISO_PATH = str(Path(os.environ.get("TUXFLOTTE_MINT_ISO_PATH", "data/upstrea
# zum Subprocess-cwd (TUXFLOTTE_INSTALLER_DIR) interpretiert, nicht relativ
# zum eigenen Arbeitsverzeichnis.
ISO_BUILD_OUTPUT_DIR = Path(os.environ.get("TUXFLOTTE_ISO_BUILD_OUTPUT_DIR", "data/iso-builds")).resolve()
# Golden Images (ADR-0024): bewusst unauthentifiziert, analog zu
# /installers/fedora-workstation/ks.cfg - werden von backend_launch()
# per curl geladen, bevor das Zielgeraet irgendein Token besitzt. Kein
# personalisierter/kundenspezifischer Inhalt (anders als ISO-Builds oben),
# nur ein generisches Basis-Root-Dateisystem pro Distribution.
GOLDEN_IMAGES_DIR = Path(os.environ.get("TUXFLOTTE_GOLDEN_IMAGES_DIR", "data/golden-images")).resolve()
ISO_BUILD_TIMEOUT_SECONDS = 1800
# ISO-Ablauf nach 7 Tagen (Migration 0021): ergaenzt die bestehende
# "neuer Build ersetzt alten"-Aufraeumung um eine reine Zeitdimension.
@ -2577,6 +2583,22 @@ def get_fedora_kickstart():
)
@app.get("/golden-images/{filename}")
def get_golden_image(filename: str):
# Path-Traversal-Schutz: Path(...).name kappt jeden "../"-Anteil, der
# Vergleich mit dem Original-Parameter erkennt den Versuch und lehnt ihn
# ab, statt ihn still auf einen anderen Dateinamen umzubiegen.
safe_name = Path(filename).name
if safe_name != filename:
raise HTTPException(status_code=400, detail="invalid_filename")
file_path = GOLDEN_IMAGES_DIR / safe_name
if not file_path.is_file():
raise HTTPException(status_code=404, detail="not_found")
return FileResponse(file_path, media_type="application/zstd")
@app.post("/api/v1/agent/bootstrap")
def agent_bootstrap(payload: AgentBootstrapRequest):
agent_secret = secrets.token_urlsafe(32)