Files
minerops/backend/app/agent_enrollment/service.py
T

135 lines
4.0 KiB
Python

"""Application service for secure one-time Agent enrollment."""
import secrets
from uuid import UUID, uuid4
from app.agent_auth.service import hash_agent_secret
from app.agent_enrollment.exceptions import (
AgentEnrollmentConflict,
AgentEnrollmentCredentialCollision,
)
from app.agent_enrollment.models import (
AgentCredentialTransitionCreateRequest,
AgentCredentialTransitionRedeemRequest,
AgentEnrollmentCreateRequest,
AgentEnrollRequest,
)
MAX_GENERATION_ATTEMPTS = 3
def generate_enrollment_token() -> str:
return secrets.token_urlsafe(32)
def generate_agent_secret() -> str:
return secrets.token_urlsafe(32)
def generate_agent_id() -> str:
return f"agent-{uuid4().hex}"
class AgentEnrollmentService:
def __init__(self, repository):
self.repository = repository
def create_enrollment(
self,
request: AgentEnrollmentCreateRequest,
*,
actor_user_id: UUID,
) -> dict:
for _ in range(MAX_GENERATION_ATTEMPTS):
token = generate_enrollment_token()
try:
enrollment = self.repository.create_enrollment(
token_hash=hash_agent_secret(token),
site_id=request.site_id,
agent_zone_id=request.agent_zone_id,
display_name=request.display_name,
actor_user_id=actor_user_id,
)
return {
**enrollment,
"enrollment_token": token,
}
except AgentEnrollmentCredentialCollision:
continue
raise AgentEnrollmentConflict(
"Unable to generate a unique enrollment token"
)
def list_enrollments(
self,
*,
site_id: UUID | None = None,
agent_zone_id: UUID | None = None,
limit: int = 100,
) -> list[dict]:
return self.repository.list_enrollments(
site_id=site_id,
agent_zone_id=agent_zone_id,
limit=limit,
)
def create_credential_transition(
self,
request: AgentCredentialTransitionCreateRequest,
*,
actor_user_id: UUID,
) -> dict:
for _ in range(MAX_GENERATION_ATTEMPTS):
token = generate_enrollment_token()
try:
transition = self.repository.create_credential_transition(
token_hash=hash_agent_secret(token),
target_agent_id=request.agent_id,
actor_user_id=actor_user_id,
)
return {
**transition,
"transition_token": token,
}
except AgentEnrollmentCredentialCollision:
continue
raise AgentEnrollmentConflict(
"Unable to generate a unique credential transition token"
)
def redeem(self, request: AgentEnrollRequest) -> dict:
token_hash = hash_agent_secret(request.token)
for _ in range(MAX_GENERATION_ATTEMPTS):
agent_id = generate_agent_id()
agent_secret = generate_agent_secret()
try:
result = self.repository.redeem_enrollment(
token_hash=token_hash,
agent_id=agent_id,
agent_secret_hash=hash_agent_secret(agent_secret),
)
return {
**result,
"agent_secret": agent_secret,
}
except AgentEnrollmentCredentialCollision:
continue
raise AgentEnrollmentConflict(
"Unable to generate unique Agent credentials"
)
def redeem_credential_transition(
self,
request: AgentCredentialTransitionRedeemRequest,
) -> dict:
return self.repository.redeem_credential_transition(
token_hash=hash_agent_secret(request.token),
agent_secret_hash=request.agent_secret_hash,
)