Exemplo n.º 1
0
def delete(client_obj: client.VaultClientBase, name: str,
           key: Optional[str]) -> None:
    """
    Delete a single secret.
    """
    client_obj.delete_secret(path=name, key=key)
    click.echo("Done")
Exemplo n.º 2
0
def set_all(
    client_obj: client.VaultClientBase,
    update: bool,
    force: Optional[bool],
    yaml_file: TextIO,
):
    """
    Set multiple secrets at once from a yaml mapping.
    \b
    Mapping expected format:
    path/to/secret/a:
        somekey: mysecret
        otherkey: othersecret
    path/to/secret/a:
        yetanotherkey: [the, secret, is, a, list]
    """
    secrets = yaml.safe_load(yaml_file)
    error = "Mapping expected format is a mapping of paths to secret objects"
    if not isinstance(secrets, dict) or not all(
        isinstance(v, dict) for v in secrets.values()
    ):
        raise click.ClickException(error)

    try:
        client_obj.set_secrets(secrets=secrets, force=force, update=update)
    except exceptions.VaultOverwriteSecretError as exc:
        raise click.ClickException(f"{exc}\nUse -f to force overwriting")
    except exceptions.VaultMixSecretAndFolder as exc:
        raise click.ClickException(str(exc))
    click.echo("Done")
Exemplo n.º 3
0
def set_(
    client_obj: client.VaultClientBase,
    format_yaml: bool,
    stdin: bool,
    name: str,
    value: Sequence[str],
):
    """
    Set a single secret to the given value(s).

    Value can be either passed as argument (several arguments will be
    interpreted as a list) or via stdin with the --stdin flag.
    """
    if stdin and value:
        raise click.UsageError("Can't set both --stdin and a value")

    final_value: types.JSONValue
    if stdin:
        final_value = click.get_text_stream("stdin").read().strip()

    elif len(value) == 1:
        final_value = value[0]

    else:
        final_value = list(value)

    if format_yaml:
        assert isinstance(final_value, str)
        final_value = yaml.safe_load(final_value)

    client_obj.set_secret(path=name, value=final_value)
    click.echo("Done")
Exemplo n.º 4
0
def ssh_(
    client_obj: client.VaultClientBase,
    key: str,
    passphrase: Optional[str],
    command: Sequence[str],
) -> NoReturn:
    """
    Launch a command, with a configured ssh-agent running.

    The ssh agent has all the keys specified as `--key` arguments.

    The effect will be that any command launched through this wrapper will "magically"
    (thanks to ssh-agent) be able to use the secret keys given as `--key` for their
    ssh connections.
    """
    ssh.ensure_agent()

    if ":" not in key:
        raise click.UsageError("Format for private_key: `path/to/private_key:key`")
    private_key_name, private_key_key = key.rsplit(":", 1)
    private_key_secret_obj = client_obj.get_secret(private_key_name, private_key_key)
    private_key_secret = ensure_str(secret=private_key_secret_obj, path=key)

    passphrase_secret = None
    if passphrase:
        if ":" not in passphrase:
            raise click.UsageError("Format for passphrase: `path/to/passphrase:key`")
        passphrase_name, passphrase_key = passphrase.rsplit(":", 1)
        passphrase_secret_obj = client_obj.get_secret(passphrase_name, passphrase_key)
        passphrase_secret = ensure_str(secret=passphrase_secret_obj, path=passphrase)

    ssh.add_key(key=private_key_secret, passphrase=passphrase_secret)

    environment.exec_command(command=command)
Exemplo n.º 5
0
def env(
    client_obj: client.VaultClientBase,
    path: Sequence[str],
    omit_single_key: bool,
    command: Sequence[str],
) -> NoReturn:
    """
    Launch a command, loading secrets in environment.

    Strings are exported as-is, other types (including booleans, nulls, dicts, lists)
    are exported JSON-encoded.

    If the path ends with `:key` then only one key of the mapping is used and its name is the name of the key.

    VARIABLE NAMES

    By default the name is build upon the relative path to the parent of the given path (in parameter) and the name of the keys in the value mapping.
    Let's say that we have stored the mapping `{'username': '******', 'password': '******'}` at path `a/b/c`

    Using `--path a/b` will inject the following environment variables: B_C_USERNAME and B_C_PASSWORD
    Using `--path a/b/c` will inject the following environment variables: C_USERNAME and C_PASSWORD
    Using `--path a/b/c:username` will only inject `USERNAME=me` in the environment.

    You can customize the variable names generation by appending `=SOME_PREFIX` to the path.
    In this case the part corresponding to the base path is replace by your prefix.

    Using `--path a/b=FOO` will inject the following environment variables: FOO_C_USERNAME and FOO_C_PASSWORD
    Using `--path a/b/c=FOO` will inject the following environment variables: FOO_USERNAME and FOO_PASSWORD
    Using `--path a/b/c:username=FOO` will inject `FOO=me` in the environment.
    """
    paths = list(path) or [""]

    env_secrets = {}

    for path in paths:
        path_with_key, _, prefix = path.partition("=")
        path, _, filter_key = path_with_key.partition(":")

        if filter_key:
            secret = client_obj.get_secret(path=path, key=filter_key)
            env_updates = environment.get_envvars_for_secret(key=filter_key,
                                                             secret=secret,
                                                             prefix=prefix)
        else:
            secrets = client_obj.get_secrets(path=path, relative=True)
            env_updates = environment.get_envvars_for_secrets(
                path=path,
                prefix=prefix,
                secrets=secrets,
                omit_single_key=omit_single_key,
            )
        env_secrets.update(env_updates)

    environ = os.environ.copy()
    environ.update(env_secrets)

    environment.exec_command(command=command, environ=environ)
Exemplo n.º 6
0
def set_(
    client_obj: client.VaultClientBase,
    format_yaml: bool,
    prompt: bool,
    stdin: bool,
    strip: bool,
    name: str,
    value: Sequence[str],
    force: Optional[bool],
):
    """
    Set a single secret to the given value(s).

    Value can be either passed as argument (several arguments will be
    interpreted as a list) or via stdin with the --stdin flag.
    """
    if prompt + bool(value) + stdin > 1:
        raise click.UsageError(
            "Conflicting input methods: use only one of --prompt, --stdin and "
            "positional argument"
        )

    json_value: types.JSONValue
    if stdin:
        json_value = click.get_text_stream("stdin").read()

    elif prompt:
        json_value = click.prompt(f"Please enter value for `{name}`", hide_input=True)

    elif len(value) == 1:
        json_value = value[0]

    else:
        json_value = list(value)

    if format_yaml:
        if not isinstance(json_value, str):
            raise click.UsageError(
                "Cannot pass several values when using yaml format. "
                "Please use a yaml list instead."
            )
        json_value = yaml.safe_load(json_value)

    if strip and isinstance(json_value, str):
        json_value = fix_whitespaces(string=json_value)

    try:
        client_obj.set_secret(path=name, value=json_value, force=force)
    except exceptions.VaultOverwriteSecretError as exc:
        raise click.ClickException(
            f"Secret already exists at {exc.path}. Use -f to force overwriting."
        )
    except exceptions.VaultMixSecretAndFolder as exc:
        raise click.ClickException(str(exc))
    click.echo("Done")
Exemplo n.º 7
0
def set_(
    client_obj: client.VaultClientBase,
    format_yaml: bool,
    prompt: bool,
    stdin: bool,
    name: str,
    value: Sequence[str],
    force: Optional[bool],
):
    """
    Set a single secret to the given value(s).

    Value can be either passed as argument (several arguments will be
    interpreted as a list) or via stdin with the --stdin flag.
    """
    if prompt and value:
        raise click.UsageError("Can't set both --prompt and a value")

    if stdin and value:
        raise click.UsageError("Can't set both --stdin and a value")

    if stdin and prompt:
        raise click.UsageError("Can't use both --stdin and --prompt")

    final_value: types.JSONValue
    if stdin:
        final_value = click.get_text_stream("stdin").read().strip()
    elif prompt:
        final_value = click.prompt(
            f"Please enter value for `{name}`", hide_input=True
        ).strip()

    elif len(value) == 1:
        final_value = value[0]

    else:
        final_value = list(value)

    if format_yaml:
        assert isinstance(final_value, str)
        final_value = yaml.safe_load(final_value)

    try:
        client_obj.set_secret(path=name, value=final_value, force=force)
    except exceptions.VaultOverwriteSecretError as exc:
        raise click.ClickException(
            f"Secret already exists at {exc.path}. Use -f to force overwriting."
        )
    except exceptions.VaultMixSecretAndFolder as exc:
        raise click.ClickException(str(exc))
    click.echo("Done")
