Exemplo n.º 1
0
 def __init__(self,
              path: str,
              timeout: Optional[int] = None,
              backoff: Optional[float] = None):
     self._filewatcher = FileWatcher(path,
                                     json.load,
                                     timeout=timeout,
                                     backoff=backoff)
Exemplo n.º 2
0
 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)
Exemplo n.º 3
0
 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
Exemplo n.º 4
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()
Exemplo n.º 5
0
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,
        )
Exemplo n.º 6
0
 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
Exemplo n.º 7
0
 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)
Exemplo n.º 8
0
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)
Exemplo n.º 9
0
 def __init__(self, filename: str):
     self._filewatcher = FileWatcher(filename, _parse)