diff options
Diffstat (limited to 'lib/ansible')
| -rw-r--r-- | lib/ansible/constants.py | 1 | ||||
| -rw-r--r-- | lib/ansible/plugins/action/__init__.py | 93 | ||||
| -rw-r--r-- | lib/ansible/plugins/action/assemble.py | 10 | ||||
| -rw-r--r-- | lib/ansible/plugins/action/async.py | 17 | ||||
| -rw-r--r-- | lib/ansible/plugins/action/copy.py | 12 | ||||
| -rw-r--r-- | lib/ansible/plugins/action/patch.py | 9 | ||||
| -rw-r--r-- | lib/ansible/plugins/action/script.py | 12 | ||||
| -rw-r--r-- | lib/ansible/plugins/action/template.py | 6 | ||||
| -rw-r--r-- | lib/ansible/plugins/action/unarchive.py | 15 | ||||
| -rw-r--r-- | lib/ansible/plugins/shell/__init__.py | 35 |
10 files changed, 155 insertions, 55 deletions
diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py index 365bda04d0..ef3ffd4d02 100644 --- a/lib/ansible/constants.py +++ b/lib/ansible/constants.py @@ -165,6 +165,7 @@ DEFAULT_VAR_COMPRESSION_LEVEL = get_config(p, DEFAULTS, 'var_compression_level', # disclosure DEFAULT_NO_LOG = get_config(p, DEFAULTS, 'no_log', 'ANSIBLE_NO_LOG', False, boolean=True) DEFAULT_NO_TARGET_SYSLOG = get_config(p, DEFAULTS, 'no_target_syslog', 'ANSIBLE_NO_TARGET_SYSLOG', False, boolean=True) +ALLOW_WORLD_READABLE_TMPFILES = get_config(p, DEFAULTS, 'allow_world_readable_tmpfiles', None, False, boolean=True) # selinux DEFAULT_SELINUX_SPECIAL_FS = get_config(p, 'selinux', 'special_context_filesystems', None, 'fuse, nfs, vboxsf, ramfs', islist=True) diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py index 8666329fff..2ba0650de3 100644 --- a/lib/ansible/plugins/action/__init__.py +++ b/lib/ansible/plugins/action/__init__.py @@ -192,7 +192,7 @@ class ActionBase(with_metaclass(ABCMeta, object)): return True return False - def _make_tmp_path(self): + def _make_tmp_path(self, remote_user): ''' Create and return a temporary path on a remote box. ''' @@ -200,12 +200,10 @@ class ActionBase(with_metaclass(ABCMeta, object)): basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) use_system_tmp = False - if self._play_context.become and self._play_context.become_user != 'root': + if self._play_context.become and self._play_context.become_user not in ('root', remote_user): use_system_tmp = True - tmp_mode = None - if self._play_context.remote_user != 'root' or self._play_context.become and self._play_context.become_user != 'root': - tmp_mode = 0o755 + tmp_mode = 0o700 cmd = self._connection._shell.mkdtemp(basefile, use_system_tmp, tmp_mode) result = self._low_level_execute_command(cmd, sudoable=False) @@ -255,6 +253,10 @@ class ActionBase(with_metaclass(ABCMeta, object)): # If ssh breaks we could leave tmp directories out on the remote system. self._low_level_execute_command(cmd, sudoable=False) + def _transfer_file(self, local_path, remote_path): + self._connection.put_file(local_path, remote_path) + return remote_path + def _transfer_data(self, remote_path, data): ''' Copies the module data out to the temporary module path. @@ -269,25 +271,85 @@ class ActionBase(with_metaclass(ABCMeta, object)): data = to_bytes(data, errors='strict') afo.write(data) except Exception as e: - #raise AnsibleError("failure encoding into utf-8: %s" % str(e)) raise AnsibleError("failure writing module data to temporary file for transfer: %s" % str(e)) afo.flush() afo.close() try: - self._connection.put_file(afile, remote_path) + self._transfer_file(afile, remote_path) finally: os.unlink(afile) return remote_path - def _remote_chmod(self, mode, path, sudoable=False): + def _fixup_perms(self, remote_path, remote_user, execute=False, recursive=True): + """ + If the become_user is unprivileged and different from the + remote_user then we need to make the files we've uploaded readable by them. + """ + if remote_path is None: + # Sometimes code calls us naively -- it has a var which could + # contain a path to a tmp dir but doesn't know if it needs to + # exist or not. If there's no path, then there's no need for us + # to do work + self._display.debug('_fixup_perms called with remote_path==None. Sure this is correct?') + return remote_path + + if self._play_context.become and self._play_context.become_user not in ('root', remote_user): + # Unprivileged user that's different than the ssh user. Let's get + # to work! + if remote_user == 'root': + # SSh'ing as root, therefore we can chown + self._remote_chown(remote_path, self._play_context.become_user, recursive=recursive) + if execute: + # root can read things that don't have read bit but can't + # execute them. + self._remote_chmod('u+x', remote_path, recursive=recursive) + else: + if execute: + mode = 'rx' + else: + mode = 'rX' + # Try to use fs acls to solve this problem + res = self._remote_set_user_facl(remote_path, self._play_context.become_user, mode, recursive=recursive, sudoable=False) + if res['rc'] != 0: + if C.ALLOW_WORLD_READABLE_TMPFILES: + # fs acls failed -- do things this insecure way only + # if the user opted in in the config file + self._display.warning('Using world-readable permissions for temporary files Ansible needs to create when becoming an unprivileged user which may be insecure. For information on securing this, see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user') + self._remote_chmod('a+%s' % mode, remote_path, recursive=recursive) + else: + raise AnsibleError('Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user. For information on working around this, see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user') + elif execute: + # Can't depend on the file being transferred with execute + # permissions. Only need user perms because no become was + # used here + self._remote_chmod('u+x', remote_path, recursive=recursive) + + return remote_path + + def _remote_chmod(self, mode, path, recursive=True, sudoable=False): ''' Issue a remote chmod command ''' + cmd = self._connection._shell.chmod(mode, path, recursive=recursive) + res = self._low_level_execute_command(cmd, sudoable=sudoable) + return res + + def _remote_chown(self, path, user, group=None, recursive=True, sudoable=False): + ''' + Issue a remote chown command + ''' + cmd = self._connection._shell.chown(path, user, group, recursive=recursive) + res = self._low_level_execute_command(cmd, sudoable=sudoable) + return res - cmd = self._connection._shell.chmod(mode, path) + def _remote_set_user_facl(self, path, user, mode, recursive=True, sudoable=False): + ''' + Issue a remote call to setfacl + ''' + cmd = self._connection._shell.set_user_facl(path, user, mode, recursive=recursive) res = self._low_level_execute_command(cmd, sudoable=sudoable) return res @@ -417,6 +479,9 @@ class ActionBase(with_metaclass(ABCMeta, object)): else: module_args['_ansible_check_mode'] = False + # Get the connection user for permission checks + remote_user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user + # set no log in the module arguments, if required module_args['_ansible_no_log'] = self._play_context.no_log or C.DEFAULT_NO_TARGET_SYSLOG @@ -437,7 +502,7 @@ class ActionBase(with_metaclass(ABCMeta, object)): remote_module_path = None args_file_path = None if not tmp and self._late_needs_tmp_path(tmp, module_style): - tmp = self._make_tmp_path() + tmp = self._make_tmp_path(remote_user) if tmp: remote_module_filename = self._connection._shell.get_remote_filename(module_name) @@ -462,11 +527,9 @@ class ActionBase(with_metaclass(ABCMeta, object)): environment_string = self._compute_environment_string() - if tmp and "tmp" in tmp and self._play_context.become and self._play_context.become_user != 'root': - # deal with possible umask issues once sudo'ed to other user - self._remote_chmod('a+r', remote_module_path) - if args_file_path is not None: - self._remote_chmod('a+r', args_file_path) + # Fix permissions of the tmp path and tmp files. This should be + # called after all files have been transferred. + self._fixup_perms(tmp, remote_user, recursive=True) cmd = "" in_data = None diff --git a/lib/ansible/plugins/action/assemble.py b/lib/ansible/plugins/action/assemble.py index eeb13c21ae..9c7e709122 100644 --- a/lib/ansible/plugins/action/assemble.py +++ b/lib/ansible/plugins/action/assemble.py @@ -98,8 +98,9 @@ class ActionModule(ActionBase): return result cleanup_remote_tmp = False + remote_user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user if not tmp: - tmp = self._make_tmp_path() + tmp = self._make_tmp_path(remote_user) cleanup_remote_tmp = True if boolean(remote_src): @@ -146,16 +147,15 @@ class ActionModule(ActionBase): ) if path_checksum != dest_stat['checksum']: - resultant = file(path).read() if self._play_context.diff: diff = self._get_diff_data(dest, path, task_vars) - xfered = self._transfer_data('src', resultant) + remote_path = self._connection._shell.join_path(tmp, 'src') + xfered = self._transfer_file(path, remote_path) # fix file permissions when the copy is done as a different user - if self._play_context.become and self._play_context.become_user != 'root': - self._remote_chmod('a+r', xfered) + self._fixup_perms(tmp, remote_user, recursive=True) new_module_args.update( dict( src=xfered,)) diff --git a/lib/ansible/plugins/action/async.py b/lib/ansible/plugins/action/async.py index 8a7175aeb8..b85f1c20f4 100644 --- a/lib/ansible/plugins/action/async.py +++ b/lib/ansible/plugins/action/async.py @@ -38,8 +38,9 @@ class ActionModule(ActionBase): result['msg'] = 'check mode not supported for this module' return result + remote_user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user if not tmp: - tmp = self._make_tmp_path() + tmp = self._make_tmp_path(remote_user) module_name = self._task.action async_module_path = self._connection._shell.join_path(tmp, 'async_wrapper') @@ -54,15 +55,25 @@ class ActionModule(ActionBase): # configure, upload, and chmod the target module (module_style, shebang, module_data) = self._configure_module(module_name=module_name, module_args=module_args, task_vars=task_vars) self._transfer_data(remote_module_path, module_data) - self._remote_chmod('a+rx', remote_module_path) # configure, upload, and chmod the async_wrapper module (async_module_style, shebang, async_module_data) = self._configure_module(module_name='async_wrapper', module_args=dict(), task_vars=task_vars) self._transfer_data(async_module_path, async_module_data) - self._remote_chmod('a+rx', async_module_path) argsfile = self._transfer_data(self._connection._shell.join_path(tmp, 'arguments'), json.dumps(module_args)) + self._fixup_perms(tmp, remote_user, execute=True, recursive=True) + # Only the following two files need to be executable but we'd have to + # make three remote calls if we wanted to just set them executable. + # There's not really a problem with marking too many of the temp files + # executable so we go ahead and mark them all as executable in the + # line above (the line above is needed in any case [although + # execute=False is okay if we uncomment the lines below] so that all + # the files are readable in case the remote_user and become_user are + # different and both unprivileged) + #self._fixup_perms(remote_module_path, remote_user, execute=True, recursive=False) + #self._fixup_perms(async_module_path, remote_user, execute=True, recursive=False) + async_limit = self._task.async async_jid = str(random.randint(0, 999999999999)) diff --git a/lib/ansible/plugins/action/copy.py b/lib/ansible/plugins/action/copy.py index b8094b2bd6..9734a29444 100644 --- a/lib/ansible/plugins/action/copy.py +++ b/lib/ansible/plugins/action/copy.py @@ -141,9 +141,10 @@ class ActionModule(ActionBase): delete_remote_tmp = (len(source_files) == 1) # If this is a recursive action create a tmp path that we can share as the _exec_module create is too late. + remote_user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user if not delete_remote_tmp: if tmp is None or "-tmp-" not in tmp: - tmp = self._make_tmp_path() + tmp = self._make_tmp_path(remote_user) # expand any user home dir specifier dest = self._remote_expand_user(dest) @@ -196,7 +197,7 @@ class ActionModule(ActionBase): # If this is recursive we already have a tmp path. if delete_remote_tmp: if tmp is None or "-tmp-" not in tmp: - tmp = self._make_tmp_path() + tmp = self._make_tmp_path(remote_user) if self._play_context.diff and not raw: diffs.append(self._get_diff_data(dest_file, source_full, task_vars)) @@ -211,16 +212,15 @@ class ActionModule(ActionBase): tmp_src = self._connection._shell.join_path(tmp, 'source') if not raw: - self._connection.put_file(source_full, tmp_src) + self._transfer_file(source_full, tmp_src) else: - self._connection.put_file(source_full, dest_file) + self._transfer_file(source_full, dest_file) # We have copied the file remotely and no longer require our content_tempfile self._remove_tempfile_if_content_defined(content, content_tempfile) # fix file permissions when the copy is done as a different user - if self._play_context.become and self._play_context.become_user != 'root': - self._remote_chmod('a+r', tmp_src) + self._fixup_perms(tmp, remote_user, recursive=True) if raw: # Continue to next iteration if raw is defined. diff --git a/lib/ansible/plugins/action/patch.py b/lib/ansible/plugins/action/patch.py index c08f901cf5..4fbc69b66a 100644 --- a/lib/ansible/plugins/action/patch.py +++ b/lib/ansible/plugins/action/patch.py @@ -34,6 +34,7 @@ class ActionModule(ActionBase): src = self._task.args.get('src', None) remote_src = boolean(self._task.args.get('remote_src', 'no')) + remote_user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user if src is None: result['failed'] = True @@ -52,14 +53,12 @@ class ActionModule(ActionBase): # create the remote tmp dir if needed, and put the source file there if tmp is None or "-tmp-" not in tmp: - tmp = self._make_tmp_path() + tmp = self._make_tmp_path(remote_user) tmp_src = self._connection._shell.join_path(tmp, os.path.basename(src)) - self._connection.put_file(src, tmp_src) + self._transfer_file(src, tmp_src) - if self._play_context.become and self._play_context.become_user != 'root': - if not self._play_context.check_mode: - self._remote_chmod('a+r', tmp_src) + self._fixup_perms(tmp, remote_user, recursive=True) new_module_args = self._task.args.copy() new_module_args.update( diff --git a/lib/ansible/plugins/action/script.py b/lib/ansible/plugins/action/script.py index 5b0f324dfc..a0d6640548 100644 --- a/lib/ansible/plugins/action/script.py +++ b/lib/ansible/plugins/action/script.py @@ -38,8 +38,9 @@ class ActionModule(ActionBase): result['msg'] = 'check mode not supported for this module' return result + remote_user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user if not tmp: - tmp = self._make_tmp_path() + tmp = self._make_tmp_path(remote_user) creates = self._task.args.get('creates') if creates: @@ -76,16 +77,11 @@ class ActionModule(ActionBase): # transfer the file to a remote tmp location tmp_src = self._connection._shell.join_path(tmp, os.path.basename(source)) - self._connection.put_file(source, tmp_src) + self._transfer_file(source, tmp_src) sudoable = True # set file permissions, more permissive when the copy is done as a different user - if self._play_context.become and self._play_context.become_user != 'root': - chmod_mode = 'a+rx' - sudoable = False - else: - chmod_mode = '+rx' - self._remote_chmod(chmod_mode, tmp_src, sudoable=sudoable) + self._fixup_perms(tmp, remote_user, execute=True, recursive=True) # add preparation steps to one ssh roundtrip executing the script env_string = self._compute_environment_string() diff --git a/lib/ansible/plugins/action/template.py b/lib/ansible/plugins/action/template.py index 5ddd624bde..66320aaf29 100644 --- a/lib/ansible/plugins/action/template.py +++ b/lib/ansible/plugins/action/template.py @@ -138,8 +138,9 @@ class ActionModule(ActionBase): return result cleanup_remote_tmp = False + remote_user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user if not tmp: - tmp = self._make_tmp_path() + tmp = self._make_tmp_path(remote_user) cleanup_remote_tmp = True local_checksum = checksum_s(resultant) @@ -163,8 +164,7 @@ class ActionModule(ActionBase): xfered = self._transfer_data(self._connection._shell.join_path(tmp, 'source'), resultant) # fix file permissions when the copy is done as a different user - if self._play_context.become and self._play_context.become_user != 'root': - self._remote_chmod('a+r', xfered) + self._fixup_perms(tmp, remote_user, recursive=True) # run the copy module new_module_args.update( diff --git a/lib/ansible/plugins/action/unarchive.py b/lib/ansible/plugins/action/unarchive.py index b6c43a3c59..6ace6f9975 100644 --- a/lib/ansible/plugins/action/unarchive.py +++ b/lib/ansible/plugins/action/unarchive.py @@ -45,8 +45,9 @@ class ActionModule(ActionBase): result['msg'] = "src (or content) and dest are required" return result + remote_user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user if not tmp: - tmp = self._make_tmp_path() + tmp = self._make_tmp_path(remote_user) if creates: # do not run the command if the line contains creates=filename @@ -80,17 +81,15 @@ class ActionModule(ActionBase): if copy: # transfer the file to a remote tmp location - tmp_src = tmp + 'source' - self._connection.put_file(source, tmp_src) + tmp_src = self._connection._shell.join_path(tmp, 'source') + self._transfer_file(source, tmp_src) # handle diff mode client side # handle check mode client side - # fix file permissions when the copy is done as a different user - if copy: - if self._play_context.become and self._play_context.become_user != 'root': - if not self._play_context.check_mode: - self._remote_chmod('a+r', tmp_src) + if copy: + # fix file permissions when the copy is done as a different user + self._fixup_perms(tmp, remote_user, recursive=True) # Build temporary module_args. new_module_args = self._task.args.copy() new_module_args.update( diff --git a/lib/ansible/plugins/shell/__init__.py b/lib/ansible/plugins/shell/__init__.py index 50530c6b69..daa011e3da 100644 --- a/lib/ansible/plugins/shell/__init__.py +++ b/lib/ansible/plugins/shell/__init__.py @@ -52,9 +52,40 @@ class ShellBase(object): def path_has_trailing_slash(self, path): return path.endswith('/') - def chmod(self, mode, path): + def chmod(self, mode, path, recursive=True): path = pipes.quote(path) - return 'chmod %s %s' % (mode, path) + cmd = ['chmod', mode, path] + if recursive: + cmd.append('-R') + return ' '.join(cmd) + + def chown(self, path, user, group=None, recursive=True): + path = pipes.quote(path) + user = pipes.quote(user) + + if group is None: + cmd = ['chown', user, path] + else: + group = pipes.quote(group) + cmd = ['chown', '%s:%s' % (user, group), path] + + if recursive: + cmd.append('-R') + + return ' '.join(cmd) + + def set_user_facl(self, path, user, mode, recursive=True): + """Only sets acls for users as that's really all we need""" + path = pipes.quote(path) + mode = pipes.quote(mode) + user = pipes.quote(user) + + cmd = ['setfacl'] + if recursive: + cmd.append('-R') + cmd.extend(('-m', 'u:%s:%s %s' % (user, mode, path))) + + return ' '.join(cmd) def remove(self, path, recurse=False): path = pipes.quote(path) |
