feat: prepare ARR for controlled public deployment
This commit is contained in:
62
arr_processing/config.py
Normal file
62
arr_processing/config.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""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,
|
||||
)
|
||||
Reference in New Issue
Block a user