Fix use of --ask-vault-pass argument
Currently, this argument does not work correctly, since the vault password is not passed to kayobe via $KAYOBE_VAULT_PASSWORD, meaning that it cannot update the kolla-ansible passwords.yml file. It also works non-optimally, since every invocation of ansible-playbook will prompt for a password. This change fixes the issue by prompting for a password once in the kayobe CLI, and storing the password in the $KAYOBE_VAULT_PASSWORD environment variable. The kayobe-vault-password-helper command is then used as the --vault-password-file to ansible-playbook, and the helper pulls the password out of the environment. Change-Id: I88b1b7b9e9be15b52e730d353ce1b1a6feacceb8 Story: 2001664 Task: 27009
This commit is contained in:
parent
17c1e316df
commit
358b5c6882
@ -79,6 +79,7 @@ def _get_inventory_path(parsed_args):
|
||||
|
||||
def _validate_args(parsed_args, playbooks):
|
||||
"""Validate Kayobe Ansible arguments."""
|
||||
vault.validate_args(parsed_args)
|
||||
result = utils.is_readable_dir(parsed_args.config_path)
|
||||
if not result["result"]:
|
||||
LOG.error("Kayobe configuration path %s is invalid: %s",
|
||||
@ -124,7 +125,7 @@ def build_args(parsed_args, playbooks,
|
||||
cmd += ["-" + "v" * verbose_level]
|
||||
if parsed_args.list_tasks:
|
||||
cmd += ["--list-tasks"]
|
||||
cmd += vault.build_args(parsed_args)
|
||||
cmd += vault.build_args(parsed_args, "--vault-password-file")
|
||||
inventory = _get_inventory_path(parsed_args)
|
||||
cmd += ["--inventory", inventory]
|
||||
vars_files = _get_vars_files(parsed_args.config_path)
|
||||
@ -152,13 +153,6 @@ def build_args(parsed_args, playbooks,
|
||||
return cmd
|
||||
|
||||
|
||||
def _read_vault_password_file(vault_password_file):
|
||||
"""Return the password from a vault password file."""
|
||||
vault_password = utils.read_file(vault_password_file)
|
||||
vault_password = vault_password.strip()
|
||||
return vault_password
|
||||
|
||||
|
||||
def run_playbooks(parsed_args, playbooks,
|
||||
extra_vars=None, limit=None, tags=None, quiet=False,
|
||||
verbose_level=None, check=None):
|
||||
@ -168,13 +162,7 @@ def run_playbooks(parsed_args, playbooks,
|
||||
extra_vars=extra_vars, limit=limit, tags=tags,
|
||||
verbose_level=verbose_level, check=check)
|
||||
env = os.environ.copy()
|
||||
# If the Vault password has been specified via --vault-password-file,
|
||||
# ensure the environment variable is set, so that it can be referenced by
|
||||
# playbooks to generate the kolla-ansible passwords.yml file.
|
||||
if vault.VAULT_PASSWORD_ENV not in env and parsed_args.vault_password_file:
|
||||
vault_password = _read_vault_password_file(
|
||||
parsed_args.vault_password_file)
|
||||
env[vault.VAULT_PASSWORD_ENV] = vault_password
|
||||
vault.update_environment(parsed_args, env)
|
||||
# If the configuration path has been specified via --config-path, ensure
|
||||
# the environment variable is set, so that it can be referenced by
|
||||
# playbooks.
|
||||
|
@ -19,6 +19,7 @@ import subprocess
|
||||
import sys
|
||||
|
||||
from kayobe import utils
|
||||
from kayobe import vault
|
||||
|
||||
|
||||
DEFAULT_CONFIG_PATH = "/etc/kolla"
|
||||
@ -78,6 +79,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)
|
||||
result = utils.is_readable_dir(parsed_args.kolla_config_path)
|
||||
if not result["result"]:
|
||||
LOG.error("Kolla configuration path %s is invalid: %s",
|
||||
@ -106,8 +108,7 @@ def build_args(parsed_args, command, inventory_filename, extra_vars=None,
|
||||
cmd += ["kolla-ansible", command]
|
||||
if verbose_level:
|
||||
cmd += ["-" + "v" * verbose_level]
|
||||
if parsed_args.vault_password_file:
|
||||
cmd += ["--key", parsed_args.vault_password_file]
|
||||
cmd += vault.build_args(parsed_args, "--key")
|
||||
inventory = _get_inventory_path(parsed_args, inventory_filename)
|
||||
cmd += ["--inventory", inventory]
|
||||
if parsed_args.kolla_config_path != DEFAULT_CONFIG_PATH:
|
||||
@ -144,8 +145,10 @@ def run(parsed_args, command, inventory_filename, extra_vars=None,
|
||||
verbose_level=verbose_level,
|
||||
extra_args=extra_args,
|
||||
limit=limit)
|
||||
env = os.environ.copy()
|
||||
vault.update_environment(parsed_args, env)
|
||||
try:
|
||||
utils.run_command(" ".join(cmd), quiet=quiet, shell=True)
|
||||
utils.run_command(" ".join(cmd), quiet=quiet, shell=True, env=env)
|
||||
except subprocess.CalledProcessError as e:
|
||||
LOG.error("kolla-ansible %s exited %d", command, e.returncode)
|
||||
sys.exit(e.returncode)
|
||||
|
@ -28,12 +28,12 @@ from kayobe import utils
|
||||
from kayobe import vault
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True)
|
||||
class TestCase(unittest.TestCase):
|
||||
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(ansible, "_get_vars_files")
|
||||
@mock.patch.object(ansible, "_validate_args")
|
||||
@mock.patch.dict(os.environ, clear=True)
|
||||
def test_run_playbooks(self, mock_validate, mock_vars, mock_run):
|
||||
mock_vars.return_value = ["/etc/kayobe/vars-file1.yml",
|
||||
"/etc/kayobe/vars-file2.yaml"]
|
||||
@ -58,7 +58,6 @@ class TestCase(unittest.TestCase):
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(ansible, "_get_vars_files")
|
||||
@mock.patch.object(ansible, "_validate_args")
|
||||
@mock.patch.dict(os.environ, clear=True)
|
||||
def test_run_playbooks_all_the_args(self, mock_validate, mock_vars,
|
||||
mock_run):
|
||||
mock_vars.return_value = ["/path/to/config/vars-file1.yml",
|
||||
@ -102,14 +101,15 @@ class TestCase(unittest.TestCase):
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(ansible, "_get_vars_files")
|
||||
@mock.patch.object(ansible, "_validate_args")
|
||||
@mock.patch.dict(os.environ, clear=True)
|
||||
def test_run_playbooks_all_the_long_args(self, mock_validate, mock_vars,
|
||||
mock_run):
|
||||
@mock.patch.object(vault, "_ask_vault_pass")
|
||||
def test_run_playbooks_all_the_long_args(self, mock_ask, mock_validate,
|
||||
mock_vars, mock_run):
|
||||
mock_vars.return_value = ["/path/to/config/vars-file1.yml",
|
||||
"/path/to/config/vars-file2.yaml"]
|
||||
parser = argparse.ArgumentParser()
|
||||
ansible.add_args(parser)
|
||||
vault.add_args(parser)
|
||||
mock_ask.return_value = "test-pass"
|
||||
args = [
|
||||
"--ask-vault-pass",
|
||||
"--become",
|
||||
@ -123,11 +123,12 @@ class TestCase(unittest.TestCase):
|
||||
"--list-tasks",
|
||||
]
|
||||
parsed_args = parser.parse_args(args)
|
||||
mock_run.return_value = "/path/to/kayobe-vault-password-helper"
|
||||
ansible.run_playbooks(parsed_args, ["playbook1.yml", "playbook2.yml"])
|
||||
expected_cmd = [
|
||||
"ansible-playbook",
|
||||
"--list-tasks",
|
||||
"--ask-vault-pass",
|
||||
"--vault-password-file", "/path/to/kayobe-vault-password-helper",
|
||||
"--inventory", "/path/to/inventory",
|
||||
"-e", "@/path/to/config/vars-file1.yml",
|
||||
"-e", "@/path/to/config/vars-file2.yaml",
|
||||
@ -140,20 +141,24 @@ class TestCase(unittest.TestCase):
|
||||
"playbook1.yml",
|
||||
"playbook2.yml",
|
||||
]
|
||||
expected_env = {"KAYOBE_CONFIG_PATH": "/path/to/config"}
|
||||
mock_run.assert_called_once_with(expected_cmd, quiet=False,
|
||||
env=expected_env)
|
||||
expected_env = {"KAYOBE_CONFIG_PATH": "/path/to/config",
|
||||
"KAYOBE_VAULT_PASSWORD": "test-pass"}
|
||||
expected_calls = [
|
||||
mock.call(["which", "kayobe-vault-password-helper"],
|
||||
check_output=True),
|
||||
mock.call(expected_cmd, quiet=False, env=expected_env)
|
||||
]
|
||||
self.assertEqual(expected_calls, mock_run.mock_calls)
|
||||
mock_vars.assert_called_once_with("/path/to/config")
|
||||
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(ansible, "_get_vars_files")
|
||||
@mock.patch.object(ansible, "_validate_args")
|
||||
@mock.patch.object(ansible, "_read_vault_password_file")
|
||||
@mock.patch.dict(os.environ, clear=True)
|
||||
def test_run_playbooks_vault_password_file(self, mock_read, mock_validate,
|
||||
@mock.patch.object(vault, "update_environment")
|
||||
def test_run_playbooks_vault_password_file(self, mock_update,
|
||||
mock_validate,
|
||||
mock_vars, mock_run):
|
||||
mock_vars.return_value = []
|
||||
mock_read.return_value = "test-pass"
|
||||
parser = argparse.ArgumentParser()
|
||||
ansible.add_args(parser)
|
||||
vault.add_args(parser)
|
||||
@ -168,10 +173,10 @@ class TestCase(unittest.TestCase):
|
||||
"--inventory", "/etc/kayobe/inventory",
|
||||
"playbook1.yml",
|
||||
]
|
||||
expected_env = {"KAYOBE_CONFIG_PATH": "/etc/kayobe",
|
||||
"KAYOBE_VAULT_PASSWORD": "test-pass"}
|
||||
expected_env = {"KAYOBE_CONFIG_PATH": "/etc/kayobe"}
|
||||
mock_run.assert_called_once_with(expected_cmd, quiet=False,
|
||||
env=expected_env)
|
||||
mock_update.assert_called_once_with(mock.ANY, expected_env)
|
||||
|
||||
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"},
|
||||
clear=True)
|
||||
@ -204,7 +209,6 @@ class TestCase(unittest.TestCase):
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(ansible, "_get_vars_files")
|
||||
@mock.patch.object(ansible, "_validate_args")
|
||||
@mock.patch.dict(os.environ, clear=True)
|
||||
def test_run_playbooks_vault_ask_and_file(self, mock_validate, mock_vars,
|
||||
mock_run):
|
||||
mock_vars.return_value = []
|
||||
@ -220,7 +224,6 @@ class TestCase(unittest.TestCase):
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(ansible, "_get_vars_files")
|
||||
@mock.patch.object(ansible, "_validate_args")
|
||||
@mock.patch.dict(os.environ, clear=True)
|
||||
def test_run_playbooks_func_args(self, mock_validate, mock_vars, mock_run):
|
||||
mock_vars.return_value = ["/etc/kayobe/vars-file1.yml",
|
||||
"/etc/kayobe/vars-file2.yaml"]
|
||||
@ -263,7 +266,6 @@ class TestCase(unittest.TestCase):
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(ansible, "_get_vars_files")
|
||||
@mock.patch.object(ansible, "_validate_args")
|
||||
@mock.patch.dict(os.environ, clear=True)
|
||||
def test_run_playbooks_failure(self, mock_validate, mock_vars, mock_run):
|
||||
parser = argparse.ArgumentParser()
|
||||
ansible.add_args(parser)
|
||||
@ -387,10 +389,3 @@ class TestCase(unittest.TestCase):
|
||||
mock_is_readable.assert_called_once_with(
|
||||
"/etc/kayobe/ansible/requirements.yml")
|
||||
mock_mkdirs.assert_called_once_with("/etc/kayobe/ansible/roles")
|
||||
|
||||
@mock.patch.object(utils, 'read_file')
|
||||
def test__read_vault_password_file(self, mock_read):
|
||||
mock_read.return_value = "test-pass\n"
|
||||
result = ansible._read_vault_password_file("/path/to/file")
|
||||
self.assertEqual("test-pass", result)
|
||||
mock_read.assert_called_once_with("/path/to/file")
|
||||
|
@ -25,6 +25,7 @@ from kayobe import vault
|
||||
|
||||
|
||||
@mock.patch.object(os, "getcwd", new=lambda: "/path/to/cwd")
|
||||
@mock.patch.dict(os.environ, clear=True)
|
||||
class TestCase(unittest.TestCase):
|
||||
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@ -41,7 +42,8 @@ class TestCase(unittest.TestCase):
|
||||
"--inventory", "/etc/kolla/inventory/overcloud",
|
||||
]
|
||||
expected_cmd = " ".join(expected_cmd)
|
||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False)
|
||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False,
|
||||
env={})
|
||||
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(kolla_ansible, "_validate_args")
|
||||
@ -69,14 +71,17 @@ class TestCase(unittest.TestCase):
|
||||
"--tags", "tag1,tag2",
|
||||
]
|
||||
expected_cmd = " ".join(expected_cmd)
|
||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False)
|
||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False,
|
||||
env={})
|
||||
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(kolla_ansible, "_validate_args")
|
||||
def test_run_all_the_long_args(self, mock_validate, mock_run):
|
||||
@mock.patch.object(vault, "_ask_vault_pass")
|
||||
def test_run_all_the_long_args(self, mock_ask, mock_validate, mock_run):
|
||||
parser = argparse.ArgumentParser()
|
||||
kolla_ansible.add_args(parser)
|
||||
vault.add_args(parser)
|
||||
mock_ask.return_value = "test-pass"
|
||||
args = [
|
||||
"--ask-vault-pass",
|
||||
"--kolla-config-path", "/path/to/config",
|
||||
@ -87,10 +92,12 @@ class TestCase(unittest.TestCase):
|
||||
"--kolla-tags", "tag1,tag2",
|
||||
]
|
||||
parsed_args = parser.parse_args(args)
|
||||
mock_run.return_value = "/path/to/kayobe-vault-password-helper"
|
||||
kolla_ansible.run(parsed_args, "command", "overcloud")
|
||||
expected_cmd = [
|
||||
".", "/path/to/cwd/venvs/kolla-ansible/bin/activate", "&&",
|
||||
"kolla-ansible", "command",
|
||||
"--key", "/path/to/kayobe-vault-password-helper",
|
||||
"--inventory", "/path/to/inventory",
|
||||
"--configdir", "/path/to/config",
|
||||
"--passwords", "/path/to/config/passwords.yml",
|
||||
@ -100,11 +107,19 @@ class TestCase(unittest.TestCase):
|
||||
"--tags", "tag1,tag2",
|
||||
]
|
||||
expected_cmd = " ".join(expected_cmd)
|
||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False)
|
||||
expected_env = {"KAYOBE_VAULT_PASSWORD": "test-pass"}
|
||||
expected_calls = [
|
||||
mock.call(["which", "kayobe-vault-password-helper"],
|
||||
check_output=True),
|
||||
mock.call(expected_cmd, shell=True, quiet=False, env=expected_env)
|
||||
]
|
||||
self.assertEqual(expected_calls, mock_run.mock_calls)
|
||||
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(kolla_ansible, "_validate_args")
|
||||
def test_run_vault_password_file(self, mock_validate, mock_run):
|
||||
@mock.patch.object(vault, "update_environment")
|
||||
def test_run_vault_password_file(self, mock_update, mock_validate,
|
||||
mock_run):
|
||||
parser = argparse.ArgumentParser()
|
||||
kolla_ansible.add_args(parser)
|
||||
vault.add_args(parser)
|
||||
@ -120,12 +135,15 @@ class TestCase(unittest.TestCase):
|
||||
"--inventory", "/etc/kolla/inventory/overcloud",
|
||||
]
|
||||
expected_cmd = " ".join(expected_cmd)
|
||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False)
|
||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False,
|
||||
env={})
|
||||
mock_update.assert_called_once_with(mock.ANY, {})
|
||||
|
||||
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"})
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(kolla_ansible, "_validate_args")
|
||||
def test_run_vault_password_helper(self, mock_vars, mock_run):
|
||||
@mock.patch.object(vault, "update_environment")
|
||||
def test_run_vault_password_helper(self, mock_update, mock_vars, mock_run):
|
||||
mock_vars.return_value = []
|
||||
parser = argparse.ArgumentParser()
|
||||
mock_run.return_value = "/path/to/kayobe-vault-password-helper"
|
||||
@ -143,7 +161,10 @@ class TestCase(unittest.TestCase):
|
||||
"--inventory", "/etc/kolla/inventory/overcloud",
|
||||
]
|
||||
expected_cmd = " ".join(expected_cmd)
|
||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False)
|
||||
expected_env = {"KAYOBE_VAULT_PASSWORD": "test-pass"}
|
||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False,
|
||||
env=expected_env)
|
||||
mock_update.assert_called_once_with(mock.ANY, expected_env)
|
||||
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(kolla_ansible, "_validate_args")
|
||||
@ -174,7 +195,8 @@ class TestCase(unittest.TestCase):
|
||||
"--arg1", "--arg2",
|
||||
]
|
||||
expected_cmd = " ".join(expected_cmd)
|
||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False)
|
||||
mock_run.assert_called_once_with(expected_cmd, shell=True, quiet=False,
|
||||
env={})
|
||||
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(kolla_ansible, "_validate_args")
|
||||
|
104
kayobe/tests/unit/test_vault.py
Normal file
104
kayobe/tests/unit/test_vault.py
Normal file
@ -0,0 +1,104 @@
|
||||
# Copyright (c) 2018 StackHPC Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
from kayobe import utils
|
||||
from kayobe import vault
|
||||
|
||||
|
||||
class TestCase(unittest.TestCase):
|
||||
|
||||
def test_validate_args_ok(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
vault.add_args(parser)
|
||||
parsed_args = parser.parse_args([])
|
||||
vault.validate_args(parsed_args)
|
||||
|
||||
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"})
|
||||
def test_validate_args_env(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
vault.add_args(parser)
|
||||
parsed_args = parser.parse_args([])
|
||||
vault.validate_args(parsed_args)
|
||||
|
||||
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"})
|
||||
def test_validate_args_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)
|
||||
|
||||
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"})
|
||||
def test_validate_args_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)
|
||||
|
||||
@mock.patch.object(vault.getpass, 'getpass')
|
||||
def test__ask_vault_pass(self, mock_getpass):
|
||||
mock_getpass.return_value = 'test-pass'
|
||||
|
||||
# Call twice to verify that the user is only prompted once.
|
||||
result = vault._ask_vault_pass()
|
||||
self.assertEqual('test-pass', result)
|
||||
mock_getpass.assert_called_once_with("Vault password: ")
|
||||
|
||||
result = vault._ask_vault_pass()
|
||||
self.assertEqual('test-pass', result)
|
||||
mock_getpass.assert_called_once_with("Vault password: ")
|
||||
|
||||
@mock.patch.object(utils, 'read_file')
|
||||
def test__read_vault_password_file(self, mock_read):
|
||||
mock_read.return_value = "test-pass\n"
|
||||
result = vault._read_vault_password_file("/path/to/file")
|
||||
self.assertEqual("test-pass", result)
|
||||
mock_read.assert_called_once_with("/path/to/file")
|
||||
|
||||
def test_update_environment_no_vault(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
vault.add_args(parser)
|
||||
parsed_args = parser.parse_args([])
|
||||
env = {}
|
||||
vault.update_environment(parsed_args, env)
|
||||
self.assertEqual({}, env)
|
||||
|
||||
@mock.patch.object(vault, '_ask_vault_pass')
|
||||
def test_update_environment_prompt(self, mock_ask):
|
||||
mock_ask.return_value = "test-pass"
|
||||
parser = argparse.ArgumentParser()
|
||||
vault.add_args(parser)
|
||||
parsed_args = parser.parse_args(["--ask-vault-pass"])
|
||||
env = {}
|
||||
vault.update_environment(parsed_args, env)
|
||||
self.assertEqual({"KAYOBE_VAULT_PASSWORD": "test-pass"}, env)
|
||||
mock_ask.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(vault, '_read_vault_password_file')
|
||||
def test_update_environment_file(self, mock_read):
|
||||
mock_read.return_value = "test-pass"
|
||||
parser = argparse.ArgumentParser()
|
||||
vault.add_args(parser)
|
||||
args = ["--vault-password-file", "/path/to/file"]
|
||||
parsed_args = parser.parse_args(args)
|
||||
env = {}
|
||||
vault.update_environment(parsed_args, env)
|
||||
self.assertEqual({"KAYOBE_VAULT_PASSWORD": "test-pass"}, env)
|
||||
mock_read.assert_called_once_with("/path/to/file")
|
101
kayobe/vault.py
101
kayobe/vault.py
@ -12,15 +12,30 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import getpass
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from kayobe import utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
VAULT_PASSWORD_ENV = "KAYOBE_VAULT_PASSWORD"
|
||||
|
||||
|
||||
def _get_vault_password_helper():
|
||||
"""Return the path to the kayobe-vault-password-helper executable."""
|
||||
cmd = ["which", "kayobe-vault-password-helper"]
|
||||
try:
|
||||
output = utils.run_command(cmd, check_output=True)
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
return output.strip()
|
||||
|
||||
|
||||
def _get_default_vault_password_file():
|
||||
"""Return the default value for the vault password file argument.
|
||||
|
||||
@ -29,12 +44,7 @@ def _get_default_vault_password_file():
|
||||
"""
|
||||
if not os.getenv(VAULT_PASSWORD_ENV):
|
||||
return None
|
||||
cmd = ["which", "kayobe-vault-password-helper"]
|
||||
try:
|
||||
output = utils.run_command(cmd, check_output=True)
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
return output.strip()
|
||||
return _get_vault_password_helper()
|
||||
|
||||
|
||||
def add_args(parser):
|
||||
@ -48,11 +58,82 @@ def add_args(parser):
|
||||
help="vault password file")
|
||||
|
||||
|
||||
def build_args(parsed_args):
|
||||
def build_args(parsed_args, password_file_arg_name):
|
||||
"""Build a list of command line arguments for use with ansible-playbook."""
|
||||
cmd = []
|
||||
vault_password_file = None
|
||||
if parsed_args.ask_vault_pass:
|
||||
cmd += ["--ask-vault-pass"]
|
||||
vault_password_file = _get_vault_password_helper()
|
||||
elif parsed_args.vault_password_file:
|
||||
cmd += ["--vault-password-file", parsed_args.vault_password_file]
|
||||
vault_password_file = parsed_args.vault_password_file
|
||||
|
||||
cmd = []
|
||||
if vault_password_file:
|
||||
cmd += [password_file_arg_name, vault_password_file]
|
||||
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
|
||||
|
||||
helper = _get_vault_password_helper()
|
||||
invalid_arg = None
|
||||
if parsed_args.ask_vault_pass:
|
||||
invalid_arg = "--ask-vault-pass"
|
||||
elif parsed_args.vault_password_file != helper:
|
||||
invalid_arg = "--vault-password-file"
|
||||
|
||||
if invalid_arg:
|
||||
LOG.error("Cannot specify %s when $%s is specified" %
|
||||
(invalid_arg, VAULT_PASSWORD_ENV))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _ask_vault_pass():
|
||||
"""Prompt the user for a Vault password.
|
||||
|
||||
The first time this function is called, the user is prompted for a
|
||||
password. To avoid prompting the user multiple times per invocation of
|
||||
kayobe, we cache the password and return it without prompting on subsequent
|
||||
calls.
|
||||
|
||||
:return: The password entered by the user.
|
||||
"""
|
||||
if not hasattr(_ask_vault_pass, "password"):
|
||||
password = getpass.getpass("Vault password: ")
|
||||
setattr(_ask_vault_pass, "password", password)
|
||||
return getattr(_ask_vault_pass, "password")
|
||||
|
||||
|
||||
def _read_vault_password_file(vault_password_file):
|
||||
"""Return the password from a vault password file."""
|
||||
vault_password = utils.read_file(vault_password_file)
|
||||
vault_password = vault_password.strip()
|
||||
return vault_password
|
||||
|
||||
|
||||
def update_environment(parsed_args, env):
|
||||
"""Update environment variables with the vault password if necessary.
|
||||
|
||||
: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 VAULT_PASSWORD_ENV in env:
|
||||
return
|
||||
|
||||
vault_password = None
|
||||
if parsed_args.ask_vault_pass:
|
||||
vault_password = _ask_vault_pass()
|
||||
elif parsed_args.vault_password_file:
|
||||
vault_password = _read_vault_password_file(
|
||||
parsed_args.vault_password_file)
|
||||
|
||||
if vault_password is not None:
|
||||
env[VAULT_PASSWORD_ENV] = vault_password
|
||||
|
7
releasenotes/notes/ask-vault-pass-b6ced0220384dde1.yaml
Normal file
7
releasenotes/notes/ask-vault-pass-b6ced0220384dde1.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
Fixes an issue with the ``--ask-vault-pass`` argument, where Kayobe would
|
||||
fail to generate the Kolla Ansible ``passwords.yml`` file. Also ensures
|
||||
that the user is only prompted for the password once per execution of
|
||||
kayobe.
|
Loading…
Reference in New Issue
Block a user