fix: agent_bootstrap verweigert deprovisionierte Geräte

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 <noreply@anthropic.com>
This commit is contained in:
Thomas Stallinger 2026-08-10 13:14:49 +02:00
parent 382ec12245
commit 507bab7282

26
app.py
View File

@ -1761,13 +1761,23 @@ def get_fedora_kickstart():
def agent_bootstrap(payload: AgentBootstrapRequest): def agent_bootstrap(payload: AgentBootstrapRequest):
agent_secret = secrets.token_urlsafe(32) 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 get_database_connection() as conn:
with conn.cursor() as cur: with conn.cursor() as cur:
cur.execute( cur.execute(
""" """
UPDATE devices UPDATE devices
SET agent_secret_hash = %s, agent_secret_issued_at = %s SET agent_secret_hash = %s, agent_secret_issued_at = %s
WHERE id = %s WHERE id = %s AND deprovisioned_at IS NULL
RETURNING id RETURNING id
""", """,
( (
@ -1778,7 +1788,21 @@ def agent_bootstrap(payload: AgentBootstrapRequest):
) )
updated = cur.fetchone() 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 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 { return {
"success": False, "success": False,
"error": "device_not_found", "error": "device_not_found",