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:
1 parent
e898f283ab
commit
8b0d32cd6b
26 files changed
+3798
-4092
No files matched your search
@@ -42,6 +42,7 @@ class FakeRepository:
|
||||
self.agent = None
|
||||
self.saved = []
|
||||
self.assign_result = {MINER_A: "assigned"}
|
||||
self.discovery_match = None
|
||||
|
||||
def get_agent(self, agent_id):
|
||||
return self.agent if self.agent and self.agent["agent_id"] == agent_id else None
|
||||
@@ -107,6 +108,11 @@ class FakeRepository:
|
||||
],
|
||||
}
|
||||
|
||||
def find_miner_in_zone(self, *, agent_id, agent_zone_id, mac_address, ip_address):
|
||||
self.discovery_match_request = {'agent_id':agent_id,'agent_zone_id':agent_zone_id,
|
||||
'mac_address':mac_address,'ip_address':ip_address}
|
||||
return self.discovery_match
|
||||
|
||||
def assign_miners_to_zone(self, miner_ids, agent_zone_id):
|
||||
self.assigned = (list(miner_ids), agent_zone_id)
|
||||
return {miner_id: self.assign_result.get(miner_id, "miner_not_found")
|
||||
@@ -136,6 +142,7 @@ class AgentZoneModelTests(unittest.TestCase):
|
||||
class AgentZoneServiceTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.repository = FakeRepository()
|
||||
self.repository.agent = {"agent_id":"agent-a","site_id":SITE_A,"agent_zone_id":ZONE_A,"site_name":"North"}
|
||||
self.service = AgentZoneService(self.repository)
|
||||
|
||||
def checkin(self, **kwargs):
|
||||
@@ -144,14 +151,14 @@ class AgentZoneServiceTests(unittest.TestCase):
|
||||
AgentCheckinRequest(agent_id="agent-a", site_name="North", **kwargs),
|
||||
)
|
||||
|
||||
def test_explicit_first_assignment_and_same_assignment_are_allowed(self):
|
||||
def test_established_assignment_and_same_assignment_are_allowed(self):
|
||||
first = self.checkin(site_id=SITE_A, agent_zone_id=ZONE_A)
|
||||
second = self.checkin(site_id=SITE_A, agent_zone_id=ZONE_A)
|
||||
self.assertTrue(first.routing_available)
|
||||
self.assertEqual(second.agent_zone_id, ZONE_A)
|
||||
|
||||
def test_explicit_zone_site_mismatch_and_identity_spoof_are_rejected(self):
|
||||
with self.assertRaisesRegex(AgentZoneValidationError, "does not belong"):
|
||||
with self.assertRaisesRegex(AgentZoneValidationError, "cannot change Sites"):
|
||||
self.checkin(site_id=SITE_B, agent_zone_id=ZONE_A)
|
||||
with self.assertRaisesRegex(AgentZoneValidationError, "authenticated"):
|
||||
self.service.checkin(
|
||||
@@ -181,34 +188,20 @@ class AgentZoneServiceTests(unittest.TestCase):
|
||||
self.assertEqual(result.agent_zone_id, ZONE_A)
|
||||
|
||||
def test_transitional_resolution_requires_exactly_one_enabled_zone(self):
|
||||
single = self.checkin(site_id=SITE_A)
|
||||
self.assertEqual(single.agent_zone_id, ZONE_A)
|
||||
self.repository.agent = None
|
||||
self.repository.zones = {}
|
||||
zero = self.checkin(site_id=SITE_A)
|
||||
self.assertEqual(zero.routing_reason, "agent_zone_unavailable")
|
||||
self.repository.agent = None
|
||||
self.repository.zones = {
|
||||
ZONE_A: zone(),
|
||||
ZONE_B: zone(ZONE_B, SITE_A, name="Room 2"),
|
||||
}
|
||||
many = self.checkin(site_id=SITE_A)
|
||||
self.assertEqual(many.routing_reason, "agent_zone_ambiguous")
|
||||
self.assertIsNone(many.agent_zone_id)
|
||||
for zones in [{}, {ZONE_A: zone()}, {ZONE_A:zone(),ZONE_B:zone(ZONE_B,SITE_A)}]:
|
||||
self.repository.zones=zones
|
||||
self.repository.agent=None
|
||||
with self.assertRaisesRegex(AgentZoneValidationError,"administrator-established"):
|
||||
self.checkin(site_id=SITE_A)
|
||||
|
||||
def test_disabled_zone_is_not_selected_and_existing_assignment_is_factual(self):
|
||||
self.repository.zones[ZONE_A]["is_enabled"] = False
|
||||
unassigned = self.checkin(site_id=SITE_A)
|
||||
self.assertEqual(unassigned.routing_reason, "agent_zone_unavailable")
|
||||
self.repository.agent = {
|
||||
"agent_id": "agent-a", "site_id": SITE_A,
|
||||
"agent_zone_id": ZONE_A, "site_name": "North",
|
||||
}
|
||||
existing = self.service.checkin(
|
||||
"agent-a", AgentCheckinRequest(agent_id="agent-a")
|
||||
)
|
||||
self.assertEqual(existing.routing_reason, "agent_zone_disabled")
|
||||
self.assertEqual(existing.agent_zone_id, ZONE_A)
|
||||
self.repository.zones[ZONE_A]["is_enabled"]=False
|
||||
existing=self.service.checkin("agent-a",AgentCheckinRequest(agent_id="agent-a"))
|
||||
self.assertEqual(existing.routing_reason,"agent_zone_disabled")
|
||||
self.assertEqual(existing.agent_zone_id,ZONE_A)
|
||||
self.repository.agent=None
|
||||
with self.assertRaisesRegex(AgentZoneValidationError,"administrator-established"):
|
||||
self.checkin(site_id=SITE_A)
|
||||
|
||||
def test_payload_is_sanitized_before_persistence(self):
|
||||
request = AgentCheckinRequest.model_validate({
|
||||
@@ -255,9 +248,41 @@ class AgentZoneServiceTests(unittest.TestCase):
|
||||
self.assertNotIn("10.80.0.0/24", str(configuration))
|
||||
|
||||
|
||||
def test_discovery_match_uses_only_authenticated_agent_zone(self):
|
||||
self.checkin(site_id=SITE_A, agent_zone_id=ZONE_A)
|
||||
self.repository.discovery_match = {"id": MINER_A, "matched_by": "mac"}
|
||||
|
||||
result = self.service.match_discovered_miner(
|
||||
"agent-a",
|
||||
mac_address="02:00:00:00:16:21",
|
||||
ip_address="192.0.2.21",
|
||||
)
|
||||
|
||||
self.assertEqual(result.miner_id, MINER_A)
|
||||
self.assertEqual(result.matched_by, "mac")
|
||||
self.assertEqual(
|
||||
self.repository.discovery_match_request["agent_zone_id"], ZONE_A
|
||||
)
|
||||
|
||||
def test_discovery_match_rejects_stale_recorded_zone_without_querying(self):
|
||||
self.checkin(site_id=SITE_A, agent_zone_id=ZONE_A)
|
||||
|
||||
result = self.service.match_discovered_miner(
|
||||
"agent-a",
|
||||
recorded_zone_id=ZONE_B,
|
||||
mac_address="02:00:00:00:16:21",
|
||||
ip_address="192.0.2.21",
|
||||
)
|
||||
|
||||
self.assertIsNone(result.miner_id)
|
||||
self.assertEqual(result.routing_reason, "agent_zone_conflict")
|
||||
self.assertFalse(hasattr(self.repository, "discovery_match_request"))
|
||||
|
||||
|
||||
class AgentZoneRouterTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.repository = FakeRepository()
|
||||
self.repository.agent = {"agent_id":"agent-a","site_id":SITE_A,"agent_zone_id":ZONE_A,"site_name":"North"}
|
||||
self.service = AgentZoneService(self.repository)
|
||||
self.app = FastAPI()
|
||||
self.app.include_router(router)
|
||||
|
||||
Reference in new issue
Block a user