def __init__(self, path: str, timeout: Optional[int] = None, backoff: Optional[float] = None): self._filewatcher = FileWatcher(path, json.load, timeout=timeout, backoff=backoff)
def __init__( self, path: str, timeout: Optional[int] = None, backoff: Optional[float] = None, parser: Optional[SecretParser] = None, ): # pylint: disable=super-init-not-called self.parser = parser or parse_secrets_fetcher self._filewatcher = FileWatcher(path, json.load, timeout=timeout, backoff=backoff)
def __init__( self, path: str, event_logger: Optional[EventLogger] = None, timeout: Optional[float] = None, backoff: Optional[float] = None, ): self._filewatcher = FileWatcher(path, json.load, timeout=timeout, backoff=backoff) self._global_cache: Dict[str, Optional[Experiment]] = {} self._event_logger = event_logger self.cfg_mtime = 0.0
class ServiceInventory: """The inventory enumerates available backends for a single service. :param filename: The absolute path to the Synapse-generated inventory file in JSON format. """ def __init__(self, filename: str): self._filewatcher = FileWatcher(filename, _parse) def get_backends(self) -> Sequence[Backend]: """Return a list of all available backends in the inventory. If the inventory file becomes unavailable, the previously seen inventory is returned. """ try: # pylint: disable=maybe-no-member return self._filewatcher.get_data().backends except WatchedFileNotAvailableError: return [] def get_backend(self) -> Backend: """Return a randomly chosen backend from the available backends. If weights are specified in the inventory, they will be respected when making the random selection. :raises: :py:exc:`NoBackendsAvailableError` if the inventory has no available endpoints. """ inventory: Optional[_Inventory] try: inventory = self._filewatcher.get_data() except WatchedFileNotAvailableError: inventory = None # pylint: disable=maybe-no-member if not inventory or not inventory.lottery: raise NoBackendsAvailableError return inventory.lottery.pick()
class ExperimentsContextFactory(ContextFactory): """Experiment client context factory. This factory will attach a new :py:class:`baseplate.lib.experiments.Experiments` to an attribute on the :py:class:`~baseplate.RequestContext`. :param path: Path to the experiment configuration file. :param event_logger: The logger to use to log experiment eligibility events. If not provided, a :py:class:`~baseplate.lib.events.DebugLogger` will be created and used. :param timeout: How long, in seconds, to block instantiation waiting for the watched experiments file to become available (defaults to not blocking). :param backoff: retry backoff time for experiments file watcher. Defaults to None, which is mapped to DEFAULT_FILEWATCHER_BACKOFF. """ def __init__( self, path: str, event_logger: Optional[EventLogger] = None, timeout: Optional[float] = None, backoff: Optional[float] = None, ): self._filewatcher = FileWatcher(path, json.load, timeout=timeout, backoff=backoff) self._global_cache: Dict[str, Optional[Experiment]] = {} self._event_logger = event_logger self.cfg_mtime = 0.0 def make_object_for_context(self, name: str, span: Span) -> "Experiments": config_data: Dict[str, Dict[str, str]] = {} try: config_data, mtime = self._filewatcher.get_data_and_mtime() if mtime > self.cfg_mtime: self.cfg_mtime = mtime self._global_cache = {} except WatchedFileNotAvailableError as exc: logger.warning("Experiment config unavailable: %s", str(exc)) except TypeError as exc: logger.warning("Could not load experiment config: %s", str(exc)) return Experiments( config_watcher=self._filewatcher, server_span=span, context_name=name, cfg_data=config_data, global_cache=self._global_cache, event_logger=self._event_logger, )
def __init__( self, path: str, event_logger: Optional[EventLogger] = None, timeout: Optional[float] = None, backoff: Optional[float] = None, ): self._filewatcher = FileWatcher(path, json.load, timeout=timeout, backoff=backoff) self._event_logger = event_logger
def __init__( self, path: str, parser: SecretParser, timeout: Optional[int] = None, backoff: Optional[float] = None, ): # pylint: disable=super-init-not-called self.parser = parser self._filewatchers = {} root = Path(path) for p in root.glob("**"): file_path = str(p.relative_to(path)) self._filewatchers[file_path] = FileWatcher(file_path, json.load, timeout=timeout, backoff=backoff)
class SecretsStore(ContextFactory): """Access to secret tokens with automatic refresh when changed. This local vault allows access to the secrets cached on disk by the fetcher daemon. It will automatically reload the cache when it is changed. Do not cache or store the values returned by this class's methods but rather get them from this class each time you need them. The secrets are served from memory so there's little performance impact to doing so and you will be sure to always have the current version in the face of key rotation etc. """ def __init__(self, path: str, timeout: Optional[int] = None): self._filewatcher = FileWatcher(path, json.load, timeout=timeout) def _get_data(self) -> Any: try: return self._filewatcher.get_data() except WatchedFileNotAvailableError as exc: raise SecretsNotAvailableError(exc) def get_raw(self, path: str) -> Dict[str, str]: """Return a dictionary of key/value pairs for the given secret path. This is the raw representation of the secret in the underlying store. """ data = self._get_data() try: return data["secrets"][path] except KeyError: raise SecretNotFoundError(path) def get_credentials(self, path: str) -> CredentialSecret: """Decode and return a credential secret. Credential secrets are a convention of username/password pairs stored as separate values in the raw secret payload. The following keys are significant: ``type`` This must always be ``credential`` for this method. ``encoding`` This must be unset or set to ``identity``. ``username`` This contains the raw username. ``password`` This contains the raw password. """ secret_attributes = self.get_raw(path) if secret_attributes.get("type") != "credential": raise CorruptSecretError(path, "secret does not have type=credential") encoding = secret_attributes.get("encoding", "identity") if encoding != "identity": raise CorruptSecretError( path, f"secret has encoding={encoding} rather than encoding=identity" ) values = {} for key in ("username", "password"): try: val = secret_attributes[key] if not isinstance(val, str): raise CorruptSecretError( path, f"secret value '{key}' is not a string") values[key] = val except KeyError: raise CorruptSecretError(path, f"secret does not have key '{key}'") return CredentialSecret(**values) def get_simple(self, path: str) -> bytes: """Decode and return a simple secret. Simple secrets are a convention of key/value pairs in the raw secret payload. The following keys are significant: ``type`` This must always be ``simple`` for this method. ``value`` This contains the raw value of the secret token. ``encoding`` (Optional) If present, how to decode the value from how it's encoded at rest (only ``base64`` currently supported). """ secret_attributes = self.get_raw(path) if secret_attributes.get("type") != "simple": raise CorruptSecretError(path, "secret does not have type=simple") try: value = secret_attributes["value"] except KeyError: raise CorruptSecretError(path, "secret does not have value") encoding = secret_attributes.get("encoding", "identity") return _decode_secret(path, encoding, value) def get_versioned(self, path: str) -> VersionedSecret: """Decode and return a versioned secret. Versioned secrets are a convention of key/value pairs in the raw secret payload. The following keys are significant: ``type`` This must always be ``versioned`` for this method. ``current``, ``next``, and ``previous`` The raw secret value's versions. ``current`` is the "active" version, which is used for new creation/signing operations. ``previous`` and ``next`` are only used for validation (e.g. checking signatures) to ensure continuity when keys rotate. Both ``previous`` and ``next`` are optional. ``encoding`` (Optional) If present, how to decode the values from how they are encoded at rest (only ``base64`` currently supported). """ secret_attributes = self.get_raw(path) if secret_attributes.get("type") != "versioned": raise CorruptSecretError(path, "secret does not have type=versioned") previous_value = secret_attributes.get("previous") next_value = secret_attributes.get("next") try: current_value = secret_attributes["current"] except KeyError: raise CorruptSecretError(path, "secret does not have 'current' value") encoding = secret_attributes.get("encoding", "identity") return VersionedSecret( previous=_decode_secret(path, encoding, previous_value) if previous_value else None, current=_decode_secret(path, encoding, current_value), next=_decode_secret(path, encoding, next_value) if next_value else None, ) def get_vault_url(self) -> str: """Return the URL for accessing Vault directly. .. seealso:: The :py:mod:`baseplate.clients.hvac` module provides integration with HVAC, a Vault client. """ data = self._get_data() return data["vault"]["url"] def get_vault_token(self) -> str: """Return a Vault authentication token. The token will have policies attached based on the current EC2 server's Vault role. This is only necessary if talking directly to Vault. .. seealso:: The :py:mod:`baseplate.clients.hvac` module provides integration with HVAC, a Vault client. """ data = self._get_data() return data["vault"]["token"] def make_object_for_context(self, name: str, span: Span) -> "SecretsStore": """Return an object that can be added to the context object. This allows the secret store to be used with :py:meth:`~baseplate.Baseplate.add_to_context`:: secrets = SecretsStore("/var/local/secrets.json") baseplate.add_to_context("secrets", secrets) """ return _CachingSecretsStore(self._filewatcher)
def __init__(self, filename: str): self._filewatcher = FileWatcher(filename, _parse)