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.
334 lines
14 KiB
Python
334 lines
14 KiB
Python
"""Focused tests for authenticated Agent queue transport."""
|
|
|
|
import unittest
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.agent_auth.service import AgentAuthService
|
|
from app.agent_commands.dependencies import (
|
|
get_agent_auth_service,
|
|
get_agent_command_service,
|
|
)
|
|
from app.agent_commands.exceptions import (
|
|
AgentCommandConflict,
|
|
AgentResultValidationError,
|
|
)
|
|
from app.agent_commands.models import AgentCommandResultRequest, MinerControlResultPayload
|
|
from app.agent_commands.repository import CLAIM_COMMANDS_SQL
|
|
from app.agent_commands.router import router
|
|
from app.agent_commands.service import AgentCommandService
|
|
from app.config import settings
|
|
|
|
|
|
NOW = datetime(2026, 8, 24, tzinfo=timezone.utc)
|
|
|
|
|
|
def agent_source_root() -> Path | None:
|
|
container_root = Path("/agents/windows")
|
|
if container_root.is_dir():
|
|
return container_root
|
|
repository_root = Path(__file__).parents[2] / "agents" / "windows"
|
|
return repository_root if repository_root.is_dir() else None
|
|
|
|
|
|
class FakeRepository:
|
|
def __init__(self, row=None):
|
|
self.row = row or {
|
|
"id": "command-1", "agent_id": "test-agent",
|
|
"command_type": "poll_known_miners_now", "command_status": "claimed",
|
|
"request_payload": {"action": "suspend"},
|
|
}
|
|
self.finished = []
|
|
|
|
def claim_pending(self, agent_id, limit=5):
|
|
return [{
|
|
"command_id": "command-1", "command_type": "poll_known_miners_now",
|
|
"request_payload": {"action": "suspend"}, "created_at": NOW,
|
|
}]
|
|
|
|
def get_command(self, command_id):
|
|
return self.row if command_id == self.row.get("id") else None
|
|
|
|
def finish_command(self, *args):
|
|
self.finished.append(args)
|
|
if args[2] == "cancelled":
|
|
raise AgentCommandConflict(args[4])
|
|
|
|
|
|
def typed_result(**overrides):
|
|
result = {
|
|
"schema_version": 1,
|
|
"command_accepted": True,
|
|
"verification_attempted": False,
|
|
"verification_status": "unconfirmed",
|
|
"observed_service_state": None,
|
|
"observed_performance_mode": None,
|
|
"failure_code": None,
|
|
}
|
|
result.update(overrides)
|
|
return result
|
|
|
|
|
|
class AgentCommandServiceTests(unittest.TestCase):
|
|
def test_claim_query_uses_skip_locked_and_deterministic_order(self):
|
|
self.assertIn("FOR UPDATE SKIP LOCKED", CLAIM_COMMANDS_SQL)
|
|
self.assertIn("ORDER BY created_at, id", CLAIM_COMMANDS_SQL)
|
|
self.assertIn("queue.command_status = 'pending'", CLAIM_COMMANDS_SQL)
|
|
|
|
def test_typed_result_is_strict_and_sanitized(self):
|
|
repository = FakeRepository()
|
|
request = AgentCommandResultRequest(
|
|
command_id="command-1", status="completed",
|
|
result_payload=typed_result(), error_message="raw 192.0.2.10 vendor exception",
|
|
)
|
|
AgentCommandService(repository).submit_result("test-agent", request)
|
|
finished = repository.finished[0]
|
|
self.assertIn("[address]", finished[4])
|
|
self.assertNotIn("192.0.2.10", str(finished))
|
|
|
|
def test_wrong_agent_pending_and_terminal_results_are_rejected(self):
|
|
request = AgentCommandResultRequest(
|
|
command_id="command-1", status="completed", result_payload=typed_result()
|
|
)
|
|
with self.assertRaises(AgentCommandConflict):
|
|
AgentCommandService(FakeRepository()).submit_result("wrong-agent", request)
|
|
for queue_status in ("pending", "completed", "failed", "cancelled"):
|
|
with self.subTest(status=queue_status), self.assertRaises(AgentCommandConflict):
|
|
row = FakeRepository().row.copy()
|
|
row["command_status"] = queue_status
|
|
AgentCommandService(FakeRepository(row)).submit_result("test-agent", request)
|
|
|
|
def test_invalid_status_and_secret_fields_are_rejected(self):
|
|
with self.assertRaises(Exception):
|
|
AgentCommandResultRequest(
|
|
command_id="command-1", status="unknown", result_payload={}
|
|
)
|
|
for forbidden in ("password", "account", "token", "raw_response"):
|
|
with self.subTest(field=forbidden):
|
|
repository = FakeRepository()
|
|
request = AgentCommandResultRequest(
|
|
command_id="command-1", status="completed",
|
|
result_payload={**typed_result(), forbidden: "sensitive"},
|
|
)
|
|
with self.assertRaises(ValueError):
|
|
MinerControlResultPayload.model_validate(request.result_payload)
|
|
|
|
def test_suspend_resume_cannot_claim_confirmation(self):
|
|
for status in ("confirmed", "verification_failed"):
|
|
with self.subTest(status=status):
|
|
request = AgentCommandResultRequest(
|
|
command_id="command-1", status="completed",
|
|
result_payload=typed_result(
|
|
verification_attempted=True, verification_status=status
|
|
),
|
|
)
|
|
repository = FakeRepository()
|
|
repository.row["command_type"] = "miner_control"
|
|
with self.assertRaisesRegex(AgentCommandConflict, "physical_control_disabled_stage_a"):
|
|
AgentCommandService(repository).submit_result("test-agent", request)
|
|
|
|
|
|
class FakeAgentAuthRepository:
|
|
def get_credential(self, agent_id):
|
|
return None
|
|
|
|
|
|
class AgentCommandRouterTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.app = FastAPI()
|
|
self.app.include_router(router)
|
|
self.repository = FakeRepository()
|
|
self.app.dependency_overrides[get_agent_command_service] = lambda: AgentCommandService(
|
|
self.repository
|
|
)
|
|
self.app.dependency_overrides[get_agent_auth_service] = lambda: AgentAuthService(
|
|
FakeAgentAuthRepository()
|
|
)
|
|
self.client = TestClient(self.app)
|
|
self.old_id = settings.minerops_agent_id
|
|
self.old_secret = settings.minerops_agent_shared_secret
|
|
settings.minerops_agent_id = "test-agent"
|
|
settings.minerops_agent_shared_secret = "test-secret"
|
|
|
|
def tearDown(self):
|
|
settings.minerops_agent_id = self.old_id
|
|
settings.minerops_agent_shared_secret = self.old_secret
|
|
self.client.close()
|
|
|
|
def headers(self, agent="test-agent"):
|
|
return {
|
|
"X-MinerOps-Agent-ID": agent,
|
|
"X-MinerOps-Agent-Secret": "test-secret",
|
|
}
|
|
|
|
def test_poll_accepts_secret_only_legacy_agent_and_ignores_query_spoofing(self):
|
|
before_claim_calls = []
|
|
self.app.state.agent_command_before_claim = lambda: before_claim_calls.append(True)
|
|
self.assertEqual(self.client.get("/api/agent/commands/pending").status_code, 401)
|
|
self.assertEqual(
|
|
self.client.get(
|
|
"/api/agent/commands/pending",
|
|
headers={"X-MinerOps-Agent-Secret": "test-secret"},
|
|
).status_code,
|
|
200,
|
|
)
|
|
self.assertEqual(
|
|
self.client.get(
|
|
"/api/agent/commands/pending?agent_id=other", headers=self.headers()
|
|
).status_code,
|
|
200,
|
|
)
|
|
# Agent polling may claim already queued commands, but must never invoke
|
|
# a legacy scheduler/materializer as a side effect.
|
|
self.assertEqual(before_claim_calls, [])
|
|
self.assertEqual(
|
|
self.client.get(
|
|
"/api/agent/commands/pending", headers=self.headers("other")
|
|
).status_code,
|
|
403,
|
|
)
|
|
|
|
def test_result_requires_strict_claimed_contract(self):
|
|
response = self.client.post(
|
|
"/api/agent/commands/result",
|
|
headers=self.headers(),
|
|
json={
|
|
"command_id": "command-1", "status": "completed",
|
|
"result_payload": typed_result(),
|
|
},
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.json()["status"], "completed")
|
|
self.assertEqual(
|
|
self.client.post(
|
|
"/api/agent/commands/result", headers=self.headers(),
|
|
json={"command_id": "command-1", "status": "other",
|
|
"result_payload": {}},
|
|
).status_code,
|
|
422,
|
|
)
|
|
|
|
|
|
class AgentSourceContractTests(unittest.TestCase):
|
|
def test_agent_typed_path_has_locked_commands_and_no_default_credentials(self):
|
|
root = agent_source_root()
|
|
if root is None:
|
|
self.skipTest("Windows Agent source is not mounted")
|
|
service = (root / "MinerOpsAgentService.ps1").read_text()
|
|
control = (root / "MinerOpsControl.ps1").read_text()
|
|
main = (Path(__file__).parents[1] / "app" / "main.py").read_text()
|
|
powershell = "\n".join(path.read_text() for path in root.glob("*.ps1"))
|
|
self.assertNotIn('DefaultValue "super"', service)
|
|
self.assertNotIn('DefaultValue "super"', powershell)
|
|
self.assertIn('"X-MinerOps-Agent-ID"', service)
|
|
self.assertIn('.agent_zone_id', service)
|
|
self.assertIn('"agent_zone_id"', (root / "config.example.json").read_text())
|
|
self.assertNotIn("?agent_id=", service)
|
|
self.assertIn("physical_control_disabled_stage_a", control)
|
|
self.assertNotIn('"set.miner.', control)
|
|
self.assertNotIn("$Payload.performance_mode", control)
|
|
self.assertNotIn("api3_account =", control)
|
|
self.assertNotIn("api3_password =", control)
|
|
for forbidden in ("raw_response", "raw_command", "raw_exception"):
|
|
with self.subTest(field=forbidden):
|
|
self.assertNotIn(forbidden, control.lower())
|
|
self.assertNotIn('"agent_secret": secret', main)
|
|
self.assertNotIn('"secret_length":', main)
|
|
self.assertIn('"secret_configured": bool(secret)', main)
|
|
|
|
def test_deployment_preserves_local_state_and_registers_established_tasks(self):
|
|
root = agent_source_root()
|
|
if root is None:
|
|
self.skipTest("Windows Agent source is not mounted")
|
|
install = (root / "install.ps1").read_text()
|
|
uninstall = (root / "uninstall.ps1").read_text()
|
|
for task in (
|
|
"MinerOpsAgent", "MinerOpsAgentTray",
|
|
"MinerOpsApi3Enrichment", "MinerOpsPoolPoller",
|
|
):
|
|
self.assertIn(task, install)
|
|
self.assertIn(task, uninstall)
|
|
for local_path in ("agent-config.json", "agent-state.json", "logs"):
|
|
self.assertNotIn(f'"{local_path}"', install)
|
|
self.assertNotIn(f'"{local_path}"', uninstall)
|
|
self.assertNotIn("agent_secret", install.lower())
|
|
self.assertNotIn("api3_password", install.lower())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|
|
|
|
|
|
class AgentTransportIsolationSourceTests(unittest.TestCase):
|
|
def test_known_miners_and_pool_snapshots_are_agent_zone_scoped(self):
|
|
from app.main import minerops_agent_known_miners, agent_pool_snapshots
|
|
from inspect import getsource
|
|
for fn,method in [(minerops_agent_known_miners,"known"),(agent_pool_snapshots,"snapshots")]:
|
|
source=getsource(fn)
|
|
self.assertIn("Depends(require_agent_identity)",source)
|
|
self.assertIn("discovery_dispatch",source)
|
|
self.assertIn("agent_id=authenticated_agent_id",source)
|
|
|
|
def test_windows_machine_pollers_send_agent_identity(self):
|
|
root = agent_source_root()
|
|
if root is None:
|
|
self.skipTest("Windows Agent source is not mounted")
|
|
|
|
for filename in (
|
|
"MinerOpsPoolPoller.ps1",
|
|
"MinerOpsApi3EnrichmentPoller.ps1",
|
|
"EnrichOneMiner.ps1",
|
|
):
|
|
with self.subTest(filename=filename):
|
|
source = (root / filename).read_text()
|
|
self.assertIn("X-MinerOps-Agent-ID", source)
|
|
self.assertIn("X-MinerOps-Agent-Secret", source)
|
|
|
|
|
|
class AgentEnrichmentIsolationSourceTests(unittest.TestCase):
|
|
def test_enrichment_routes_require_agent_identity_and_zone_scope(self):
|
|
from app import main
|
|
from inspect import getsource
|
|
for fn in [main.minerops_agent_miner_enrichment,main.minerops_agent_miner_enrichment_v2,main.minerops_agent_miner_enrichment_v3]:
|
|
source=getsource(fn)
|
|
self.assertIn("Depends(require_agent_identity)",source)
|
|
self.assertIn("discovery_dispatch",source)
|
|
self.assertIn("agent_id=authenticated_agent_id",source)
|
|
|
|
def test_api3_uses_authenticated_identity_and_never_global_agent_identity(self):
|
|
from app.main import minerops_agent_api3_enrichment
|
|
from inspect import getsource
|
|
source=getsource(minerops_agent_api3_enrichment)
|
|
self.assertIn("Depends(require_agent_identity)",source)
|
|
self.assertIn("discovery_dispatch",source)
|
|
self.assertIn("agent_id=authenticated_agent_id",source)
|
|
|
|
def test_legacy_config_remains_explicitly_legacy_only(self):
|
|
main = (Path(__file__).parents[1] / "app" / "main.py").read_text()
|
|
|
|
start = main.index('@app.get("/api/agent/config")')
|
|
end = main.index("\n@app.", start + 1)
|
|
route = main[start:end]
|
|
|
|
self.assertIn("minerops_require_agent_secret(request)", route)
|
|
self.assertIn('"site_name": "Plexus"', route)
|
|
self.assertNotIn("Depends(require_agent_identity)", route)
|
|
|
|
def test_disabled_old_command_routes_are_fixed_retirement_responses(self):
|
|
main = (Path(__file__).parents[1] / "app" / "main.py").read_text()
|
|
for decorator in (
|
|
'@app.get("/api/agent/commands/pending-disabled-old")',
|
|
'@app.post("/api/agent/commands/result-disabled-old")',
|
|
):
|
|
with self.subTest(decorator=decorator):
|
|
start = main.index(decorator)
|
|
end = main.index("\n@app.", start + 1)
|
|
route = main[start:end]
|
|
self.assertIn("status_code=410", route)
|
|
self.assertNotIn("minerops_require_agent_secret", route)
|
|
self.assertNotIn("request.json", route)
|
|
self.assertNotIn("minerops_agent_command_queue", route)
|