C-6: ConnectorRuntime enforces capability_mask per operation.
READ-only ACs cannot invoke MUTATE operations (wipe, lock, retire).
C-7: AC validated against database (exists, active, not expired)
before connector invocation.
C-9: Delegated AC capability bounded by delegator's capability.
C-10: Command counter uses atomic SQL increment with limit check.
M-23: expire_stale() uses same atomic SQL pattern.
H-1: Sensitive credential fields hidden from repr/logs via repr=False.
H-2: Stub backend requires ALLOW_STUB_CREDENTIALS=true to activate.
H-3: Kerberos backend raises CredentialResolutionError instead of
returning stub ticket.
H-4: Chronicle INTENT emitted before execution, RESULT after.
H-5: device_id validated as UUID before Graph API URL interpolation.
H-8: ConnectorRuntime enforces governance for all connector invocations.
Signed-off-by: Tyler King <tking@guildhouse.dev>
86 lines
2.7 KiB
Python
86 lines
2.7 KiB
Python
from __future__ import annotations as _annotations # important for BaseSettings import to work
|
|
|
|
import json
|
|
from collections.abc import Mapping
|
|
from typing import TYPE_CHECKING
|
|
|
|
from ..utils import parse_env_vars
|
|
from .env import EnvSettingsSource
|
|
|
|
if TYPE_CHECKING:
|
|
from pydantic_settings.main import BaseSettings
|
|
|
|
|
|
boto3_client = None
|
|
SecretsManagerClient = None
|
|
|
|
|
|
def import_aws_secrets_manager() -> None:
|
|
global boto3_client
|
|
global SecretsManagerClient
|
|
|
|
try:
|
|
from boto3 import client as boto3_client
|
|
from mypy_boto3_secretsmanager.client import SecretsManagerClient
|
|
except ImportError as e: # pragma: no cover
|
|
raise ImportError(
|
|
'AWS Secrets Manager dependencies are not installed, run `pip install pydantic-settings[aws-secrets-manager]`'
|
|
) from e
|
|
|
|
|
|
class AWSSecretsManagerSettingsSource(EnvSettingsSource):
|
|
_secret_id: str
|
|
_secretsmanager_client: SecretsManagerClient # type: ignore
|
|
|
|
def __init__(
|
|
self,
|
|
settings_cls: type[BaseSettings],
|
|
secret_id: str,
|
|
region_name: str | None = None,
|
|
endpoint_url: str | None = None,
|
|
case_sensitive: bool | None = True,
|
|
env_prefix: str | None = None,
|
|
env_nested_delimiter: str | None = '--',
|
|
env_parse_none_str: str | None = None,
|
|
env_parse_enums: bool | None = None,
|
|
version_id: str | None = None,
|
|
) -> None:
|
|
import_aws_secrets_manager()
|
|
self._secretsmanager_client = boto3_client('secretsmanager', region_name=region_name, endpoint_url=endpoint_url) # type: ignore
|
|
self._secret_id = secret_id
|
|
self._version_id = version_id
|
|
super().__init__(
|
|
settings_cls,
|
|
case_sensitive=case_sensitive,
|
|
env_prefix=env_prefix,
|
|
env_nested_delimiter=env_nested_delimiter,
|
|
env_ignore_empty=False,
|
|
env_parse_none_str=env_parse_none_str,
|
|
env_parse_enums=env_parse_enums,
|
|
)
|
|
|
|
def _load_env_vars(self) -> Mapping[str, str | None]:
|
|
request = {'SecretId': self._secret_id}
|
|
|
|
if self._version_id:
|
|
request['VersionId'] = self._version_id
|
|
|
|
response = self._secretsmanager_client.get_secret_value(**request) # type: ignore
|
|
|
|
return parse_env_vars(
|
|
json.loads(response['SecretString']),
|
|
self.case_sensitive,
|
|
self.env_ignore_empty,
|
|
self.env_parse_none_str,
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
return (
|
|
f'{self.__class__.__name__}(secret_id={self._secret_id!r}, '
|
|
f'env_nested_delimiter={self.env_nested_delimiter!r})'
|
|
)
|
|
|
|
|
|
__all__ = [
|
|
'AWSSecretsManagerSettingsSource',
|
|
]
|