feat(agent): add durable existing-agent credential transitions
This commit is contained in:
1 parent
abf5654a25
commit
3cfb0d1abe
33 files changed
+3828
-54
No files matched your search
@@ -4,6 +4,7 @@ import unittest
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from uuid import uuid4
|
||||
|
||||
from psycopg.errors import ForeignKeyViolation
|
||||
from psycopg.rows import dict_row
|
||||
|
||||
from app.agent_auth.exceptions import AgentAuthenticationFailed
|
||||
@@ -11,6 +12,7 @@ from app.agent_auth.repository import AgentAuthRepository
|
||||
from app.agent_auth.service import AgentAuthService, hash_agent_secret
|
||||
from app.agent_enrollment.exceptions import (
|
||||
AgentEnrollmentConflict,
|
||||
AgentEnrollmentCredentialCollision,
|
||||
AgentEnrollmentNotFound,
|
||||
AgentEnrollmentTokenInvalid,
|
||||
)
|
||||
@@ -26,6 +28,7 @@ from app.agent_enrollment.service import AgentEnrollmentService
|
||||
from app.agent_zones.models import AgentCheckinRequest
|
||||
from app.agent_zones.repository import AgentZoneRepository
|
||||
from app.agent_zones.service import AgentZoneService
|
||||
from app.config import settings
|
||||
|
||||
|
||||
class AgentEnrollmentPostgresTests(unittest.TestCase):
|
||||
@@ -322,6 +325,7 @@ class AgentEnrollmentPostgresTests(unittest.TestCase):
|
||||
agent_secret_hash,
|
||||
credential_created_at,
|
||||
credential_revoked_at,
|
||||
legacy_auth_disabled_at,
|
||||
last_payload::text AS last_payload
|
||||
FROM minerops_site_agents
|
||||
WHERE site_id = %s
|
||||
@@ -345,6 +349,16 @@ class AgentEnrollmentPostgresTests(unittest.TestCase):
|
||||
)
|
||||
enrollment_row = cur.fetchone()
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT retired_at
|
||||
FROM agent_legacy_auth_retirements
|
||||
WHERE agent_id = %s
|
||||
""",
|
||||
(agent_id,),
|
||||
)
|
||||
retirement_row = cur.fetchone()
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT new_value_json::text AS payload
|
||||
@@ -369,6 +383,15 @@ class AgentEnrollmentPostgresTests(unittest.TestCase):
|
||||
)
|
||||
self.assertIsNotNone(agent["credential_created_at"])
|
||||
self.assertIsNone(agent["credential_revoked_at"])
|
||||
self.assertEqual(
|
||||
agent["legacy_auth_disabled_at"],
|
||||
agent["credential_created_at"],
|
||||
)
|
||||
self.assertIsNotNone(retirement_row)
|
||||
self.assertEqual(
|
||||
retirement_row["retired_at"],
|
||||
agent["credential_created_at"],
|
||||
)
|
||||
|
||||
self.assertIsNotNone(enrollment_row["used_at"])
|
||||
self.assertIsNone(enrollment_row["revoked_at"])
|
||||
@@ -409,6 +432,17 @@ class AgentEnrollmentPostgresTests(unittest.TestCase):
|
||||
"definitely-not-the-issued-secret",
|
||||
)
|
||||
|
||||
original_legacy_id = settings.minerops_agent_id
|
||||
original_legacy_secret = settings.minerops_agent_shared_secret
|
||||
settings.minerops_agent_id = agent_id
|
||||
settings.minerops_agent_shared_secret = "global-secret-must-not-work"
|
||||
try:
|
||||
with self.assertRaises(AgentAuthenticationFailed):
|
||||
auth_service.authenticate(None, "global-secret-must-not-work")
|
||||
finally:
|
||||
settings.minerops_agent_id = original_legacy_id
|
||||
settings.minerops_agent_shared_secret = original_legacy_secret
|
||||
|
||||
# Normal check-in must never mutate the credential.
|
||||
secret_hash_before = agent["agent_secret_hash"]
|
||||
|
||||
@@ -433,7 +467,8 @@ class AgentEnrollmentPostgresTests(unittest.TestCase):
|
||||
SELECT
|
||||
agent_secret_hash,
|
||||
credential_created_at,
|
||||
credential_revoked_at
|
||||
credential_revoked_at,
|
||||
legacy_auth_disabled_at
|
||||
FROM minerops_site_agents
|
||||
WHERE agent_id = %s
|
||||
""",
|
||||
@@ -453,6 +488,150 @@ class AgentEnrollmentPostgresTests(unittest.TestCase):
|
||||
self.assertIsNone(
|
||||
after_checkin["credential_revoked_at"]
|
||||
)
|
||||
self.assertEqual(
|
||||
after_checkin["legacy_auth_disabled_at"],
|
||||
agent["legacy_auth_disabled_at"],
|
||||
)
|
||||
|
||||
# 0017 enrollment integrity remains RESTRICT: the current Agent row
|
||||
# cannot be deleted while its enrollment relationship exists.
|
||||
connection = default_connection_factory()
|
||||
try:
|
||||
with self.assertRaises(ForeignKeyViolation):
|
||||
with connection.cursor() as cur:
|
||||
cur.execute(
|
||||
"DELETE FROM minerops_site_agents WHERE agent_id = %s",
|
||||
(agent_id,),
|
||||
)
|
||||
connection.rollback()
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
# After an operator deliberately removes the enrollment history, the
|
||||
# independent retirement ledger must outlive deletion and recreation
|
||||
# of the current Agent row with the identical identity.
|
||||
connection = default_connection_factory()
|
||||
try:
|
||||
with connection.cursor() as cur:
|
||||
cur.execute(
|
||||
"DELETE FROM agent_enrollments WHERE id = %s",
|
||||
(enrollment["id"],),
|
||||
)
|
||||
cur.execute(
|
||||
"DELETE FROM minerops_site_agents WHERE agent_id = %s",
|
||||
(agent_id,),
|
||||
)
|
||||
connection.commit()
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
original_legacy_id = settings.minerops_agent_id
|
||||
original_legacy_secret = settings.minerops_agent_shared_secret
|
||||
settings.minerops_agent_id = agent_id
|
||||
settings.minerops_agent_shared_secret = "recreation-global-secret"
|
||||
try:
|
||||
with self.assertRaises(AgentAuthenticationFailed):
|
||||
auth_service.authenticate(None, "recreation-global-secret")
|
||||
|
||||
connection = default_connection_factory()
|
||||
try:
|
||||
with connection.cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO minerops_site_agents (
|
||||
agent_id,
|
||||
site_id,
|
||||
agent_zone_id,
|
||||
site_name
|
||||
)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
RETURNING legacy_auth_disabled_at
|
||||
""",
|
||||
(
|
||||
agent_id,
|
||||
self.site_id,
|
||||
self.zone_id,
|
||||
self.site_name,
|
||||
),
|
||||
)
|
||||
recreated = cur.fetchone()
|
||||
connection.commit()
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
self.assertEqual(
|
||||
recreated["legacy_auth_disabled_at"],
|
||||
retirement_row["retired_at"],
|
||||
)
|
||||
with self.assertRaises(AgentAuthenticationFailed):
|
||||
auth_service.authenticate(agent_id, "recreation-global-secret")
|
||||
finally:
|
||||
settings.minerops_agent_id = original_legacy_id
|
||||
settings.minerops_agent_shared_secret = original_legacy_secret
|
||||
|
||||
def test_failed_credential_issuance_rolls_back_new_retirement(self):
|
||||
enrollment = self.create_enrollment("Collision Agent")
|
||||
collision_id = f"agent-collision-{uuid4().hex}"
|
||||
|
||||
connection = default_connection_factory()
|
||||
try:
|
||||
with connection.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO minerops_site_agents (agent_id, site_name)
|
||||
VALUES (%s, 'Existing uncredentialed identity')
|
||||
""",
|
||||
(collision_id,),
|
||||
)
|
||||
connection.commit()
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
try:
|
||||
with self.assertRaises(AgentEnrollmentCredentialCollision):
|
||||
AgentEnrollmentRepository().redeem_enrollment(
|
||||
token_hash=hash_agent_secret(enrollment["enrollment_token"]),
|
||||
agent_id=collision_id,
|
||||
agent_secret_hash=hash_agent_secret("never-issued-secret"),
|
||||
)
|
||||
|
||||
connection = default_connection_factory()
|
||||
try:
|
||||
with connection.cursor(row_factory=dict_row) as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT count(*) AS count
|
||||
FROM agent_legacy_auth_retirements
|
||||
WHERE agent_id = %s
|
||||
""",
|
||||
(collision_id,),
|
||||
)
|
||||
self.assertEqual(cur.fetchone()["count"], 0)
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT used_at, enrolled_agent_id
|
||||
FROM agent_enrollments
|
||||
WHERE id = %s
|
||||
""",
|
||||
(enrollment["id"],),
|
||||
)
|
||||
enrollment_row = cur.fetchone()
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
self.assertIsNone(enrollment_row["used_at"])
|
||||
self.assertIsNone(enrollment_row["enrolled_agent_id"])
|
||||
finally:
|
||||
connection = default_connection_factory()
|
||||
try:
|
||||
with connection.cursor() as cur:
|
||||
cur.execute(
|
||||
"DELETE FROM minerops_site_agents WHERE agent_id = %s",
|
||||
(collision_id,),
|
||||
)
|
||||
connection.commit()
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
def test_expired_token_is_rejected_without_side_effects(self):
|
||||
enrollment = self.create_enrollment()
|
||||
|
||||
Reference in new issue
Block a user