63 lines
2.2 KiB
Python
63 lines
2.2 KiB
Python
"""Environment-only configuration for authenticated Agent result writeback."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import binascii
|
|
import os
|
|
from dataclasses import dataclass
|
|
from typing import Mapping, Optional
|
|
|
|
from arr_ingestion.contracts import OPAQUE_ID_RE
|
|
from arr_processing.errors import ProcessingError
|
|
from arr_processing.signatures import SignedResultCodec
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ResultVerificationConfig:
|
|
key_id: str
|
|
secret: bytes
|
|
max_age_seconds: int = 600
|
|
|
|
@classmethod
|
|
def from_environment(
|
|
cls,
|
|
environment: Optional[Mapping[str, str]] = None,
|
|
) -> "ResultVerificationConfig":
|
|
values = environment if environment is not None else os.environ
|
|
key_id = str(values.get("ARR_AGENT_RESULT_HMAC_KEY_ID", "")).strip()
|
|
encoded = str(values.get("ARR_AGENT_RESULT_HMAC_KEY_B64", "")).strip()
|
|
age_text = str(values.get("ARR_AGENT_RESULT_MAX_AGE_SECONDS", "600")).strip()
|
|
if not OPAQUE_ID_RE.fullmatch(key_id):
|
|
raise ProcessingError(
|
|
"PROCESSING_SIGNATURE_CONFIG_MISSING",
|
|
"Agent result signing key identifier is missing",
|
|
)
|
|
try:
|
|
secret = base64.b64decode(encoded, validate=True)
|
|
max_age = int(age_text)
|
|
except (binascii.Error, ValueError):
|
|
raise ProcessingError(
|
|
"PROCESSING_SIGNATURE_CONFIG_INVALID",
|
|
"Agent result signing configuration is invalid",
|
|
) from None
|
|
if len(secret) < 32 or not 1 <= max_age <= 3600:
|
|
raise ProcessingError(
|
|
"PROCESSING_SIGNATURE_CONFIG_INVALID",
|
|
"Agent result signing configuration is invalid",
|
|
)
|
|
return cls(key_id=key_id, secret=secret, max_age_seconds=max_age)
|
|
|
|
def verifier(self) -> SignedResultCodec:
|
|
return SignedResultCodec(
|
|
{self.key_id: self.secret},
|
|
max_age_seconds=self.max_age_seconds,
|
|
)
|
|
|
|
def signer(self) -> SignedResultCodec:
|
|
return SignedResultCodec(
|
|
{self.key_id: self.secret},
|
|
signing_key_id=self.key_id,
|
|
max_age_seconds=self.max_age_seconds,
|
|
)
|