diff --git a/lib/ansible/modules/commands/script.py b/lib/ansible/modules/commands/script.py index fc69a89e9e..f1c2babea6 100644 --- a/lib/ansible/modules/commands/script.py +++ b/lib/ansible/modules/commands/script.py @@ -56,6 +56,8 @@ notes: author: - Ansible Core Team - Michael DeHaan +extends_documentation_fragment: + - decrypt """ EXAMPLES = ''' diff --git a/lib/ansible/modules/files/assemble.py b/lib/ansible/modules/files/assemble.py index 3ff02d2c39..a36529d97b 100644 --- a/lib/ansible/modules/files/assemble.py +++ b/lib/ansible/modules/files/assemble.py @@ -96,6 +96,7 @@ options: author: "Stephen Fromm (@sfromm)" extends_documentation_fragment: - files + - decrypt ''' EXAMPLES = ''' diff --git a/lib/ansible/modules/files/copy.py b/lib/ansible/modules/files/copy.py index b9f9248468..7ae519b230 100644 --- a/lib/ansible/modules/files/copy.py +++ b/lib/ansible/modules/files/copy.py @@ -98,6 +98,7 @@ options: extends_documentation_fragment: - files - validate + - decrypt author: - "Ansible Core Team" - "Michael DeHaan" diff --git a/lib/ansible/modules/files/unarchive.py b/lib/ansible/modules/files/unarchive.py index a573a5b76f..c44a3d8284 100644 --- a/lib/ansible/modules/files/unarchive.py +++ b/lib/ansible/modules/files/unarchive.py @@ -31,7 +31,7 @@ DOCUMENTATION = ''' module: unarchive version_added: 1.4 short_description: Unpacks an archive after (optionally) copying it from the local machine. -extends_documentation_fragment: files +extends_documentation_fragment: [files, decrypt] description: - The C(unarchive) module unpacks an archive. By default, it will copy the source file from the local system to the target before unpacking. Set remote_src=yes to unpack an archive which already exists on the target. diff --git a/lib/ansible/parsing/dataloader.py b/lib/ansible/parsing/dataloader.py index 9022de381a..db94c47449 100644 --- a/lib/ansible/parsing/dataloader.py +++ b/lib/ansible/parsing/dataloader.py @@ -372,7 +372,7 @@ class DataLoader: f.close() return content_tempfile - def get_real_file(self, file_path): + def get_real_file(self, file_path, decrypt=True): """ If the file is vault encrypted return a path to a temporary decrypted file If the file is not encrypted then the path is returned @@ -392,22 +392,23 @@ class DataLoader: real_path = self.path_dwim(file_path) try: - with open(to_bytes(real_path), 'rb') as f: - # Limit how much of the file is read since we do not know - # whether this is a vault file and therefore it could be very - # large. - if is_encrypted_file(f, count=len(b_HEADER)): - # if the file is encrypted and no password was specified, - # the decrypt call would throw an error, but we check first - # since the decrypt function doesn't know the file name - data = f.read() - if not self._b_vault_password: - raise AnsibleParserError("A vault password must be specified to decrypt %s" % file_path) + if decrypt: + with open(to_bytes(real_path), 'rb') as f: + # Limit how much of the file is read since we do not know + # whether this is a vault file and therefore it could be very + # large. + if is_encrypted_file(f, count=len(b_HEADER)): + # if the file is encrypted and no password was specified, + # the decrypt call would throw an error, but we check first + # since the decrypt function doesn't know the file name + data = f.read() + if not self._b_vault_password: + raise AnsibleParserError("A vault password must be specified to decrypt %s" % file_path) - data = self._vault.decrypt(data, filename=real_path) - # Make a temp file - real_path = self._create_content_tempfile(data) - self._tempfiles.add(real_path) + data = self._vault.decrypt(data, filename=real_path) + # Make a temp file + real_path = self._create_content_tempfile(data) + self._tempfiles.add(real_path) return real_path diff --git a/lib/ansible/plugins/action/assemble.py b/lib/ansible/plugins/action/assemble.py index 1121bb937f..de22b06a18 100644 --- a/lib/ansible/plugins/action/assemble.py +++ b/lib/ansible/plugins/action/assemble.py @@ -36,7 +36,7 @@ class ActionModule(ActionBase): TRANSFERS_FILES = True - def _assemble_from_fragments(self, src_path, delimiter=None, compiled_regexp=None, ignore_hidden=False): + def _assemble_from_fragments(self, src_path, delimiter=None, compiled_regexp=None, ignore_hidden=False, decrypt=True): ''' assemble a file from a directory of fragments ''' tmpfd, temp_path = tempfile.mkstemp() @@ -51,7 +51,7 @@ class ActionModule(ActionBase): if not os.path.isfile(fragment) or (ignore_hidden and os.path.basename(fragment).startswith('.')): continue - fragment_content = open(self._loader.get_real_file(fragment), 'rb').read() + fragment_content = open(self._loader.get_real_file(fragment, decrypt=decrypt), 'rb').read() # always put a newline between fragments if the previous fragment didn't end with a newline. if add_newline: @@ -97,6 +97,7 @@ class ActionModule(ActionBase): regexp = self._task.args.get('regexp', None) follow = self._task.args.get('follow', False) ignore_hidden = self._task.args.get('ignore_hidden', False) + decrypt = self._task.args.get('decrypt', True) if src is None or dest is None: result['failed'] = True @@ -127,7 +128,7 @@ class ActionModule(ActionBase): _re = re.compile(regexp) # Does all work assembling the file - path = self._assemble_from_fragments(src, delimiter, _re, ignore_hidden) + path = self._assemble_from_fragments(src, delimiter, _re, ignore_hidden, decrypt) path_checksum = checksum_s(path) dest = self._remote_expand_user(dest) @@ -139,7 +140,7 @@ class ActionModule(ActionBase): new_module_args = self._task.args.copy() # clean assemble specific options - for opt in ['remote_src', 'regexp', 'delimiter', 'ignore_hidden']: + for opt in ['remote_src', 'regexp', 'delimiter', 'ignore_hidden', 'decrypt']: if opt in new_module_args: del new_module_args[opt] diff --git a/lib/ansible/plugins/action/copy.py b/lib/ansible/plugins/action/copy.py index 3395ec39f8..de9d512f46 100644 --- a/lib/ansible/plugins/action/copy.py +++ b/lib/ansible/plugins/action/copy.py @@ -50,6 +50,7 @@ class ActionModule(ActionBase): force = boolean(self._task.args.get('force', 'yes')) remote_src = boolean(self._task.args.get('remote_src', False)) follow = boolean(self._task.args.get('follow', False)) + decrypt = boolean(self._task.args.get('decrypt', True)) result['failed'] = True if (source is None and content is None) or dest is None: @@ -157,7 +158,7 @@ class ActionModule(ActionBase): # If the local file does not exist, get_real_file() raises AnsibleFileNotFound try: - source_full = self._loader.get_real_file(source_full) + source_full = self._loader.get_real_file(source_full, decrypt=decrypt) except AnsibleFileNotFound as e: result['failed'] = True result['msg'] = "could not find src=%s, %s" % (source_full, e) @@ -255,8 +256,11 @@ class ActionModule(ActionBase): original_basename=source_rel, ) ) - if 'content' in new_module_args: - del new_module_args['content'] + + # remove action plugin only keys + for key in ('content', 'decrypt'): + if key in new_module_args: + del new_module_args[key] module_return = self._execute_module(module_name='copy', module_args=new_module_args, task_vars=task_vars, diff --git a/lib/ansible/plugins/action/script.py b/lib/ansible/plugins/action/script.py index 32960d4ca8..d0f358a54b 100644 --- a/lib/ansible/plugins/action/script.py +++ b/lib/ansible/plugins/action/script.py @@ -69,7 +69,7 @@ class ActionModule(ActionBase): args = ' '.join(parts[1:]) try: - source = self._loader.get_real_file(self._find_needle('files', source)) + source = self._loader.get_real_file(self._find_needle('files', source), decrypt=self._task.args.get('decrypt', True)) except AnsibleError as e: return dict(failed=True, msg=to_native(e)) diff --git a/lib/ansible/plugins/action/unarchive.py b/lib/ansible/plugins/action/unarchive.py index 001f63cc56..17b9393798 100644 --- a/lib/ansible/plugins/action/unarchive.py +++ b/lib/ansible/plugins/action/unarchive.py @@ -42,6 +42,7 @@ class ActionModule(ActionBase): dest = self._task.args.get('dest', None) remote_src = boolean(self._task.args.get('remote_src', False)) creates = self._task.args.get('creates', None) + decrypt = self._task.args.get('decrypt', True) # "copy" is deprecated in favor of "remote_src". if 'copy' in self._task.args: @@ -77,7 +78,7 @@ class ActionModule(ActionBase): if not remote_src: try: - source = self._loader.get_real_file(self._find_needle('files', source)) + source = self._loader.get_real_file(self._find_needle('files', source), decrypt=decrypt) except AnsibleError: result['failed'] = True result['msg'] = to_native(get_exception()) @@ -126,6 +127,11 @@ class ActionModule(ActionBase): ), ) + # remove action plugin only key + for key in ('remote_src', 'decrypt'): + if key in new_module_args: + del new_module_args[key] + # execute the unarchive module now, with the updated args result.update(self._execute_module(module_args=new_module_args, task_vars=task_vars)) self._remove_tmp_path(tmp) diff --git a/lib/ansible/utils/module_docs_fragments/decrypt.py b/lib/ansible/utils/module_docs_fragments/decrypt.py new file mode 100644 index 0000000000..cacec76794 --- /dev/null +++ b/lib/ansible/utils/module_docs_fragments/decrypt.py @@ -0,0 +1,31 @@ +# (c) 2017, Brian Coca +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = """ +options: + decrypt: + description: + - This option controls the autodecryption of source files using vault. + required: false + choices: ['Yes', 'No'] + default: 'Yes' + version_added: "2.4" +"""