diff --git a/bin/ansible b/bin/ansible index 7e767b2f7db..aa6941a7c26 100755 --- a/bin/ansible +++ b/bin/ansible @@ -124,17 +124,8 @@ class Cli(object): (sshpass, sudopass, su_pass, vault_pass) = utils.ask_passwords(ask_pass=options.ask_pass, ask_sudo_pass=options.ask_sudo_pass, ask_su_pass=options.ask_su_pass, ask_vault_pass=options.ask_vault_pass) # read vault_pass from a file - if options.vault_password_file: - this_path = os.path.expanduser(options.vault_password_file) - try: - f = open(this_path, "rb") - tmp_vault_pass=f.read().strip() - f.close() - except (OSError, IOError), e: - raise errors.AnsibleError("Could not read %s: %s" % (this_path, e)) - - if not options.ask_vault_pass: - vault_pass = tmp_vault_pass + if not options.ask_vault_pass and options.vault_password_file: + vault_pass = utils.read_vault_file(options.vault_password_file) inventory_manager = inventory.Inventory(options.inventory, vault_password=vault_pass) if options.subset: diff --git a/bin/ansible-playbook b/bin/ansible-playbook index 149a9f1c6ef..3d05d8e29c8 100755 --- a/bin/ansible-playbook +++ b/bin/ansible-playbook @@ -120,17 +120,9 @@ def main(args): options.sudo_user = options.sudo_user or C.DEFAULT_SUDO_USER options.su_user = options.su_user or C.DEFAULT_SU_USER - if options.vault_password_file: - this_path = os.path.expanduser(options.vault_password_file) - try: - f = open(this_path, "rb") - tmp_vault_pass=f.read().strip() - f.close() - except (OSError, IOError), e: - raise errors.AnsibleError("Could not read %s: %s" % (this_path, e)) - - if not options.ask_vault_pass: - vault_pass = tmp_vault_pass + # read vault_pass from a file + if not options.ask_vault_pass and options.vault_password_file: + vault_pass = utils.read_vault_file(options.vault_password_file) extra_vars = {} for extra_vars_opt in options.extra_vars: diff --git a/docsite/rst/playbooks_vault.rst b/docsite/rst/playbooks_vault.rst index 991c58f16ce..934ca150820 100644 --- a/docsite/rst/playbooks_vault.rst +++ b/docsite/rst/playbooks_vault.rst @@ -83,12 +83,16 @@ To run a playbook that contains vault-encrypted data files, you must pass one of This prompt will then be used to decrypt (in memory only) any vault encrypted files that are accessed. Currently this requires that all passwords be encrypted with the same password. -Alternatively, passwords can be specified with a file. If this is done, be careful to ensure permissions on the file are such that no one else can access your key, and do not add your key to source control:: +Alternatively, passwords can be specified with a file or a script. If this is done, be careful to ensure permissions on the file are such that no one else can access your key, and do not add your key to source control:: ansible-playbook site.yml --vault-password-file ~/.vault_pass.txt + ansible-playbook site.yml --vault-password-file ~/.vault_pass.py + The password should be a string stored as a single line in the file. +If you are using a script instead of a flat file, ensure that it is marked as executable, and that the password is printed to STDOUT. If your script needs to prompt for data, prompts can be sent to STDERR. + This is likely something you may wish to do if using Ansible from a continuous integration system like Jenkins. (The `--vault-password-file` option can also be used with the :ref:`ansible-pull` command if you wish, though this would require distributing the keys to your nodes, so understand the implications -- vault is more intended for push mode). diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py index c2364f80ab2..fb3e2420e95 100644 --- a/lib/ansible/constants.py +++ b/lib/ansible/constants.py @@ -120,6 +120,7 @@ DEFAULT_SUDO_USER = get_config(p, DEFAULTS, 'sudo_user', 'ANSIBLE DEFAULT_ASK_SUDO_PASS = get_config(p, DEFAULTS, 'ask_sudo_pass', 'ANSIBLE_ASK_SUDO_PASS', False, boolean=True) DEFAULT_REMOTE_PORT = get_config(p, DEFAULTS, 'remote_port', 'ANSIBLE_REMOTE_PORT', None, integer=True) DEFAULT_ASK_VAULT_PASS = get_config(p, DEFAULTS, 'ask_vault_pass', 'ANSIBLE_ASK_VAULT_PASS', False, boolean=True) +DEFAULT_VAULT_PASSWORD_FILE = shell_expand_path(get_config(p, DEFAULTS, 'vault_password_file', 'ANSIBLE_VAULT_PASSWORD_FILE', None)) DEFAULT_TRANSPORT = get_config(p, DEFAULTS, 'transport', 'ANSIBLE_TRANSPORT', 'smart') DEFAULT_SCP_IF_SSH = get_config(p, 'ssh_connection', 'scp_if_ssh', 'ANSIBLE_SCP_IF_SSH', False, boolean=True) DEFAULT_MANAGED_STR = get_config(p, DEFAULTS, 'ansible_managed', None, 'Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host}') diff --git a/lib/ansible/utils/__init__.py b/lib/ansible/utils/__init__.py index f04b4df5c21..f5f879d2a7c 100644 --- a/lib/ansible/utils/__init__.py +++ b/lib/ansible/utils/__init__.py @@ -45,6 +45,7 @@ import traceback import getpass import sys import json +import subprocess from vault import VaultLib @@ -153,6 +154,32 @@ def decrypt(key, msg): # UTILITY FUNCTIONS FOR COMMAND LINE TOOLS ############################################################### +def read_vault_file(vault_password_file): + """Read a vault password from a file or if executable, execute the script and + retrieve password from STDOUT + """ + if vault_password_file: + this_path = os.path.realpath(os.path.expanduser(vault_password_file)) + if is_executable(this_path): + try: + # STDERR not captured to make it easier for users to prompt for input in their scripts + p = subprocess.Popen(this_path, stdout=subprocess.PIPE) + except OSError, e: + raise errors.AnsibleError("problem running %s (%s)" % (' '.join(this_path), e)) + stdout, stderr = p.communicate() + vault_pass = stdout.strip('\r\n') + else: + try: + f = open(this_path, "rb") + vault_pass=f.read().strip() + f.close() + except (OSError, IOError), e: + raise errors.AnsibleError("Could not read %s: %s" % (this_path, e)) + + return vault_pass + else: + return None + def err(msg): ''' print an error message to stderr ''' @@ -797,8 +824,8 @@ def base_parser(constants=C, usage="", output_opts=False, runas_opts=False, help='ask for su password') parser.add_option('--ask-vault-pass', default=False, dest='ask_vault_pass', action='store_true', help='ask for vault password') - parser.add_option('--vault-password-file', default=None, dest='vault_password_file', - help="vault password file") + parser.add_option('--vault-password-file', default=constants.DEFAULT_VAULT_PASSWORD_FILE, + dest='vault_password_file', help="vault password file") parser.add_option('--list-hosts', dest='listhosts', action='store_true', help='outputs a list of matching hosts; does not execute anything else') parser.add_option('-M', '--module-path', dest='module_path',