Support encryption of configuration using Ansible Vault
This commit is contained in:
parent
cb7ed2f48c
commit
f06483eb68
@ -102,6 +102,18 @@ Site Localisation and Customisation
|
||||
Site localisation and customisation is applied using Ansible extra-vars files
|
||||
in ``${KAYOBE_CONFIG_PATH}/*.yml``.
|
||||
|
||||
Encryption of Secrets
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Kayobe supports the use of `Ansible vault
|
||||
<http://docs.ansible.com/ansible/playbooks_vault.html>`_ to encrypt sensitive
|
||||
information in its configuration. The ``ansible-vault`` tool should be used to
|
||||
manage individual files for which encryption is required. Any of the
|
||||
configuration files may be encrypted. Since encryption can make working with
|
||||
Kayobe difficult, it is recommended to follow `best practice
|
||||
<http://docs.ansible.com/ansible/playbooks_best_practices.html#best-practices-for-variables-and-vaults>`_,
|
||||
adding a layer of indirection and using encryption only where necessary.
|
||||
|
||||
Command Line Interface
|
||||
======================
|
||||
|
||||
@ -128,6 +140,22 @@ can be activated by generating and then sourcing the bash completion script::
|
||||
(kayobe-venv) $ kayobe complete > kayobe-complete
|
||||
(kayobe-venv) $ source kayobe-complete
|
||||
|
||||
Working with Ansible Vault
|
||||
--------------------------
|
||||
|
||||
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:
|
||||
|
||||
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
|
||||
Export the environment variable ``KAYOBE_VAULT_PASSWORD`` to read the
|
||||
password from the environment.
|
||||
|
||||
Ansible Control Host
|
||||
====================
|
||||
|
||||
|
@ -27,12 +27,34 @@ DEFAULT_CONFIG_PATH = "/etc/kayobe"
|
||||
|
||||
CONFIG_PATH_ENV = "KAYOBE_CONFIG_PATH"
|
||||
|
||||
VAULT_PASSWORD_ENV = "KAYOBE_VAULT_PASSWORD"
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_default_vault_password_file():
|
||||
"""Return the default value for the vault password file argument.
|
||||
|
||||
It is possible to use an environment variable to avoid typing the vault
|
||||
password.
|
||||
"""
|
||||
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()
|
||||
|
||||
|
||||
def add_args(parser):
|
||||
"""Add arguments required for running Ansible playbooks to a parser."""
|
||||
default_config_path = os.getenv(CONFIG_PATH_ENV, DEFAULT_CONFIG_PATH)
|
||||
default_vault_password_file = _get_default_vault_password_file()
|
||||
vault = parser.add_mutually_exclusive_group()
|
||||
vault.add_argument("--ask-vault-pass", action="store_true",
|
||||
help="ask for vault password")
|
||||
parser.add_argument("-b", "--become", action="store_true",
|
||||
help="run operations with become (nopasswd implied)")
|
||||
parser.add_argument("-C", "--check", action="store_true",
|
||||
@ -57,6 +79,9 @@ def add_args(parser):
|
||||
parser.add_argument("-t", "--tags", metavar="TAGS",
|
||||
help="only run plays and tasks tagged with these "
|
||||
"values")
|
||||
vault.add_argument("--vault-password-file", metavar="VAULT_PASSWORD_FILE",
|
||||
default=default_vault_password_file,
|
||||
help="vault password file")
|
||||
|
||||
|
||||
def _get_inventory_path(parsed_args):
|
||||
@ -108,6 +133,10 @@ def build_args(parsed_args, playbooks,
|
||||
cmd = ["ansible-playbook"]
|
||||
if verbose_level:
|
||||
cmd += ["-" + "v" * verbose_level]
|
||||
if parsed_args.ask_vault_pass:
|
||||
cmd += ["--ask-vault-pass"]
|
||||
elif parsed_args.vault_password_file:
|
||||
cmd += ["--vault-password-file", parsed_args.vault_password_file]
|
||||
inventory = _get_inventory_path(parsed_args)
|
||||
cmd += ["--inventory", inventory]
|
||||
vars_files = _get_vars_files(parsed_args.config_path)
|
||||
|
26
kayobe/cmd/kayobe_vault_password_helper.py
Normal file
26
kayobe/cmd/kayobe_vault_password_helper.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Copyright (c) 2017 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.
|
||||
|
||||
from __future__ import print_function
|
||||
import os
|
||||
|
||||
|
||||
VAULT_PASSWORD_ENV = "KAYOBE_VAULT_PASSWORD"
|
||||
|
||||
|
||||
def main():
|
||||
"""Helper script to allow specification of vault password via env."""
|
||||
password = os.getenv(VAULT_PASSWORD_ENV)
|
||||
if password:
|
||||
print(password)
|
@ -94,6 +94,7 @@ class TestCase(unittest.TestCase):
|
||||
parser = argparse.ArgumentParser()
|
||||
ansible.add_args(parser)
|
||||
args = [
|
||||
"--ask-vault-pass",
|
||||
"--become",
|
||||
"--check",
|
||||
"--config-path", "/path/to/config",
|
||||
@ -106,6 +107,7 @@ class TestCase(unittest.TestCase):
|
||||
ansible.run_playbooks(parsed_args, ["playbook1.yml", "playbook2.yml"])
|
||||
expected_cmd = [
|
||||
"ansible-playbook",
|
||||
"--ask-vault-pass",
|
||||
"--inventory", "/path/to/inventory",
|
||||
"-e", "@/path/to/config/vars-file1.yml",
|
||||
"-e", "@/path/to/config/vars-file2.yaml",
|
||||
@ -120,6 +122,64 @@ class TestCase(unittest.TestCase):
|
||||
mock_run.assert_called_once_with(expected_cmd, quiet=False)
|
||||
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")
|
||||
def test_run_playbooks_vault_password_file(self, mock_validate, mock_vars,
|
||||
mock_run):
|
||||
mock_vars.return_value = []
|
||||
parser = argparse.ArgumentParser()
|
||||
ansible.add_args(parser)
|
||||
args = [
|
||||
"--vault-password-file", "/path/to/vault/pw",
|
||||
]
|
||||
parsed_args = parser.parse_args(args)
|
||||
ansible.run_playbooks(parsed_args, ["playbook1.yml"])
|
||||
expected_cmd = [
|
||||
"ansible-playbook",
|
||||
"--vault-password-file", "/path/to/vault/pw",
|
||||
"--inventory", "/etc/kayobe/inventory",
|
||||
"playbook1.yml",
|
||||
]
|
||||
mock_run.assert_called_once_with(expected_cmd, quiet=False)
|
||||
|
||||
@mock.patch.dict(os.environ, {"KAYOBE_VAULT_PASSWORD": "test-pass"})
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(ansible, "_get_vars_files")
|
||||
@mock.patch.object(ansible, "_validate_args")
|
||||
def test_run_playbooks_vault_password_helper(self, mock_validate,
|
||||
mock_vars, mock_run):
|
||||
mock_vars.return_value = []
|
||||
parser = argparse.ArgumentParser()
|
||||
mock_run.return_value = "/path/to/kayobe-vault-password-helper"
|
||||
ansible.add_args(parser)
|
||||
mock_run.assert_called_once_with(
|
||||
["which", "kayobe-vault-password-helper"], check_output=True)
|
||||
mock_run.reset_mock()
|
||||
parsed_args = parser.parse_args([])
|
||||
ansible.run_playbooks(parsed_args, ["playbook1.yml"])
|
||||
expected_cmd = [
|
||||
"ansible-playbook",
|
||||
"--vault-password-file", "/path/to/kayobe-vault-password-helper",
|
||||
"--inventory", "/etc/kayobe/inventory",
|
||||
"playbook1.yml",
|
||||
]
|
||||
mock_run.assert_called_once_with(expected_cmd, quiet=False)
|
||||
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(ansible, "_get_vars_files")
|
||||
@mock.patch.object(ansible, "_validate_args")
|
||||
def test_run_playbooks_vault_ask_and_file(self, mock_validate, mock_vars,
|
||||
mock_run):
|
||||
mock_vars.return_value = []
|
||||
parser = argparse.ArgumentParser()
|
||||
ansible.add_args(parser)
|
||||
args = [
|
||||
"--ask-vault-pass",
|
||||
"--vault-password-file", "/path/to/vault/pw",
|
||||
]
|
||||
self.assertRaises(SystemExit, parser.parse_args, args)
|
||||
|
||||
@mock.patch.object(utils, "run_command")
|
||||
@mock.patch.object(ansible, "_get_vars_files")
|
||||
@mock.patch.object(ansible, "_validate_args")
|
||||
|
3
setup.py
3
setup.py
@ -48,7 +48,8 @@ setup(
|
||||
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'kayobe = kayobe.cmd.kayobe:main'
|
||||
'kayobe = kayobe.cmd.kayobe:main',
|
||||
'kayobe-vault-password-helper = kayobe.cmd.kayobe_vault_password_helper:main',
|
||||
],
|
||||
'kayobe.cli': [
|
||||
'control_host_bootstrap = kayobe.cli.commands:ControlHostBootstrap',
|
||||
|
Loading…
Reference in New Issue
Block a user