feat: add secure agent enrollment
This commit is contained in:
1 parent
82309db79a
commit
d9f0da694b
26 files changed
+2942
-76
No files matched your search
@@ -0,0 +1,66 @@
|
||||
"""Per-Agent authentication with bounded legacy compatibility."""
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
|
||||
from app.agent_auth.exceptions import (
|
||||
AgentAuthenticationFailed,
|
||||
AgentAuthenticationUnavailable,
|
||||
AgentCredentialRevoked,
|
||||
AgentIdentityUnauthorized,
|
||||
)
|
||||
from app.config import settings
|
||||
|
||||
|
||||
def hash_agent_secret(secret: str) -> str:
|
||||
"""Hash a high-entropy machine-generated Agent secret for persistence."""
|
||||
return hashlib.sha256(secret.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
class AgentAuthService:
|
||||
def __init__(self, repository):
|
||||
self.repository = repository
|
||||
|
||||
def authenticate(self, agent_id: str | None, provided_secret: str | None) -> str:
|
||||
identity = (agent_id or "").strip()
|
||||
secret = (provided_secret or "").strip()
|
||||
|
||||
if not identity:
|
||||
raise AgentAuthenticationFailed("Missing Agent identity")
|
||||
if not secret:
|
||||
raise AgentAuthenticationFailed("Invalid or missing Agent secret")
|
||||
|
||||
credential = self.repository.get_credential(identity)
|
||||
|
||||
# Any Agent with a per-Agent credential is permanently on the new
|
||||
# authentication path. The legacy shared secret must never authenticate
|
||||
# that identity, even if the Agent later becomes revoked.
|
||||
if credential and credential.get("agent_secret_hash"):
|
||||
if credential.get("credential_revoked_at") is not None:
|
||||
raise AgentCredentialRevoked("Agent credential has been revoked")
|
||||
|
||||
supplied_hash = hash_agent_secret(secret)
|
||||
if not hmac.compare_digest(
|
||||
supplied_hash,
|
||||
str(credential["agent_secret_hash"]),
|
||||
):
|
||||
raise AgentAuthenticationFailed("Invalid or missing Agent secret")
|
||||
|
||||
return identity
|
||||
|
||||
# Temporary compatibility for the single pre-enrollment Agent.
|
||||
legacy_id = settings.minerops_agent_id.strip()
|
||||
legacy_secret = settings.minerops_agent_shared_secret.strip()
|
||||
|
||||
if not legacy_id or not legacy_secret:
|
||||
raise AgentAuthenticationUnavailable(
|
||||
"Legacy Agent authentication is not configured"
|
||||
)
|
||||
|
||||
if not hmac.compare_digest(identity, legacy_id):
|
||||
raise AgentIdentityUnauthorized("Agent identity is not authorized")
|
||||
|
||||
if not hmac.compare_digest(secret, legacy_secret):
|
||||
raise AgentAuthenticationFailed("Invalid or missing Agent secret")
|
||||
|
||||
return identity
|
||||
Reference in new issue
Block a user