def render_template( self, template: str, search_path: pathlib.Path = pathlib.Path("."), ) -> str: """ Renders a template to a string, giving it access to a `vault` function that can read from the vault Parameters ---------- template : str Jinja template string render : bool, optional Whether template secrets should be rendered, by default True search_path: pathlib.Path object, optional search path for additional Jinja2 templates, by default current working directory See https://jinja.palletsprojects.com/en/2.10.x/api/#jinja2.FileSystemLoader Returns ------- str The rendered template Raises ------ exceptions.VaultRenderTemplateError If a secret is not found or access is forbidden """ def vault(path): try: return self.get_secret(path) except exceptions.VaultException as exc: raise exceptions.VaultRenderTemplateError( "Error while rendering template") from exc env = jinja2.sandbox.SandboxedEnvironment( loader=jinja2.FileSystemLoader(search_path.as_posix()), keep_trailing_newline=True, ) try: return env.from_string(template).render(vault=vault) except jinja2.exceptions.SecurityError as exc: raise exceptions.VaultRenderTemplateError( "Jinja2 template security error") from exc except jinja2.exceptions.TemplateSyntaxError as exc: raise exceptions.VaultRenderTemplateError( "Jinja2 template syntax error") from exc
def get_secret(self, path: str, key: Optional[str] = None, render: bool = True ) -> Union[types.JSONValue, utils.RecursiveValue]: """ Retrieve the value of a single secret Parameters ---------- path : str Path of the secret key : str, optional If set, return only this key render : bool, optional Whether to render templated secret or not, by default True Returns ------- types.JSONValue Secret value """ full_path = self._build_full_path(path) if full_path in self._currently_fetching: return utils.RecursiveValue(path) self._currently_fetching.add(full_path) try: assert self.cache is not None try: mapping = self.cache[full_path] except KeyError: mapping = self.cache[full_path] = self._get_secret( path=full_path) if mapping and render and self.render: try: mapping = self._render_template_dict(mapping) except exceptions.VaultRenderTemplateError as exc: message = f'Error while rendering secret at path "{path}"' raise exceptions.VaultRenderTemplateError(message) from exc finally: self._currently_fetching.remove(full_path) if key is not None: try: secret = mapping[key] except KeyError: raise exceptions.VaultSecretNotFound(errors=[ f"Key '{key}' not found in secret at path '{full_path}'" ]) else: secret = mapping return secret
def _render_template_dict( self, secrets: Dict[str, types.JSONValue]) -> Dict[str, types.JSONValue]: result = {} for key, value in secrets.items(): try: result[key] = self._render_template_value(value) except exceptions.VaultRenderTemplateError as exc: message = f'Error while rendering secret value for key "{key}"' raise exceptions.VaultRenderTemplateError(message) from exc return result
def vault(path): try: return self.get_secret(path, render=render) except exceptions.VaultException as exc: raise exceptions.VaultRenderTemplateError( "Error while rendering template") from exc
def vault(path): try: return self.get_secret(path, render=render) except exceptions.VaultException: raise exceptions.VaultRenderTemplateError(f"'{path}' not found")
def test_vault_render_template_error(): assert ( str(exceptions.VaultRenderTemplateError("yay")) == "VaultRenderTemplateError: Error while rendering template: yay" )
def test_vault_render_template_error(): assert str(exceptions.VaultRenderTemplateError("yay")) == "yay"