99 lines
2.7 KiB
Python
99 lines
2.7 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 (
|
|
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 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"
|
|
)
|