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
|
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.
|
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
|
Prompt
|
||||||
Use ``kayobe --ask-vault-pass`` to prompt for the password.
|
Use ``kayobe --ask-vault-pass`` to prompt for the password.
|
||||||
File
|
File
|
||||||
Use ``kayobe --vault-password-file <file>`` to read the password from a
|
Use ``kayobe --vault-password-file <file>`` to read the password from a
|
||||||
(plain text) file.
|
(plain text) file.
|
||||||
Environment variable
|
Environment variable: ``KAYOBE_VAULT_PASSWORD``
|
||||||
Export the environment variable ``KAYOBE_VAULT_PASSWORD`` to read the
|
Export the environment variable ``KAYOBE_VAULT_PASSWORD`` to read the
|
||||||
password from the environment.
|
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
|
Limiting Hosts
|
||||||
--------------
|
--------------
|
||||||
|
@ -112,7 +112,7 @@ def _get_inventories_paths(parsed_args, env_path):
|
|||||||
|
|
||||||
def _validate_args(parsed_args, playbooks):
|
def _validate_args(parsed_args, playbooks):
|
||||||
"""Validate Kayobe Ansible arguments."""
|
"""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)
|
result = utils.is_readable_dir(parsed_args.config_path)
|
||||||
if not result["result"]:
|
if not result["result"]:
|
||||||
LOG.error("Kayobe configuration path %s is invalid: %s",
|
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):
|
def _validate_args(parsed_args, inventory_filename):
|
||||||
"""Validate Kayobe Ansible arguments."""
|
"""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)
|
result = utils.is_readable_dir(parsed_args.kolla_config_path)
|
||||||
if not result["result"]:
|
if not result["result"]:
|
||||||
LOG.error("Kolla configuration path %s is invalid: %s",
|
LOG.error("Kolla configuration path %s is invalid: %s",
|
||||||
|
@ -34,33 +34,53 @@ class TestCase(unittest.TestCase):
|
|||||||
universal_newlines=True)
|
universal_newlines=True)
|
||||||
self.assertEqual('fake-password', result)
|
self.assertEqual('fake-password', result)
|
||||||
|
|
||||||
def test_validate_args_ok(self):
|
def test_enforce_single_password_source_ok(self):
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
vault.add_args(parser)
|
vault.add_args(parser)
|
||||||
parsed_args = parser.parse_args([])
|
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"})
|
@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()
|
parser = argparse.ArgumentParser()
|
||||||
vault.add_args(parser)
|
vault.add_args(parser)
|
||||||
parsed_args = parser.parse_args([])
|
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"})
|
@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()
|
parser = argparse.ArgumentParser()
|
||||||
vault.add_args(parser)
|
vault.add_args(parser)
|
||||||
parsed_args = parser.parse_args(["--ask-vault-pass"])
|
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"})
|
@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()
|
parser = argparse.ArgumentParser()
|
||||||
vault.add_args(parser)
|
vault.add_args(parser)
|
||||||
parsed_args = parser.parse_args(["--vault-password-file",
|
parsed_args = parser.parse_args(["--vault-password-file",
|
||||||
"/path/to/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')
|
@mock.patch.object(vault.getpass, 'getpass')
|
||||||
def test__ask_vault_pass(self, mock_getpass):
|
def test__ask_vault_pass(self, mock_getpass):
|
||||||
@ -102,7 +122,7 @@ class TestCase(unittest.TestCase):
|
|||||||
mock_ask.assert_called_once_with()
|
mock_ask.assert_called_once_with()
|
||||||
|
|
||||||
@mock.patch.object(vault, '_read_vault_password_file')
|
@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"
|
mock_read.return_value = "test-pass"
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
vault.add_args(parser)
|
vault.add_args(parser)
|
||||||
@ -112,3 +132,17 @@ class TestCase(unittest.TestCase):
|
|||||||
vault.update_environment(parsed_args, env)
|
vault.update_environment(parsed_args, env)
|
||||||
self.assertEqual({"KAYOBE_VAULT_PASSWORD": "test-pass"}, env)
|
self.assertEqual({"KAYOBE_VAULT_PASSWORD": "test-pass"}, env)
|
||||||
mock_read.assert_called_once_with("/path/to/file")
|
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__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
VAULT_PASSWORD_ENV = "KAYOBE_VAULT_PASSWORD"
|
VAULT_PASSWORD_ENV = "KAYOBE_VAULT_PASSWORD"
|
||||||
|
VAULT_PASSWORD_FILE_ENV = "ANSIBLE_VAULT_PASSWORD_FILE"
|
||||||
|
|
||||||
|
|
||||||
def _get_vault_password_helper():
|
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
|
It is possible to use an environment variable to avoid typing the vault
|
||||||
password.
|
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 None
|
||||||
return _get_vault_password_helper()
|
return _get_vault_password_helper()
|
||||||
|
|
||||||
@ -75,23 +77,48 @@ def build_args(parsed_args, password_file_arg_name):
|
|||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
def validate_args(parsed_args):
|
def _validate_environment_variables():
|
||||||
"""Validate command line arguments."""
|
"""Verify that only one password environment variable is set"""
|
||||||
# Ensure that a password prompt or file has not been requested if the
|
|
||||||
# password environment variable is set.
|
invalid_source = None
|
||||||
if VAULT_PASSWORD_ENV not in os.environ:
|
password_env_var = None
|
||||||
return
|
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()
|
helper = _get_vault_password_helper()
|
||||||
invalid_arg = None
|
invalid_source = None
|
||||||
if parsed_args.ask_vault_pass:
|
if parsed_args.ask_vault_pass:
|
||||||
invalid_arg = "--ask-vault-pass"
|
invalid_source = "--ask-vault-pass"
|
||||||
elif parsed_args.vault_password_file != helper:
|
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" %
|
LOG.error("Cannot specify %s when $%s is specified" %
|
||||||
(invalid_arg, VAULT_PASSWORD_ENV))
|
(invalid_source, password_env_var))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@ -124,15 +151,20 @@ def update_environment(parsed_args, env):
|
|||||||
:param parsed_args: Parsed command line arguments.
|
:param parsed_args: Parsed command line arguments.
|
||||||
:params env: Dict of environment variables to update.
|
:params env: Dict of environment variables to update.
|
||||||
"""
|
"""
|
||||||
# If the Vault password has been specified via --vault-password-file, or a
|
# If the Vault password has been specified via --vault-password-file, a
|
||||||
# prompt has been requested via --ask-vault-pass, ensure the environment
|
# prompt has been requested via --ask-vault-pass, or the
|
||||||
# variable is set, so that it can be referenced by playbooks to generate
|
# $ANSIBLE_VAULT_PASSWORD_FILE environment variable is set, ensure the
|
||||||
# the kolla-ansible passwords.yml file.
|
# $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:
|
if VAULT_PASSWORD_ENV in env:
|
||||||
return
|
return
|
||||||
|
|
||||||
vault_password = None
|
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()
|
vault_password = _ask_vault_pass()
|
||||||
elif parsed_args.vault_password_file:
|
elif parsed_args.vault_password_file:
|
||||||
vault_password = _read_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