Skip to content

Commit

Permalink
Added support for ES_VAULT_PASSWORD env var
Browse files Browse the repository at this point in the history
Signed-off-by: Andreas Maier <andreas.r.maier@gmx.de>
  • Loading branch information
andy-maier committed Apr 2, 2021
1 parent 68c1d06 commit 85fed07
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 26 deletions.
82 changes: 64 additions & 18 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ The servers and groups are identified with user-defined nicknames.
.. _`Using the es_server fixture`:

Using the es_server fixture
----------------------------------
---------------------------

If your pytest test function uses the :func:`~pytest_easy_server.es_server`
fixture, the test function will be invoked for the server specified in the
Expand Down Expand Up @@ -202,12 +202,48 @@ defaults:
Default: The default from the server file.
.. _`Protecting secrets`:
.. _`Running pytest as a developer`:

Protecting secrets
------------------
Running pytest as a developer
-----------------------------

There are two kinds of secrets here:
When running pytest with this plugin on your local system, you are prompted
(in the command line) for the vault password upon first use of a particular
vault file. The password is then stored in the keyring service of your local
system to avoid future such prompts.

The section :ref:`Running pytest in a CI/CD system` describes the use of
an environment variable to store the password which avoids the password prompt.
For security reasons, you should not use this approach when you run pytest.
The one password prompt can be afforded, and subsequent retrieval of the vault
password from the keyring service avoids further prompts.


.. _`Running pytest in a CI/CD system`:

Running pytest in a CI/CD system
--------------------------------

When running pytest in a CI/CD system, you must set an environment variable
named "ES_VAULT_PASSWORD" to the vault password.

This can be done in a secure way by defining a corresponding secret in the
CI/CD system and in our test run configuration (e.g. GitHub Actions workflow)
setting that environment variable to the CI/CD system secret. State of the
art CI/CD systems support storing secrets in a secure manner.

The pytest plugin picks up the vault password from the "ES_VAULT_PASSWORD"
environment variable and that causes it not to prompt for the password and
also not to store it in the keyring service (of the system used for the test
run in the CI/CD system).


.. _`Security aspects`:

Security aspects
----------------

There are two kinds of secrets involved:

* The secrets in the vault file.
* The vault password.
Expand All @@ -219,19 +255,29 @@ store the vault file in a repository, make sure it is encrypted.

The vault password is protected in the following ways:

* For local use on your system, you are prompted for the vault password upon
first use of the vault. The easy-vault package then stores the vault password
in the keyring facility of your local system, to avoid future such prompts.

* For use in a CI/CD system, you can define a secret in the CI/CD system that
holds the vault password. Most CI/CD systems support storing secrets in
a secure manner. The password secret is then put into an environment variable
named "ES_VAULT_PASSWORD" where the pytest plugin picks it up from.

You should not use the approach with the environment variable on your local
system at least not when you set the variable in a script, because then the
script has the clear text vault password. Always use the prompting approach
on your local system.
* When running pytest with this plugin on your local system, there is no
permanent storage of the vault password anywhere except in the keyring
service of your local system. There are no commands that take the vault
password in their command line. The way the password gets specified is only
in a password prompt, and then it is immediately stored in the keyring
service and in future pytest runs retrieved from there.

Your Python test programs of course can get at the secrets from the vault
file (that is the whole idea after all). They can also get at the vault
password by using the keyring service access functions but they have no need
to deal with the vault password. In any case, make sure that the test
functions do not print or log the vault secrets.

* When running pytest in a CI/CD system, the "ES_VAULT_PASSWORD" environment
variable that needs to be set can be set from a secret stored in the CI/CD
system. State of the art CI/CD systems go a long way of ensuring that
these secrets cannot simply be echoed or otherwise revealed.

The keyring service provides access to the secrets stored there, for the user
that is authorized to access it. The details on how this is done depend on
the particular keyring service used and its configuration. For example,
on macOS the keyring service occasionally require entering the password of
the macOS user.


.. _`Derived Pytest fixtures`:
Expand Down
37 changes: 29 additions & 8 deletions pytest_easy_server/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@

DEFAULT_SERVER_FILE = 'es_server.yml'

DOCS_LINK = 'https://pytest-easy-server.readthedocs.io/'
PLUGIN_NAME = 'pytest-easy-server'
PLUGIN_DOCS_LINK = 'https://pytest-easy-server.readthedocs.io/'
VAULT_PASSWORD_VAR = 'ES_VAULT_PASSWORD'


def pytest_addoption(parser):
Expand All @@ -32,10 +34,9 @@ def pytest_addoption(parser):
group.addoption() supports the same arguments as argparse.add_argument().
"""

group = parser.getgroup('pytest-easy-server')
group.description = "pytest-easy-server - " \
"Pytest plugin for easy testing against servers, " \
"see {}".format(DOCS_LINK)
group = parser.getgroup(PLUGIN_NAME)
group.description = "{} - Pytest plugin for easy testing against " \
"servers, see {}".format(PLUGIN_NAME, PLUGIN_DOCS_LINK)

group.addoption(
'--es-file',
Expand Down Expand Up @@ -87,16 +88,36 @@ def pytest_generate_tests(metafunc):
es_nickname = config.getvalue('es_nickname')

if config.getvalue('verbose'):
print("\npytest-easy-server: Using server file {fn}".
format(fn=es_file))
print("\n{p}: Using server file {fn}".
format(p=PLUGIN_NAME, fn=es_file))

es_vault_password = os.getenv(VAULT_PASSWORD_VAR)
if es_vault_password:
# Assuming headless CI/CD mode
sf_kwargs = dict(
password=es_vault_password,
use_keyring=False,
use_prompting=False)
if config.getvalue('verbose'):
print("{p}: Using vault password from {v} environment "
"variable.".format(p=PLUGIN_NAME, v=VAULT_PASSWORD_VAR))
else:
# Assuming interactive mode
sf_kwargs = dict(
password=None,
use_keyring=True,
use_prompting=True)
if config.getvalue('verbose'):
print("{p}: Using vault password from prompt or keyring "
"service.".format(p=PLUGIN_NAME))

# The following constructs place the pytest.exit() call outside of the
# exception handling which avoids the well-known exception traceback
# "During handling of the above exception, ...".

exit_message = None
try:
esf_obj = easy_server.ServerFile(es_file)
esf_obj = easy_server.ServerFile(es_file, **sf_kwargs)
except easy_server.ServerFileException as exc:
exit_message = str(exc)
if exit_message:
Expand Down

0 comments on commit 85fed07

Please sign in to comment.