security(discovery): enforce Zone isolation and retire unsafe legacy writers

Bind Agent observations, review, enrichment and telemetry to current Zone
and inventory authority. Preserve immutable MAC ownership and historical
snapshots with stale-result rejection and transactional security audits.

Retire unbound direct/range/scheduled discovery and one/bulk legacy summary
writes in both modes. Require reconciled exact inventory identity or
explicit Zone-scoped IP selection for CSV metadata imports.

Independent Blocker #1 application-security re-audit: PASS on tree
cf863aadc9. Evidence: 141 focused PostgreSQL/HTTP
tests passed; Chromium and PowerShell checks passed. Two documented baseline
source assertions remain unchanged. Production deployment is not approved.
This commit is contained in:
lussierj committed 2026-10-17 00:45:04 +00:00
1 parent e898f283ab
commit 8b0d32cd6b
26 files changed
+3798 -4092

No files matched your search

+5
View File
@@ -108,6 +108,11 @@ class AgentZoneRoutingContext(BaseModel):
routing_reason: str | None = None
class AgentDiscoveryMinerMatch(AgentZoneRoutingContext):
miner_id: UUID | None = None
matched_by: str | None = None
class MinerZoneAssociationResult(BaseModel):
agent_zone_id: UUID | None = None
routing_reason: str | None = None
+19 -9
View File
@@ -204,6 +204,8 @@ class AgentZoneRepository:
ip_address = EXCLUDED.ip_address,
last_checkin_at = now(),
last_payload = EXCLUDED.last_payload
WHERE minerops_site_agents.site_id IS NOT DISTINCT FROM EXCLUDED.site_id
AND minerops_site_agents.agent_zone_id IS NOT DISTINCT FROM EXCLUDED.agent_zone_id
""",
(
agent_id,
@@ -216,6 +218,8 @@ class AgentZoneRepository:
Jsonb(payload),
),
)
if cur.rowcount != 1:
raise AgentZoneConflict("Agent assignment changed concurrently")
connection.commit()
except UniqueViolation as exc:
connection.rollback()
@@ -294,6 +298,20 @@ class AgentZoneRepository:
finally:
connection.close()
def find_miner_in_zone(self, *, agent_id: str, agent_zone_id: UUID,
mac_address: str | None, ip_address: str | None) -> dict | None:
"""Retained helper uses the same atomic authority/collision boundary as HTTP."""
from app.discovery.repository import DiscoveryRepository, reject
repository = DiscoveryRepository(self._connection_factory)
with repository.transaction() as cur:
context = repository.context(cur, agent_id)
if context['agent_zone_id'] != agent_zone_id:
reject('discovery_stale_authority')
if not mac_address and not ip_address:
return None
row = repository.match(cur, context, {'mac_address': mac_address, 'ip_address': ip_address})
return {'id': row['id'], 'matched_by': 'mac' if mac_address else 'ip'} if row else None
def assign_miners_to_zone(
self, miner_ids: Iterable[UUID], agent_zone_id: UUID
) -> dict[UUID, str]:
@@ -326,15 +344,7 @@ class AgentZoneRepository:
elif miner["site_id"] not in {None, zone["site_id"]}:
outcomes[miner_id] = "site_conflict"
else:
cur.execute(
"""
UPDATE miners
SET site_id = %s, agent_zone_id = %s, updated_at = now()
WHERE id = %s AND agent_zone_id IS NULL
""",
(zone["site_id"], agent_zone_id, miner_id),
)
outcomes[miner_id] = "assigned"
outcomes[miner_id] = "miner_zone_unassigned"
connection.commit()
return outcomes
except Exception:
+27
View File
@@ -10,6 +10,7 @@ from app.agent_zones.exceptions import (
from app.agent_zones.models import (
AgentCheckinRequest,
AgentCheckinResponse,
AgentDiscoveryMinerMatch,
AgentZoneRoutingContext,
AgentZoneCreateRequest,
AgentZoneSummary,
@@ -81,6 +82,8 @@ class AgentZoneService:
self.validate_agent_identity(authenticated_agent_id, request.agent_id)
existing = self.repository.get_agent(authenticated_agent_id)
if not existing or existing.get("agent_zone_id") is None:
raise AgentZoneValidationError("Agent requires an administrator-established Zone assignment")
requested_site = request.site_id
requested_zone = request.agent_zone_id
final_site = requested_site or (existing or {}).get("site_id")
@@ -199,6 +202,30 @@ class AgentZoneService:
],
}
def match_discovered_miner(
self,
agent_id: str,
*,
mac_address: str | None,
ip_address: str | None,
recorded_zone_id: UUID | None = None,
) -> AgentDiscoveryMinerMatch:
routing = self.routing_context(agent_id, recorded_zone_id)
if routing.routing_reason or routing.agent_zone_id is None:
return AgentDiscoveryMinerMatch(**routing.model_dump())
match = self.repository.find_miner_in_zone(
agent_id=agent_id,
agent_zone_id=routing.agent_zone_id,
mac_address=mac_address,
ip_address=ip_address,
)
return AgentDiscoveryMinerMatch(
**routing.model_dump(),
miner_id=match["id"] if match else None,
matched_by=match["matched_by"] if match else None,
)
def associate_miners_from_agent(
self, agent_id: str, miner_ids: list[UUID]
) -> MinerZoneAssociationResult: