Skip to content

Commit ae87b65

Browse files
author
Joachim Jablon
authored
Merge pull request #186 from peopledoc/env-write-185
2 parents 8505388 + 4e075d4 commit ae87b65

File tree

9 files changed

+133
-51
lines changed

9 files changed

+133
-51
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ Here are a few things you might do with ``vault-cli``:
6565
ohsosecret
6666
6767
$ # Load a secret into the environment variables:
68-
$ vault-cli env --path mysecret -- env | grep MYSECRET
68+
$ vault-cli env --envvar mysecret -- env | grep MYSECRET
6969
MYSECRET_MYKEY=ohsosecret
7070
7171
$ # Load an ssh key into your ssh-agent:

docs/howto/environment.rst

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ works:
1616
$ vault-cli set test/my_secret value=qwerty
1717
Done
1818
19-
$ vault-cli env --path test/my_secret -- bash -c 'echo $MY_SECRET_VALUE'
19+
$ vault-cli env --envvar test/my_secret -- bash -c 'echo $MY_SECRET_VALUE'
2020
qwerty
2121
2222
Environment variable naming
@@ -68,29 +68,29 @@ Let's consider the vault contains only the following secret:
6868
This table maps input to output. Note that there will always be a single environment
6969
variable and its value will always be ``mysecret``.
7070

71-
+-------------+-----------------------+---------------------------+
72-
| ``--path`` | ``--omit-single-key`` | environment variable name |
73-
+-------------+-----------------------+---------------------------+
74-
| ``a`` | False | ``A_B_C`` |
75-
+-------------+-----------------------+---------------------------+
76-
| ``a`` | True | ``A_B`` |
77-
+-------------+-----------------------+---------------------------+
78-
| ``a=D`` | False | ``D_B_C`` |
79-
+-------------+-----------------------+---------------------------+
80-
| ``a=D`` | True | ``D_B`` |
81-
+-------------+-----------------------+---------------------------+
82-
| ``a/b`` | False | ``B_C`` |
83-
+-------------+-----------------------+---------------------------+
84-
| ``a/b`` | True | ``B`` |
85-
+-------------+-----------------------+---------------------------+
86-
| ``a/b=D`` | False | ``D_C`` |
87-
+-------------+-----------------------+---------------------------+
88-
| ``a/b=D`` | True | ``D`` |
89-
+-------------+-----------------------+---------------------------+
90-
| ``a/b:c`` | True or False | ``C`` |
91-
+-------------+-----------------------+---------------------------+
92-
| ``a/b:c=D`` | True or False | ``D`` |
93-
+-------------+-----------------------+---------------------------+
71+
+---------------+-----------------------+---------------------------+
72+
| ``--envvar`` | ``--omit-single-key`` | environment variable name |
73+
+---------------+-----------------------+---------------------------+
74+
| ``a`` | False | ``A_B_C`` |
75+
+---------------+-----------------------+---------------------------+
76+
| ``a`` | True | ``A_B`` |
77+
+---------------+-----------------------+---------------------------+
78+
| ``a=D`` | False | ``D_B_C`` |
79+
+---------------+-----------------------+---------------------------+
80+
| ``a=D`` | True | ``D_B`` |
81+
+---------------+-----------------------+---------------------------+
82+
| ``a/b`` | False | ``B_C`` |
83+
+---------------+-----------------------+---------------------------+
84+
| ``a/b`` | True | ``B`` |
85+
+---------------+-----------------------+---------------------------+
86+
| ``a/b=D`` | False | ``D_C`` |
87+
+---------------+-----------------------+---------------------------+
88+
| ``a/b=D`` | True | ``D`` |
89+
+---------------+-----------------------+---------------------------+
90+
| ``a/b:c`` | True or False | ``C`` |
91+
+---------------+-----------------------+---------------------------+
92+
| ``a/b:c=D`` | True or False | ``D`` |
93+
+---------------+-----------------------+---------------------------+
9494

9595
Recommended setup
9696
-----------------
@@ -112,7 +112,7 @@ Your call would look like:
112112

113113
.. code:: console
114114
115-
$ vault-cli env --omit-single-key --path myapp -- myapp
115+
$ vault-cli env --omit-single-key --envvar myapp -- myapp
116116
117117
Ignoring errors
118118
---------------
@@ -123,7 +123,7 @@ even if it will be missing some secrets.
123123

