Reject revokes the node's MQTT login and API key (server-26#195)
reject_node only set approval_status, so an approved-then-rejected node kept its dynsec login, its /upload key and /systems read access. - reject_node: status -> rejected first (fail-closed), then dynsec.delete_node_client, then delete node_keys. dynsec failure -> 502, key kept, retry is idempotent. Re-approve mints a fresh key. - upload.py: valid key alone is not enough; node must be approved (403), uncached read so revocation bites immediately. - dynsec.reconcile_all: deletes rejected nodes' logins on startup, healing nodes rejected before this change or whose revoke failed. - Reject confirm text says it revokes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d57738a8f6
commit
73147803ea
@@ -312,7 +312,8 @@ async def delete_node_client(node_id: str) -> None:
|
||||
async def reconcile_all() -> None:
|
||||
"""
|
||||
Rebuild dynsec state for every approved node from Firestore
|
||||
(node_keys is the source of truth). Called once at c2-core startup,
|
||||
(node_keys is the source of truth), and delete the login of every
|
||||
rejected node. Called once at c2-core startup,
|
||||
after ensure_roles_and_c2core_grant(). Self-heals a lost/corrupted
|
||||
dynamic-security.json (e.g. volume wiped, or a prior approve/reissue's
|
||||
dynsec publish silently failed to persist for some other reason) —
|
||||
@@ -320,6 +321,20 @@ async def reconcile_all() -> None:
|
||||
dynsec store would lock out every previously-approved node until
|
||||
someone noticed and manually re-approved each one.
|
||||
"""
|
||||
# server-26#195: revoke first. A node rejected before reject_node()
|
||||
# revoked anything (or whose revoke hit a dynsec failure) still has a
|
||||
# broker login in dynamic-security.json; startup is where that heals.
|
||||
# delete_node_client() treats "not found" as success, so this is a no-op
|
||||
# for every node already revoked.
|
||||
for node in await fstore.collection_list("nodes", approval_status="rejected"):
|
||||
node_id = node.get("node_id")
|
||||
if not node_id:
|
||||
continue
|
||||
try:
|
||||
await delete_node_client(node_id)
|
||||
except DynsecError as e:
|
||||
logger.error(f"dynsec reconcile: failed to revoke rejected node {node_id!r}: {e}")
|
||||
|
||||
nodes = await fstore.collection_list("nodes", approval_status="approved")
|
||||
if not nodes:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user