diff --git a/vault_cli/cli.py b/vault_cli/cli.py index 7844591..c222b8b 100644 --- a/vault_cli/cli.py +++ b/vault_cli/cli.py @@ -1,4 +1,5 @@ import contextlib +import json import logging import os import pathlib @@ -414,11 +415,26 @@ def delete(client_obj: client.VaultClientBase, name: str, key: Optional[str]) -> formerly deprecated. See command help for details. """, ) +@click.option( + "--file", + multiple=True, + help=""" + Write a secret from this path into a file on the filesystem. Expected format is + path/in/vault:key=/path/in/filesystem . This option is meant to be used when you are + your command can only read its inputs from a file and not from the environment (e.g. + secret keys, ...). It's highly recommended to only use the option when provided with + a secure private temporary filesystem. Writing to a physical disk should be avoided + when possible. + """, +) @click.option( "-o", "--omit-single-key/--no-omit-single-key", default=False, - help="When the secret has only one key, don't use that key to build the name of the environment variable", + help=""" + When the secret has only one key, don't use that key to build the name of the + environment variable. This option doesn't affect --file. + """, ) @click.option( "-f", @@ -432,6 +448,7 @@ def delete(client_obj: client.VaultClientBase, name: str, key: Optional[str]) -> def env( client_obj: client.VaultClientBase, envvar: Sequence[str], + file: Sequence[str], omit_single_key: bool, force: bool, command: Sequence[str], @@ -462,6 +479,7 @@ def env( Using `--path a/b/c:username=FOO` will inject `FOO=me` in the environment. """ envvars = list(envvar) or [] + files = list(file) or [] env_secrets = {} @@ -483,6 +501,22 @@ def env( env_secrets.update(env_updates) + for file in files: + path, key, filesystem_path = get_env_parts(file) + if not (path and key and filesystem_path): + raise click.BadOptionUsage( + "file", "--file expected format is vault/path:key=filesystem/path" + ) + secret_obj = client_obj.get_secret(path=path, key=key) + + if isinstance(secret_obj, str): + secret = secret_obj + else: + secret = yaml.safe_dump( + secret_obj, default_flow_style=False, explicit_start=True + ) + pathlib.Path(filesystem_path).write_text(secret + "\n") + if bool(client_obj.errors) and not force: raise click.ClickException("There was an error while reading the secrets.") environment.exec_command(command=command, environment=env_secrets)