"""Read application secrets from AWS Secrets Manager.""" import base64 import json import os from functools import cache import boto3 SECRET_NAME_ENV = "SLI_SECRET_NAME" @cache def _load_secret(secret_name: str) -> dict: """Load and cache a JSON secret for the lifetime of the process.""" response = boto3.client("secretsmanager").get_secret_value(SecretId=secret_name) secret_content = response.get("SecretString") if secret_content is None and "SecretBinary" in response: secret_content = base64.b64decode(response["SecretBinary"]).decode("utf-8") if secret_content is None: raise RuntimeError(f"secret {secret_name!r} has no value") try: secret = json.loads(secret_content) except (json.JSONDecodeError, TypeError) as exc: raise RuntimeError(f"secret {secret_name!r} must contain valid JSON") from exc if not isinstance(secret, dict): raise TypeError(f"secret {secret_name!r} must contain a JSON object") return secret def get_secret(key: str, secret_name: str | None = None) -> str: """Return a required string value from the configured application secret.""" resolved_secret_name = secret_name or os.getenv(SECRET_NAME_ENV) if not resolved_secret_name: raise RuntimeError(f"environment variable {SECRET_NAME_ENV} is required") secret = _load_secret(resolved_secret_name) value = secret.get(key) if not isinstance(value, str) or not value: raise RuntimeError( f"secret {resolved_secret_name!r} is missing required string key {key!r}" ) return value