From 507bab7282bce681aa71c1c5a4088301eb77eeb5 Mon Sep 17 00:00:00 2001 From: Thomas Stallinger Date: Mon, 10 Aug 2026 13:14:49 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20agent=5Fbootstrap=20verweigert=20deprovi?= =?UTF-8?q?sionierte=20Ger=C3=A4te?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ohne dieses Gate würde der neue Self-Heal-Re-Bootstrap im Agenten (siehe provisioning-agent) die De-Provisionierung wirkungslos machen: der Agent hätte sich bei jedem 401 auf checkin einfach selbst ein neues Secret geholt, ganz ohne dass jemand 'erneut provisionieren' ausgelöst hat. reprovision_device() (löscht deprovisioned_at) ist jetzt die tatsächliche Voraussetzung dafür, dass ein erneuter Bootstrap-Aufruf erfolgreich ist. Co-Authored-By: Claude Sonnet 5 --- app.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index 5569f33..32388e6 100644 --- a/app.py +++ b/app.py @@ -1761,13 +1761,23 @@ def get_fedora_kickstart(): def agent_bootstrap(payload: AgentBootstrapRequest): agent_secret = secrets.token_urlsafe(32) + # deprovisioned_at wird hier bewusst mitgeprueft: der Agent ruft diesen + # Endpoint auch selbststaendig bei 401 auf checkin auf (siehe agent.py + # Self-Heal). Ohne dieses Gate wuerde ein per Kundenportal + # deprovisioniertes Geraet sich beim naechsten Poll-Zyklus einfach + # selbst ein neues Secret holen und die De-Provisionierung waere + # wirkungslos - das Gate macht reprovision_device() (Loeschen von + # deprovisioned_at) zur tatsaechlichen Voraussetzung fuer den Erfolg + # dieses Aufrufs. + existing = None + with get_database_connection() as conn: with conn.cursor() as cur: cur.execute( """ UPDATE devices SET agent_secret_hash = %s, agent_secret_issued_at = %s - WHERE id = %s + WHERE id = %s AND deprovisioned_at IS NULL RETURNING id """, ( @@ -1778,7 +1788,21 @@ def agent_bootstrap(payload: AgentBootstrapRequest): ) updated = cur.fetchone() + if updated is None: + cur.execute( + "SELECT deprovisioned_at FROM devices WHERE id = %s", + (payload.device_id,), + ) + existing = cur.fetchone() + if updated is None: + if existing is not None and existing[0] is not None: + return { + "success": False, + "error": "device_deprovisioned", + "message": "Das Gerät ist deprovisioniert und muss zuerst über das Kundenportal reprovisioniert werden.", + } + return { "success": False, "error": "device_not_found",