Exemplo n.º 8
0
def set_(
    client_obj: client.VaultClientBase,
    update: bool,
    prompt: bool,
    yaml_file: TextIO,
    path: str,
    attributes: Sequence[str],
    force: Optional[bool],
):
    """
    Set a secret.

    \b
    You can give secrets in 3 different ways:
    - Usage: vault-cli set [OPTIONS] PATH [key=value...]
      directly in the arguments. A value of "-" means that value will be read from the standard input
    - Usage: vault-cli set [OPTIONS] PATH --prompt [key...]
      prompt user for a values using hidden input
    - Usage: vault-cli set [OPTIONS] PATH --file=/path/to/file
      using a json/yaml file
    """
    if bool(attributes) + bool(yaml_file) > 1:
        raise click.UsageError(
            "Conflicting input methods: you can't mix --file and positional argument"
        )

    json_value: types.JSONValue
    if yaml_file:
        json_value = yaml.safe_load(yaml_file)
    elif prompt:
        json_value = {}
        for key in attributes:
            json_value[key] = click.prompt(
                f"Please enter a value for key `{key}` of `{path}`",
                hide_input=True)
    else:
        json_value = dict(build_kv(attributes))

    try:
        client_obj.set_secret(path=path,
                              value=json_value,
                              force=force,
                              update=update)
    except exceptions.VaultOverwriteSecretError as exc:
        raise click.ClickException(
            f"Secret already exists at {exc.path}. Use -f to force overwriting."
        )
    except exceptions.VaultMixSecretAndFolder as exc:
        raise click.ClickException(str(exc))
    click.echo("Done")
Exemplo n.º 9
0
def list_(client_obj: client.VaultClientBase, path: str):
    """
    List all the secrets at the given path. Folders are listed too. If no path
    is given, list the objects at the root.
    """
    result = client_obj.list_secrets(path=path)
    click.echo("\n".join(result))
Exemplo n.º 10
0
def env(
    client_obj: client.VaultClientBase, path: Sequence[str], command: Sequence[str]
) -> NoReturn:
    """
    Launches a command, loading secrets in environment.

    Strings are exported as-is, other types (including booleans, nulls, dicts, lists)
    are exported as yaml (more specifically as json).
    """
    paths = list(path) or [""]

    env_secrets = {}

    for path in paths:
        secrets = client_obj.get_secrets(path)
        env_secrets.update(
            {
                environment.make_env_key(
                    path=path, key=key
                ): environment.make_env_value(value)
                for key, value in secrets.items()
            }
        )

    environ = os.environ.copy()
    environ.update(env_secrets)

    environment.exec_command(command=command, environ=environ)
Exemplo n.º 11
0
def lookup_token(client_obj: client.VaultClientBase) -> None:
    """
    Return information regarding the current token
    """
    click.echo(
        yaml.safe_dump(client_obj.lookup_token(),
                       default_flow_style=False,
                       explicit_start=True),
        nl=False,
    )
Exemplo n.º 12
0
def delete_all(
    client_obj: client.VaultClientBase, path: Sequence[str], force: bool
) -> None:
    """
    Delete multiple secrets.
    """
    paths = list(path) or [""]

    for secret in client_obj.delete_all_secrets(*paths):
        if not force and not click.confirm(text=f"Delete '{secret}'?", default=False):
            raise click.Abort()
        click.echo(f"Deleted '{secret}'")
Exemplo n.º 13
0
def get(client_obj: client.VaultClientBase, text: bool, name: str):
    """
    Return a single secret value.
    """
    secret = client_obj.get_secret(path=name)
    if text:
        click.echo(secret)
        return

    click.echo(
        yaml.safe_dump(secret, default_flow_style=False, explicit_start=True), nl=False
    )
Exemplo n.º 14
0
def get_all(client_obj: client.VaultClientBase, path: Sequence[str]):
    """
    Return multiple secrets. Return a single yaml with all the secrets located
    at the given paths. Folders are recursively explored. Without a path,
    explores all the vault.
    """
    paths = list(path) or [""]

    result = client_obj.get_all_secrets(*paths)

    click.echo(
        yaml.safe_dump(result, default_flow_style=False, explicit_start=True), nl=False
    )
Exemplo n.º 15
0
def template(
    client_obj: client.VaultClientBase, template: TextIO, output: TextIO
) -> None:
    """
    Render the given template and insert secrets in it.

    Rendering is done with jinja2. A vault() function is exposed that
    recieves a path and outputs the secret at this path.

    If template is -, standard input will be read.
    """
    result = client_obj.render_template(template.read())
    output.write(result)
Exemplo n.º 16
0
def get_envvars(
    vault_client: client.VaultClientBase,
    path: str,
    prefix: str,
    omit_single_key: bool,
    filter_key: str,
) -> Dict[str, str]:
    if filter_key:
        secret = vault_client.get_secret(path=path, key=filter_key)
        return get_envvars_for_secrets(
            path="",
            prefix=prefix,
            secrets={"": {
                filter_key: secret
            }},
            omit_single_key=bool(prefix),
        )
    else:
        secrets = vault_client.get_secrets(path=path, relative=True)
        return get_envvars_for_secrets(path=path,
                                       prefix=prefix,
                                       secrets=secrets,
                                       omit_single_key=omit_single_key)
Exemplo n.º 17
0
def get(client_obj: client.VaultClientBase, text: bool, name: str):
    """
    Return a single secret value.
    """
    secret = client_obj.get_secret(path=name)
    force_yaml = isinstance(secret, list) or isinstance(secret, dict)
    if text and not force_yaml:
        if secret is None:
            secret = "null"
        click.echo(secret)
        return

    click.echo(
        yaml.safe_dump(secret, default_flow_style=False, explicit_start=True), nl=False
    )
Exemplo n.º 18
0
def cp(
    client_obj: client.VaultClientBase, source: str, dest: str, force: Optional[bool]
) -> None:
    """
    Recursively copy secrets from source to destination path.
    """
    try:
        for old_path, new_path in client_obj.copy_secrets(
            source=source, dest=dest, force=force, generator=True
        ):
            click.echo(f"Copy '{old_path}' to '{new_path}'")
    except exceptions.VaultOverwriteSecretError as exc:
        raise click.ClickException(f"{exc}\nUse -f to force overwriting")
    except exceptions.VaultMixSecretAndFolder as exc:
        raise click.ClickException(str(exc))
Exemplo n.º 19
0
def mv(client_obj: client.VaultClientBase, source: str, dest: str,
       force: Optional[bool]) -> None:
    """
    Recursively move secrets from source to destination path.
    """
    try:
        for old_path, new_path in client_obj.move_secrets(source=source,
                                                          dest=dest,
                                                          force=force,
                                                          generator=True):
            click.echo(f"Move '{old_path}' to '{new_path}'")
    except exceptions.VaultOverwriteSecretError as exc:
        raise click.ClickException(
            f"Secret already exists at {exc.path}. Use -f to force overwriting."
        )
    except exceptions.VaultMixSecretAndFolder as exc:
        raise click.ClickException(str(exc))
Exemplo n.º 20
0
def env(
    client_obj: client.VaultClientBase, path: Sequence[str], command: Sequence[str]
) -> NoReturn:
    """
    Launch a command, loading secrets in environment.

    Strings are exported as-is, other types (including booleans, nulls, dicts, lists)
    are exported as yaml (more specifically as json).

    VARIABLE NAMES

    If the path is not a folder the prefix value is used as the name of the
    variable.
    e.g.: for a/b/key, `--path a/b/c=foo` gives `FOO=...`

    If the path is a folder, then the variable names will be generated as:
    PREFIX + _ + relative path
    e.g.: for a/b/key, `--path a/b=my` gives `MY_KEY=...`

    The standard behavior when no prefix is set will use the relative path to
    the parent of the given path as variable name
    e.g.: for a/b/key, --path a/b gives `B_KEY=...`
    e.g.: for a/b/key, --path a/b/key gives `KEY=...`
    """
    paths = list(path) or [""]

    env_secrets = {}

    for path in paths:
        path, _, prefix = path.partition("=")
        secrets = client_obj.get_secrets(path)
        env_secrets.update(
            {
                environment.make_env_key(
                    path=path, prefix=prefix, key=key
                ): environment.make_env_value(value)
                for key, value in secrets.items()
            }
        )

    environ = os.environ.copy()
    environ.update(env_secrets)

    environment.exec_command(command=command, environ=environ)
Exemplo n.º 21
0
def get(
    client_obj: client.VaultClientBase,
    text: bool,
    output: Optional[TextIO],
    key: Optional[str],
    name: str,
):
    """
    Return a single secret value.
    """
    secret = client_obj.get_secret(path=name, key=key)
    force_yaml = isinstance(secret, list) or isinstance(secret, dict)
    if text and not force_yaml:
        if secret is None:
            secret = "null"
        click.echo(secret, file=output)
        return

    click.echo(
        yaml.safe_dump(secret, default_flow_style=False, explicit_start=True),
        nl=False,
        file=output,
    )
Exemplo n.º 22
0
def template(client_obj: client.VaultClientBase, template: str, output: TextIO) -> None:
    """
    Render the given template and insert secrets in it.

    Rendering is done with Jinja2. A vault() function is exposed that
    receives a path and outputs the secret at this path.

    Search path (see https://jinja.palletsprojects.com/en/2.10.x/api/#jinja2.FileSystemLoader)
    for possible Jinja2 `{% include() %}` statement is set to the template's directory.

    If template is -, standard input will be read and the current working directory becomes the search path.

    """
    if template == "-":
        template_text = sys.stdin.read()
        search_path = pathlib.Path.cwd()
    else:
        template_path = pathlib.Path(template)
        template_text = template_path.read_text()
        search_path = template_path.parent

    result = client_obj.render_template(template_text, search_path=search_path)
    output.write(result)
Exemplo n.º 23
0
def delete(client_obj: client.VaultClientBase, name: str) -> None:
    """
    Deletes a single secret.
    """
    client_obj.delete_secret(path=name)
    click.echo("Done")
Exemplo n.º 24
0
def env(
    client_obj: client.VaultClientBase,
    envvar: Sequence[str],
    file: Sequence[str],
    omit_single_key: bool,
    force: bool,
    command: Sequence[str],
) -> NoReturn:
    """
    Write secrets to disk, load secrets in environment variable, then  lauch a command.

    Secrets stored as strings are loaded as-is. Secrets of any other type exported as an
    environment variable are JSON-encoded. Secrets of any other type written to a file
    are YAML-encoded.

    LOADING ENVIRONMENT VARIABLES

    If the path ends with `:key` then only one key of the mapping is used and its name is the name of the key.

    By default the name is build upon the relative path to the parent of the given path (in parameter) and the name of the keys in the value mapping.
    Let's say that we have stored the mapping `{'username': '******', 'password': '******'}` at path `a/b/c`

    Using `--envvar a/b` will inject the following environment variables: B_C_USERNAME and B_C_PASSWORD
    Using `--envvar a/b/c` will inject the following environment variables: C_USERNAME and C_PASSWORD
    Using `--envvar a/b/c:username` will only inject `USERNAME=me` in the environment.

    You can customize the variable names generation by appending `=SOME_PREFIX` to the path.
    In this case the part corresponding to the base path is replace by your prefix.

    Using `--envvar a/b=FOO` will inject the following environment variables: FOO_C_USERNAME and FOO_C_PASSWORD
    Using `--envvar a/b/c=FOO` will inject the following environment variables: FOO_USERNAME and FOO_PASSWORD
    Using `--envvar a/b/c:username=FOO` will inject `FOO=me` in the environment.
    """
    envvars = list(envvar) or []
    files = list(file) or []

    env_secrets = {}

    for path in envvars:
        path, key, prefix = get_env_parts(path)
        if not path and key:
            raise click.BadOptionUsage(
                "envvar", "Cannot omit the path if a filter key is provided"
            )

        env_updates = {}
        env_updates = environment.get_envvars(
            vault_client=client_obj,
            path=path,
            prefix=prefix,
            omit_single_key=omit_single_key,
            filter_key=key,
        )

        env_secrets.update(env_updates)

    for file in files:
        path, key, filesystem_path = get_env_parts(file)
        if not (path and filesystem_path):
            raise click.BadOptionUsage(
                "file", "--file expects both a vault path and a filesystem path"
            )
        secret_obj = client_obj.get_secret(path=path, key=key or None)

        if not isinstance(secret_obj, (list, dict)):
            secret = str(secret_obj).strip() + "\n"
        else:
            secret = yaml.safe_dump(
                secret_obj, default_flow_style=False, explicit_start=True
            )
        pathlib.Path(filesystem_path).write_text(secret)

    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)