98 lines
4 KiB
Python
98 lines
4 KiB
Python
"""Configuration model for the aissurance compliance plugin.
|
|
|
|
Populated from the ``compliance`` block of the Router's ``config.yaml`` (see
|
|
config.Config). ``${VAR}`` references in the YAML are already expanded by
|
|
``Config._expand_env_refs`` before this model is constructed; on top of that,
|
|
the aissurance-specific environment variables (``AISSURANCE_KEY`` etc.) are
|
|
honoured as defaults so the plugin works with env-only configuration too.
|
|
|
|
This module imports nothing from the Router, so config.py can import it
|
|
without creating a cycle.
|
|
"""
|
|
import os
|
|
import socket
|
|
from typing import Optional
|
|
|
|
from pydantic import BaseModel, Field, field_validator
|
|
|
|
|
|
def _env_or_none(name: str) -> Optional[str]:
|
|
val = os.getenv(name)
|
|
return val if val else None
|
|
|
|
|
|
class ComplianceSettings(BaseModel):
|
|
"""The ``compliance:`` config block."""
|
|
|
|
enabled: bool = False
|
|
|
|
# Full URL of the discovery receive endpoint. Health and config-report URLs
|
|
# are derived from this by replacing the trailing ``/receive`` path.
|
|
server_url: str = "https://www.aissurance.eu/api/v1/discovery/receive"
|
|
|
|
# Tenant-provisioned secret (AISSURANCE_KEY). Used both as the Bearer token
|
|
# and as the HMAC signing key unless ``hmac_secret`` overrides the latter.
|
|
api_key: Optional[str] = Field(default_factory=lambda: _env_or_none("AISSURANCE_KEY"))
|
|
# Optional per-tenant HMAC secret override (AISSURANCE_HMAC_SECRET).
|
|
hmac_secret: Optional[str] = Field(default_factory=lambda: _env_or_none("AISSURANCE_HMAC_SECRET"))
|
|
# Tenant UUID, if not embedded in the key (AISSURANCE_TENANT_ID).
|
|
tenant_id: Optional[str] = Field(default_factory=lambda: _env_or_none("AISSURANCE_TENANT_ID"))
|
|
|
|
# Unique identifier for this Router instance. Multiple instances per tenant
|
|
# are allowed, so this defaults to the hostname rather than the tenant id.
|
|
router_id: str = Field(default_factory=socket.gethostname)
|
|
|
|
polling_interval: int = 300 # seconds between discovery snapshots
|
|
health_interval: int = 3600 # seconds between health/config-sync calls
|
|
batch_size: int = 50 # max models per payload
|
|
max_retry_attempts: int = 10 # in-cycle send retries before buffering
|
|
retry_backoff_base: int = 2 # exponential backoff base (seconds)
|
|
|
|
buffer_dir: str = "./compliance-buffer" # persistent encrypted retry storage
|
|
max_buffer_payloads: int = 100 # oldest dropped first when full
|
|
|
|
# TLS verification. AISSURANCE_VERIFY_TLS=0 disables it (dev only).
|
|
verify_tls: bool = Field(
|
|
default_factory=lambda: os.getenv("AISSURANCE_VERIFY_TLS", "1") != "0"
|
|
)
|
|
|
|
@field_validator("api_key", "hmac_secret", "tenant_id", mode="before")
|
|
@classmethod
|
|
def _empty_to_none(cls, v):
|
|
# config.yaml ``${VAR}`` expansion yields "" for unset vars; treat as None
|
|
# so the env-var default_factory can take over.
|
|
if v == "":
|
|
return None
|
|
return v
|
|
|
|
@property
|
|
def discovery_base_url(self) -> str:
|
|
"""Base discovery URL, derived by stripping a trailing ``/receive``."""
|
|
url = self.server_url.rstrip("/")
|
|
if url.endswith("/receive"):
|
|
url = url[: -len("/receive")]
|
|
return url
|
|
|
|
@property
|
|
def receive_url(self) -> str:
|
|
return self.server_url
|
|
|
|
@property
|
|
def health_url(self) -> str:
|
|
tid = self.tenant_id or "unknown"
|
|
return f"{self.discovery_base_url}/health/{tid}"
|
|
|
|
@property
|
|
def config_report_url(self) -> str:
|
|
return f"{self.discovery_base_url}/config/report"
|
|
|
|
def validation_errors(self) -> list[str]:
|
|
"""Return human-readable reasons the plugin cannot run, or [] if OK."""
|
|
errors: list[str] = []
|
|
if not self.api_key:
|
|
errors.append("AISSURANCE_KEY / compliance.api_key is not set")
|
|
if not self.server_url.lower().startswith("https://"):
|
|
errors.append(f"server_url must be HTTPS (got {self.server_url!r})")
|
|
if self.polling_interval < 60:
|
|
errors.append("polling_interval must be >= 60 seconds")
|
|
return errors
|