124124
.. code:: console
125125
126-
$ vault-cli env --path myapp --force -- myapp
126+
$ vault-cli env --envvar myapp --force -- myapp
127127
128128
.. warning::
129129

docs/howto/read.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ can write the secret to a specific file:
7474
with ways to write on ephemeral storage, and check your umask__ and the permissions
7575
of the created file. See :ref:`SystemD` for safe integration strategies.
7676

77+
.. note::
78+
79+
``vault-cli env`` also lets you to write secrets to a file just before launching
80+
an arbitrary command.
81+
7782
.. __: https://en.wikipedia.org/wiki/Umask
7883

7984

docs/howto/ssh.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,5 @@ If you need to have both ssh access and secrets as environment variables (see
3838
3939
$ # If your key is not passphrase-protected
4040
$ vault-cli ssh --key path/to/ssh_private_key:value \
41-
-- vault-cli env --path myapp \
41+
-- vault-cli env --envvar myapp \
4242
-- myapp_that_needs_secrets_and_ssh

docs/howto/systemd.rst

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ launch the program through ``vault-cli env``. Let’s launch it as a one-off:
5858

5959
.. code:: console
6060
61-
$ vault-cli env --path mysecret:value -- myprogram
61+
$ vault-cli env --envvar mysecret:value -- myprogram
6262
6363
This will make a variable named ``VALUE`` available to ``myprogram``.
6464
See the :ref:`vault-cli env <vault-env>` dedicated page for more details on how you can
@@ -85,7 +85,7 @@ We’ll create an override file that will change ExecStart to wrap it in
8585
# opens a new file for edition
8686
[Service]
8787
ExecStart=
88-
ExecStart=vault-cli env --path mysecret:value=MYVAR -- myprogram --options
88+
ExecStart=vault-cli env --envvar mysecret:value=MYVAR -- myprogram --options
8989
9090
The empty ``ExecStart=`` tells SystemD to ignore the previous command to
9191
launch and only launch the following one.
@@ -97,8 +97,8 @@ Save and quit the file. Load you new configuration file with:
9797
$ sudo systemctl daemon-reload
9898
$ sudo systemctl restart myprogram.service
9999
100-
Writing a single secret to a file before start
101-
----------------------------------------------
100+
Writing secrets to files on the filesystem before start
101+
-------------------------------------------------------
102102

103103
In some cases, you will need to have a file in the filesystem that
104104
contains directly the secret. This is often the case with private keys.
@@ -110,16 +110,16 @@ be written on disk.
110110

111111
.. __: https://en.wikipedia.org/wiki/RAM_drive
112112

113-
In this case, we’ll also create a service override file, but this time,
114-
we will be adding a command that launches before our main command:
113+
In this case, we’ll also create a service override file. We'll add a wrapper
114+
arount our program like before.
115115

116116
.. code:: console
117117
118118
$ sudo systemctl edit myprogram.service
119119
# opens a new file for edition
120120
[Service]
121121
TemporaryFileSystem=/private
122-
ExecStartPre=vault-cli get mysecret --output=/private/path/to/secret/file
122+
ExecStart=vault-cli env --file mysecret:key=/private/path/to/secret/file -- myprogram --options
123123
124124
Save and quit the file. Load your new configuration file with:
125125

@@ -131,7 +131,7 @@ Save and quit the file. Load your new configuration file with:
131131
You will need to configure ``myprogram`` to look for your
132132
secret file at ``/private/path/to/secret/file``.
133133

134-
If you need several files, you can repeat the ``ExecStartPre`` line as
134+
If you need several files, you can add more ``--file`` flags, as
135135
many times as needed.
136136

137137
.. note::
@@ -143,6 +143,15 @@ many times as needed.
143143
Bake secrets into a complex configuration file
144144
----------------------------------------------
145145

146+
.. warning::
147+
148+
It's been reported__ that this approach doesn't work as intended. It's left
149+
for inspiration, but as of today, ``ExecStartPre`` cannot write to the
150+
private filesystem created by ``TemporaryFileSystem`` in way that ``ExecStart``
151+
can later read. Please refer to the ticket for workarounds.
152+
153+
.. __: https://github.com/peopledoc/vault-cli/issues/185
154+
146155
In some cases, the program you want to launch doesn’t accept
147156
configuration through environment but only through configuration files.
148157
You could be tempted to use the method above, but the configuration file

docs/howto/upgrade.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ The following list shows how to update your commands:
3737
(old) vault get path/to/creds
3838
(new) vault get path/to/creds value
3939
40-
(old) vault env --path path/to/creds=FOO -- env # FOO=xxx
41-
(new) vault env --path path/to/creds=FOO -- env # FOO_VALUE=xxx
42-
(new) vault env --path path/to/creds:value=FOO -- env # FOO=xxx
43-
(new) vault env --omit-single_key --path path/to/creds=FOO -- env # FOO=xxx
40+
(old) vault env --envvar path/to/creds=FOO -- env # FOO=xxx
41+
(new) vault env --envvar path/to/creds=FOO -- env # FOO_VALUE=xxx
42+
(new) vault env --envvar path/to/creds:value=FOO -- env # FOO=xxx
43+
(new) vault env --omit-single_key --envvar path/to/creds=FOO -- env # FOO=xxx
4444
4545
The default output of ``vault get-all`` has also changed and is now flat
4646
by default (this behavior is controlled with the ``--flat/--no-flat``

docs/quickstart.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ Let's try it. First we'll launch the command ``env``, which prints the environme
208208

209209
.. code:: console
210210
211-
$ vault-cli env --path demo -- env | tail -1
211+
$ vault-cli env --envvar demo -- env | tail -1
212212
DEMO_BLAKE2_SECRET_KEY=du9dibieNg3lei0teidal9
213213
214214
As you can see, the secrets (or, here, the secret) under the path ``demo`` have been
@@ -238,7 +238,7 @@ Ok, now for the real thing:
238238

239239
.. code:: console
240240
241-
$ vault-cli env --path demo -- ./docs/quickstart_demo.py yay
241+
$ vault-cli env --envvar demo -- ./docs/quickstart_demo.py yay
242242
341c93333a9df726c57671891d6bbea1
243243
244244
**Yay!**

tests/unit/test_cli.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -414,9 +414,9 @@ def test_env_filter_key(cli_runner, vault_with_token, mocker):
414414
cli.cli,
415415
[
416416
"env",
417-
"--path",
417+
"--envvar",
418418
"foo/baz:user=MYNAME",
419-
"--path",
419+
"--envvar",
420420
"foo/baz:password",
421421
"--",
422422
"echo",
@@ -444,6 +444,40 @@ def test_env_omit_single_key(cli_runner, vault_with_token, mocker):
444444
assert kwargs["environment"]["FOO_BAZ"] == "yo"
445445

446446

447+
def test_env_file(cli_runner, vault_with_token, mocker, tmp_path):
448+
mocker.patch("vault_cli.environment.exec_command")
449+
450+
path = tmp_path / "foo"
451+
vault_with_token.db = {"foo/bar": {"value": "yay"}}
452+
cli_runner.invoke(
453+
cli.cli, ["env", "--file", f"foo/bar:value={path}", "--", "echo", "yay"]
454+
)
455+
assert path.read_text() == "yay\n"
456+
457+
458+
def test_env_file_format_error(cli_runner, vault_with_token, mocker, tmp_path):
459+
mocker.patch("vault_cli.environment.exec_command")
460+
461+
vault_with_token.db = {"foo/bar": {"value": "yay"}}
462+
result = cli_runner.invoke(
463+
cli.cli, ["env", "--file", "foo/bar", "--", "echo", "yay"]
464+
)
465+
assert result.exit_code != 0
466+
assert "expects both a vault path and a filesystem path" in result.output
467+
468+
469+
def test_env_file_yaml(cli_runner, vault_with_token, mocker, tmp_path):
470+
mocker.patch("vault_cli.environment.exec_command")
471+
472+
path = tmp_path / "foo"
473+
vault_with_token.db = {"foo/bar": {"value": "yay"}}
474+
cli_runner.invoke(
475+
cli.cli,
476+
["env", "--file", f"foo/bar={path}", "--", "echo", "yay"],
477+
)
478+
assert path.read_text() == "---\nvalue: yay\n"
479+
480+
447481
def test_main(environ, mocker):
448482
mock_cli = mocker.patch("vault_cli.cli.cli")
449483

vault_cli/cli.py

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -465,11 +465,27 @@ def delete(client_obj: client.VaultClientBase, name: str, key: Optional[str]) ->
465465
formerly deprecated. See command help for details.
466466
""",
467467
)
468+
@click.option(
469+
"--file",
470+
multiple=True,
471+
help="""
472+
Write a secret from this path into a file on the filesystem. Expected format is
473+
path/in/vault[:key]=/path/in/filesystem . This option is meant to be used when
474+
your command can only read its inputs from a file and not from the environment (e.g.
475+
secret keys, ...). It's highly recommended to only use the option when provided with
476+
a secure private temporary filesystem. Writing to a physical disk should be avoided
477+
when possible. If the secret a collection (object or array), it's dumped in YAML
478+
format.
479+
""",
480+
)
468481
@click.option(
469482
"-o",
470483
"--omit-single-key/--no-omit-single-key",
471484
default=False,
472-
help="When the secret has only one key, don't use that key to build the name of the environment variable",
485+
help="""
486+
When the secret has only one key, don't use that key to build the name of the
487+
environment variable. This option doesn't affect --file.
488+
""",
473489
)
474490
@click.option(
475491
"-f",
@@ -483,6 +499,7 @@ def delete(client_obj: client.VaultClientBase, name: str, key: Optional[str]) ->
483499
def env(
484500
client_obj: client.VaultClientBase,
485501
envvar: Sequence[str],
502+
file: Sequence[str],
486503
omit_single_key: bool,
487504
force: bool,
488505
command: Sequence[str],
@@ -501,18 +518,19 @@ def env(
501518
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.
502519
Let's say that we have stored the mapping `{'username': 'me', 'password': 'xxx'}` at path `a/b/c`
503520
504-
Using `--path a/b` will inject the following environment variables: B_C_USERNAME and B_C_PASSWORD
505-
Using `--path a/b/c` will inject the following environment variables: C_USERNAME and C_PASSWORD
506-
Using `--path a/b/c:username` will only inject `USERNAME=me` in the environment.
521+
Using `--envvar a/b` will inject the following environment variables: B_C_USERNAME and B_C_PASSWORD
522+
Using `--envvar a/b/c` will inject the following environment variables: C_USERNAME and C_PASSWORD
523+
Using `--envvar a/b/c:username` will only inject `USERNAME=me` in the environment.
507524
508525
You can customize the variable names generation by appending `=SOME_PREFIX` to the path.
509526
In this case the part corresponding to the base path is replace by your prefix.
510527
511-
Using `--path a/b=FOO` will inject the following environment variables: FOO_C_USERNAME and FOO_C_PASSWORD
512-
Using `--path a/b/c=FOO` will inject the following environment variables: FOO_USERNAME and FOO_PASSWORD
513-
Using `--path a/b/c:username=FOO` will inject `FOO=me` in the environment.
528+
Using `--envvar a/b=FOO` will inject the following environment variables: FOO_C_USERNAME and FOO_C_PASSWORD
529+
Using `--envvar a/b/c=FOO` will inject the following environment variables: FOO_USERNAME and FOO_PASSWORD
530+
Using `--envvar a/b/c:username=FOO` will inject `FOO=me` in the environment.
514531
"""
515532
envvars = list(envvar) or []
533+
files = list(file) or []
516534

517535
env_secrets = {}
518536

@@ -534,6 +552,22 @@ def env(
534552

535553
env_secrets.update(env_updates)
536554

555+
for file in files:
556+
path, key, filesystem_path = get_env_parts(file)
557+
if not (path and filesystem_path):
558+
raise click.BadOptionUsage(
559+
"file", "--file expects both a vault path and a filesystem path"
560+
)
561+
secret_obj = client_obj.get_secret(path=path, key=key or None)
562+
563+
if not isinstance(secret_obj, (list, dict)):
564+
secret = str(secret_obj).strip() + "\n"
565+
else:
566+
secret = yaml.safe_dump(
567+
secret_obj, default_flow_style=False, explicit_start=True
568+
)
569+
pathlib.Path(filesystem_path).write_text(secret)
570+
537571
if bool(client_obj.errors) and not force:
538572
raise click.ClickException("There was an error while reading the secrets.")
539573
environment.exec_command(command=command, environment=env_secrets)

0 commit comments

Comments
 (0)