Add support for ANSIBLE_VAULT_PASSWORD_FILE
Story: 2006766 Task: 37281 Change-Id: I53fac9ac5cfb17729bf854bd9e16373dc9c2efe2
This commit is contained in:
parent
05a2c1acbd
commit
4661cf7410
@ -31,18 +31,22 @@ can be activated by generating and then sourcing the bash completion script::
|
||||
Working with Ansible Vault
|
||||
--------------------------
|
||||
|
||||
If Ansible vault has been used to encrypt Kayobe configuration files, it will
|
||||
If Ansible Vault has been used to encrypt Kayobe configuration files, it will
|
||||
be necessary to provide the ``kayobe`` command with access to vault password.
|
||||
There are three options for doing this:
|
||||
There are four options for doing this:
|
||||
|
||||
Prompt
|
||||
Use ``kayobe --ask-vault-pass`` to prompt for the password.
|
||||
File
|
||||
Use ``kayobe --vault-password-file <file>`` to read the password from a
|
||||
(plain text) file.
|
||||
Environment variable
|
||||
Environment variable: ``KAYOBE_VAULT_PASSWORD``
|
||||
Export the environment variable ``KAYOBE_VAULT_PASSWORD`` to read the
|
||||
password from the environment.
|
||||
Environment variable: ``ANSIBLE_VAULT_PASSWORD_FILE``
|
||||
Export the environment variable ``ANSIBLE_VAULT_PASSWORD_FILE`` to read the
|
||||
password from a (plain text) file, with the path to that file being read
|
||||
from the environment.
|
||||
|
||||
Limiting Hosts
|
||||
--------------
|
||||
|
@ -112,7 +112,7 @@ def _get_inventories_paths(parsed_args, env_path):
|
||||
|
||||
def _validate_args(parsed_args, playbooks):
|
||||
"""Validate Kayobe Ansible arguments."""
|
||||
vault.validate_args(parsed_args)
|
||||
vault.enforce_single_password_source(parsed_args)
|
||||
result = utils.is_readable_dir(parsed_args.config_path)
|
||||
if not result["result"]:
|
||||
LOG.error("Kayobe configuration path %s is invalid: %s",
|
||||
|
@ -81,7 +81,7 @@ def _get_inventory_path(parsed_args, inventory_filename):
|
||||
|
||||
def _validate_args(parsed_args, inventory_filename):
|
||||
"""Validate Kayobe Ansible arguments."""
|
||||
vault.validate_args(parsed_args)
|
||||
vault.enforce_single_password_source(parsed_args)
|
||||
result = utils.is_readable_dir(parsed_args.kolla_config_path)
|
||||
if not result["result"]:
|
||||
LOG.error("Kolla configuration path %s is invalid: %s",
|
||||
|
@ -34,33 +34,53 @@ class TestCase(unittest.TestCase):
|
||||
universal_newlines=True)
|
||||
self.assertEqual('fake-password', result)
|
||||
|
||||
def test_validate_args_ok(self):
|
||||
def test_enforce_single_password_source_ok(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
vault.add_args(parser)
|
||||
parsed_args = parser.parse_args([])
|
||||
vault.validate_args(parsed_args)
|
||||
vault.enforce_single_password_source(parsed_args)
|
||||
|
||||
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"})
|
||||
def test_validate_args_env(self):
|
||||
def test_enforce_single_password_source_env_kayobe(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
vault.add_args(parser)
|
||||
parsed_args = parser.parse_args([])
|
||||
vault.validate_args(parsed_args)
|
||||
vault.enforce_single_password_source(parsed_args)
|
||||
|
||||
@mock.patch.dict(os.environ, {"ANSIBLE_VAULT_PASSWORD_FILE":
|
||||
"/path/to/file"})
|
||||
def test_enforce_single_password_source_env_ansible(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
vault.add_args(parser)
|
||||
parsed_args = parser.parse_args([])
|
||||
vault.enforce_single_password_source(parsed_args)
|
||||
|
||||
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"})
|
||||
def test_validate_args_ask_vault_pass(self):
|
||||
def test_enforce_single_password_source_ask_vault_pass(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
vault.add_args(parser)
|
||||
parsed_args = parser.parse_args(["--ask-vault-pass"])
|
||||
self.assertRaises(SystemExit, vault.validate_args, parsed_args)
|
||||
self.assertRaises(SystemExit, vault.enforce_single_password_source,
|
||||
parsed_args)
|
||||
|
||||
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"})
|
||||
def test_validate_args_vault_password_file(self):
|
||||
def test_enforce_single_password_source_vault_password_file(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
vault.add_args(parser)
|
||||
parsed_args = parser.parse_args(["--vault-password-file",
|
||||
"/path/to/file"])
|
||||
self.assertRaises(SystemExit, vault.validate_args, parsed_args)
|
||||
self.assertRaises(SystemExit, vault.enforce_single_password_source,
|
||||
parsed_args)
|
||||
|
||||
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass",
|
||||
"ANSIBLE_VAULT_PASSWORD_FILE":
|
||||
"/path/to/file"})
|
||||
def test_enforce_single_password_source_vault_both_env(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
vault.add_args(parser)
|
||||
parsed_args = parser.parse_args([])
|
||||
self.assertRaises(SystemExit, vault.enforce_single_password_source,
|
||||
parsed_args)
|
||||
|
||||
@mock.patch.object(vault.getpass, 'getpass')
|
||||
def test__ask_vault_pass(self, mock_getpass):
|
||||
@ -102,7 +122,7 @@ class TestCase(unittest.TestCase):
|
||||
mock_ask.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(vault, '_read_vault_password_file')
|
||||
def test_update_environment_file(self, mock_read):
|
||||
def test_update_environment_file_arg(self, mock_read):
|
||||
mock_read.return_value = "test-pass"
|
||||
parser = argparse.ArgumentParser()
|
||||
vault.add_args(parser)
|
||||
@ -112,3 +132,17 @@ class TestCase(unittest.TestCase):
|
||||
vault.update_environment(parsed_args, env)
|
||||
self.assertEqual({"KAYOBE_VAULT_PASSWORD": "test-pass"}, env)
|
||||
mock_read.assert_called_once_with("/path/to/file")
|
||||
|
||||
@mock.patch.dict(os.environ, {"ANSIBLE_VAULT_PASSWORD_FILE":
|
||||
"/path/to/file"})
|
||||
@mock.patch.object(vault, '_read_vault_password_file')
|
||||
def test_update_environment_file_env(self, mock_read):
|
||||
mock_read.return_value = "test-pass"
|
||||
parser = argparse.ArgumentParser()
|
||||
vault.add_args(parser)
|
||||
parsed_args = parser.parse_args([])
|
||||
env = {"ANSIBLE_VAULT_PASSWORD_FILE": "/path/to/file"}
|
||||
vault.update_environment(parsed_args, env)
|
||||
self.assertEqual({"KAYOBE_VAULT_PASSWORD": "test-pass",
|
||||
"ANSIBLE_VAULT_PASSWORD_FILE": "/path/to/file"}, env)
|
||||
mock_read.assert_called_once_with("/path/to/file")
|
||||
|
@ -24,6 +24,7 @@ from kayobe import utils
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
VAULT_PASSWORD_ENV = "KAYOBE_VAULT_PASSWORD"
|
||||
VAULT_PASSWORD_FILE_ENV = "ANSIBLE_VAULT_PASSWORD_FILE"
|
||||
|
||||
|
||||
def _get_vault_password_helper():
|
||||
@ -45,7 +46,8 @@ def _get_default_vault_password_file():
|
||||
It is possible to use an environment variable to avoid typing the vault
|
||||
password.
|
||||
"""
|
||||
if not os.getenv(VAULT_PASSWORD_ENV):
|
||||
if (VAULT_PASSWORD_ENV not in os.environ and
|
||||
VAULT_PASSWORD_FILE_ENV not in os.environ):
|
||||
return None
|
||||
return _get_vault_password_helper()
|
||||
|
||||
@ -75,23 +77,48 @@ def build_args(parsed_args, password_file_arg_name):
|
||||
return cmd
|
||||
|
||||
|
||||
def validate_args(parsed_args):
|
||||
"""Validate command line arguments."""
|
||||
# Ensure that a password prompt or file has not been requested if the
|
||||
# password environment variable is set.
|
||||
if VAULT_PASSWORD_ENV not in os.environ:
|
||||
return
|
||||
def _validate_environment_variables():
|
||||
"""Verify that only one password environment variable is set"""
|
||||
|
||||
invalid_source = None
|
||||
password_env_var = None
|
||||
if VAULT_PASSWORD_ENV in os.environ:
|
||||
password_env_var = VAULT_PASSWORD_ENV
|
||||
if VAULT_PASSWORD_FILE_ENV in os.environ:
|
||||
invalid_source = "$" + VAULT_PASSWORD_FILE_ENV
|
||||
elif (VAULT_PASSWORD_FILE_ENV in os.environ):
|
||||
password_env_var = VAULT_PASSWORD_FILE_ENV
|
||||
return invalid_source, password_env_var
|
||||
|
||||
|
||||
def _validate_args(parsed_args):
|
||||
"""Verify that no conflicting arguments are being used"""
|
||||
|
||||
helper = _get_vault_password_helper()
|
||||
invalid_arg = None
|
||||
invalid_source = None
|
||||
if parsed_args.ask_vault_pass:
|
||||
invalid_arg = "--ask-vault-pass"
|
||||
invalid_source = "--ask-vault-pass"
|
||||
elif parsed_args.vault_password_file != helper:
|
||||
invalid_arg = "--vault-password-file"
|
||||
invalid_source = "--vault-password-file"
|
||||
return invalid_source
|
||||
|
||||
if invalid_arg:
|
||||
|
||||
def enforce_single_password_source(parsed_args):
|
||||
"""Verify that a password is only being received from a single source"""
|
||||
# Ensure that a password prompt or file has not been requested if a
|
||||
# password environment variable is set, and that only one password
|
||||
# environment variable is set
|
||||
|
||||
invalid_source, password_env_var = _validate_environment_variables()
|
||||
if not password_env_var:
|
||||
return
|
||||
|
||||
if not invalid_source and password_env_var:
|
||||
invalid_source = _validate_args(parsed_args)
|
||||
|
||||
if invalid_source:
|
||||
LOG.error("Cannot specify %s when $%s is specified" %
|
||||
(invalid_arg, VAULT_PASSWORD_ENV))
|
||||
(invalid_source, password_env_var))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@ -124,15 +151,20 @@ def update_environment(parsed_args, env):
|
||||
:param parsed_args: Parsed command line arguments.
|
||||
:params env: Dict of environment variables to update.
|
||||
"""
|
||||
# If the Vault password has been specified via --vault-password-file, or a
|
||||
# prompt has been requested via --ask-vault-pass, ensure the environment
|
||||
# variable is set, so that it can be referenced by playbooks to generate
|
||||
# the kolla-ansible passwords.yml file.
|
||||
# If the Vault password has been specified via --vault-password-file, a
|
||||
# prompt has been requested via --ask-vault-pass, or the
|
||||
# $ANSIBLE_VAULT_PASSWORD_FILE environment variable is set, ensure the
|
||||
# $KAYOBE_PASSWORD_ENV environment variable is set, so that it can be
|
||||
# referenced by playbooks to generate the kolla-ansible passwords.yml
|
||||
# file.
|
||||
if VAULT_PASSWORD_ENV in env:
|
||||
return
|
||||
|
||||
vault_password = None
|
||||
if parsed_args.ask_vault_pass:
|
||||
if VAULT_PASSWORD_FILE_ENV in os.environ:
|
||||
vault_password = _read_vault_password_file(
|
||||
os.environ[VAULT_PASSWORD_FILE_ENV])
|
||||
elif parsed_args.ask_vault_pass:
|
||||
vault_password = _ask_vault_pass()
|
||||
elif parsed_args.vault_password_file:
|
||||
vault_password = _read_vault_password_file(
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds support for the ANSIBLE_VAULT_PASSWORD_FILE environment variable as a
|
||||
source for the Ansible Vault password. See `story 2006766
|
||||
<https://storyboard.openstack.org/#!/story/2006766>`__ for details.
|
Loading…
Reference in New Issue
Block a user