Use file list, not recursion, in _fixup_perms. (#16924)

Run setfacl/chown/chmod on each temp dir and file.

This fixes temp file permissions handling on platforms such as FreeBSD
which always return success when using find -exec. This is done by
eliminating the use of find when setting up temp files and directories.

Additionally, tests that now pass on FreeBSD have been enabled for CI.
pull/16991/head
Matt Clay 8 years ago committed by GitHub
parent e07fbba0a5
commit 72cca01cd4

@ -293,7 +293,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
return remote_path return remote_path
def _fixup_perms(self, remote_path, remote_user, execute=True, recursive=True): def _fixup_perms(self, remote_paths, remote_user, execute=True):
""" """
We need the files we upload to be readable (and sometimes executable) We need the files we upload to be readable (and sometimes executable)
by the user being sudo'd to but we want to limit other people's access by the user being sudo'd to but we want to limit other people's access
@ -319,15 +319,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
if self._connection._shell.SHELL_FAMILY == 'powershell': if self._connection._shell.SHELL_FAMILY == 'powershell':
# This won't work on Powershell as-is, so we'll just completely skip until # This won't work on Powershell as-is, so we'll just completely skip until
# we have a need for it, at which point we'll have to do something different. # we have a need for it, at which point we'll have to do something different.
return remote_path return remote_paths
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
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): 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 # Unprivileged user that's different than the ssh user. Let's get
@ -340,17 +332,17 @@ class ActionBase(with_metaclass(ABCMeta, object)):
else: else:
mode = 'rX' mode = 'rX'
res = self._remote_set_user_facl(remote_path, self._play_context.become_user, mode, recursive=recursive, sudoable=False) res = self._remote_set_user_facl(remote_paths, self._play_context.become_user, mode)
if res['rc'] != 0: if res['rc'] != 0:
# File system acls failed; let's try to use chown next # File system acls failed; let's try to use chown next
# Set executable bit first as on some systems an # Set executable bit first as on some systems an
# unprivileged user can use chown # unprivileged user can use chown
if execute: if execute:
res = self._remote_chmod('u+x', remote_path, recursive=recursive) res = self._remote_chmod(remote_paths, 'u+x')
if res['rc'] != 0: if res['rc'] != 0:
raise AnsibleError('Failed to set file mode on remote temporary files (rc: {0}, err: {1})'.format(res['rc'], res['stderr'])) raise AnsibleError('Failed to set file mode on remote temporary files (rc: {0}, err: {1})'.format(res['rc'], res['stderr']))
res = self._remote_chown(remote_path, self._play_context.become_user, recursive=recursive) res = self._remote_chown(remote_paths, self._play_context.become_user)
if res['rc'] != 0 and remote_user == 'root': if res['rc'] != 0 and remote_user == 'root':
# chown failed even if remove_user is root # chown failed even if remove_user is root
raise AnsibleError('Failed to change ownership of the temporary files Ansible needs to create despite connecting as root. Unprivileged become user would be unable to read the file.') raise AnsibleError('Failed to change ownership of the temporary files Ansible needs to create despite connecting as root. Unprivileged become user would be unable to read the file.')
@ -359,7 +351,7 @@ class ActionBase(with_metaclass(ABCMeta, object)):
# chown and fs acls failed -- do things this insecure # chown and fs acls failed -- do things this insecure
# way only if the user opted in in the config file # way only if the user opted in in the config file
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') 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')
res = self._remote_chmod('a+%s' % mode, remote_path, recursive=recursive) res = self._remote_chmod(remote_paths, 'a+%s' % mode)
if res['rc'] != 0: if res['rc'] != 0:
raise AnsibleError('Failed to set file mode on remote files (rc: {0}, err: {1})'.format(res['rc'], res['stderr'])) raise AnsibleError('Failed to set file mode on remote files (rc: {0}, err: {1})'.format(res['rc'], res['stderr']))
else: else:
@ -368,33 +360,33 @@ class ActionBase(with_metaclass(ABCMeta, object)):
# Can't depend on the file being transferred with execute # Can't depend on the file being transferred with execute
# permissions. Only need user perms because no become was # permissions. Only need user perms because no become was
# used here # used here
res = self._remote_chmod('u+x', remote_path, recursive=recursive) res = self._remote_chmod(remote_paths, 'u+x')
if res['rc'] != 0: if res['rc'] != 0:
raise AnsibleError('Failed to set file mode on remote files (rc: {0}, err: {1})'.format(res['rc'], res['stderr'])) raise AnsibleError('Failed to set file mode on remote files (rc: {0}, err: {1})'.format(res['rc'], res['stderr']))
return remote_path return remote_paths
def _remote_chmod(self, mode, path, recursive=True, sudoable=False): def _remote_chmod(self, paths, mode, sudoable=False):
''' '''
Issue a remote chmod command Issue a remote chmod command
''' '''
cmd = self._connection._shell.chmod(mode, path, recursive=recursive) cmd = self._connection._shell.chmod(paths, mode)
res = self._low_level_execute_command(cmd, sudoable=sudoable) res = self._low_level_execute_command(cmd, sudoable=sudoable)
return res return res
def _remote_chown(self, path, user, group=None, recursive=True, sudoable=False): def _remote_chown(self, paths, user, sudoable=False):
''' '''
Issue a remote chown command Issue a remote chown command
''' '''
cmd = self._connection._shell.chown(path, user, group, recursive=recursive) cmd = self._connection._shell.chown(paths, user)
res = self._low_level_execute_command(cmd, sudoable=sudoable) res = self._low_level_execute_command(cmd, sudoable=sudoable)
return res return res
def _remote_set_user_facl(self, path, user, mode, recursive=True, sudoable=False): def _remote_set_user_facl(self, paths, user, mode, sudoable=False):
''' '''
Issue a remote call to setfacl Issue a remote call to setfacl
''' '''
cmd = self._connection._shell.set_user_facl(path, user, mode, recursive=recursive) cmd = self._connection._shell.set_user_facl(paths, user, mode)
res = self._low_level_execute_command(cmd, sudoable=sudoable) res = self._low_level_execute_command(cmd, sudoable=sudoable)
return res return res
@ -616,9 +608,17 @@ class ActionBase(with_metaclass(ABCMeta, object)):
environment_string = self._compute_environment_string() environment_string = self._compute_environment_string()
remote_files = None
if args_file_path:
remote_files = tmp, remote_module_path, args_file_path
elif remote_module_path:
remote_files = tmp, remote_module_path
# Fix permissions of the tmp path and tmp files. This should be # Fix permissions of the tmp path and tmp files. This should be
# called after all files have been transferred. # called after all files have been transferred.
self._fixup_perms(tmp, remote_user, recursive=True) if remote_files:
self._fixup_perms(remote_files, remote_user)
cmd = "" cmd = ""
in_data = None in_data = None

@ -159,7 +159,7 @@ class ActionModule(ActionBase):
xfered = self._transfer_file(path, remote_path) xfered = self._transfer_file(path, remote_path)
# fix file permissions when the copy is done as a different user # fix file permissions when the copy is done as a different user
self._fixup_perms(tmp, remote_user, recursive=True) self._fixup_perms((tmp, remote_path), remote_user)
new_module_args.update( dict( src=xfered,)) new_module_args.update( dict( src=xfered,))

@ -73,17 +73,13 @@ class ActionModule(ActionBase):
args_data += '%s="%s" ' % (k, pipes.quote(to_unicode(v))) args_data += '%s="%s" ' % (k, pipes.quote(to_unicode(v)))
argsfile = self._transfer_data(self._connection._shell.join_path(tmp, 'arguments'), args_data) argsfile = self._transfer_data(self._connection._shell.join_path(tmp, 'arguments'), args_data)
self._fixup_perms(tmp, remote_user, execute=True, recursive=True) remote_paths = tmp, remote_module_path, async_module_path
# 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. # argsfile doesn't need to be executable, but this saves an extra call to the remote host
# There's not really a problem with marking too many of the temp files if argsfile:
# executable so we go ahead and mark them all as executable in the remote_paths += argsfile,
# line above (the line above is needed in any case [although
# execute=False is okay if we uncomment the lines below] so that all self._fixup_perms(remote_paths, remote_user, execute=True)
# 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_limit = self._task.async
async_jid = str(random.randint(0, 999999999999)) async_jid = str(random.randint(0, 999999999999))

@ -213,8 +213,10 @@ class ActionModule(ActionBase):
# Define a remote directory that we will copy the file to. # Define a remote directory that we will copy the file to.
tmp_src = self._connection._shell.join_path(tmp, 'source') tmp_src = self._connection._shell.join_path(tmp, 'source')
remote_path = None
if not raw: if not raw:
self._transfer_file(source_full, tmp_src) remote_path = self._transfer_file(source_full, tmp_src)
else: else:
self._transfer_file(source_full, dest_file) self._transfer_file(source_full, dest_file)
@ -223,7 +225,8 @@ class ActionModule(ActionBase):
self._loader.cleanup_tmp_file(source_full) self._loader.cleanup_tmp_file(source_full)
# fix file permissions when the copy is done as a different user # fix file permissions when the copy is done as a different user
self._fixup_perms(tmp, remote_user, recursive=True) if remote_path:
self._fixup_perms((tmp, remote_path), remote_user)
if raw: if raw:
# Continue to next iteration if raw is defined. # Continue to next iteration if raw is defined.

@ -63,7 +63,7 @@ class ActionModule(ActionBase):
tmp_src = self._connection._shell.join_path(tmp, os.path.basename(src)) tmp_src = self._connection._shell.join_path(tmp, os.path.basename(src))
self._transfer_file(src, tmp_src) self._transfer_file(src, tmp_src)
self._fixup_perms(tmp, remote_user, recursive=True) self._fixup_perms((tmp, tmp_src), remote_user)
new_module_args = self._task.args.copy() new_module_args = self._task.args.copy()
new_module_args.update( new_module_args.update(

@ -81,7 +81,7 @@ class ActionModule(ActionBase):
self._transfer_file(source, tmp_src) self._transfer_file(source, tmp_src)
# set file permissions, more permissive when the copy is done as a different user # set file permissions, more permissive when the copy is done as a different user
self._fixup_perms(tmp, remote_user, execute=True, recursive=True) self._fixup_perms((tmp, tmp_src), remote_user, execute=True)
# add preparation steps to one ssh roundtrip executing the script # add preparation steps to one ssh roundtrip executing the script
env_string = self._compute_environment_string() env_string = self._compute_environment_string()

@ -166,7 +166,7 @@ class ActionModule(ActionBase):
xfered = self._transfer_data(self._connection._shell.join_path(tmp, 'source'), resultant) xfered = self._transfer_data(self._connection._shell.join_path(tmp, 'source'), resultant)
# fix file permissions when the copy is done as a different user # fix file permissions when the copy is done as a different user
self._fixup_perms(tmp, remote_user, recursive=True) self._fixup_perms((tmp, xfered), remote_user)
# run the copy module # run the copy module
new_module_args.update( new_module_args.update(

@ -97,7 +97,7 @@ class ActionModule(ActionBase):
if copy: if copy:
# fix file permissions when the copy is done as a different user # fix file permissions when the copy is done as a different user
self._fixup_perms(tmp, remote_user, recursive=True) self._fixup_perms((tmp, tmp_src), remote_user)
# Build temporary module_args. # Build temporary module_args.
new_module_args = self._task.args.copy() new_module_args = self._task.args.copy()
new_module_args.update( new_module_args.update(

@ -57,45 +57,25 @@ class ShellBase(object):
def path_has_trailing_slash(self, path): def path_has_trailing_slash(self, path):
return path.endswith('/') return path.endswith('/')
def chmod(self, mode, path, recursive=True): def chmod(self, paths, mode):
path = pipes.quote(path) cmd = ['chmod', mode]
cmd = ['chmod'] cmd.extend(paths)
cmd = [pipes.quote(c) for c in cmd]
if recursive:
cmd.append('-R') # many chmods require -R before file list
cmd.extend([mode, path])
return ' '.join(cmd) return ' '.join(cmd)
def chown(self, path, user, group=None, recursive=True): def chown(self, paths, user):
path = pipes.quote(path) cmd = ['chown', user]
user = pipes.quote(user) cmd.extend(paths)
cmd = [pipes.quote(c) for c in cmd]
cmd = ['chown']
if recursive:
cmd.append('-R') # many chowns require -R before file list
if group is None:
cmd.extend([user, path])
else:
group = pipes.quote(group)
cmd.extend(['%s:%s' % (user, group), path])
return ' '.join(cmd) return ' '.join(cmd)
def set_user_facl(self, path, user, mode, recursive=True): def set_user_facl(self, paths, user, mode):
"""Only sets acls for users as that's really all we need""" """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', '-m', 'u:%s:%s' % (user, mode)] cmd = ['setfacl', '-m', 'u:%s:%s' % (user, mode)]
if recursive: cmd.extend(paths)
cmd = ['find', path, '-exec'] + cmd + ["'{}'", "'+'"] cmd = [pipes.quote(c) for c in cmd]
else:
cmd.append(path)
return ' '.join(cmd) return ' '.join(cmd)

@ -68,13 +68,13 @@ class ShellModule(object):
path = self._unquote(path) path = self._unquote(path)
return path.endswith('/') or path.endswith('\\') return path.endswith('/') or path.endswith('\\')
def chmod(self, mode, path, recursive=True): def chmod(self, paths, mode):
raise NotImplementedError('chmod is not implemented for Powershell') raise NotImplementedError('chmod is not implemented for Powershell')
def chown(self, path, user, group=None, recursive=True): def chown(self, paths, user):
raise NotImplementedError('chown is not implemented for Powershell') raise NotImplementedError('chown is not implemented for Powershell')
def set_user_facl(self, path, user, mode, recursive=True): def set_user_facl(self, paths, user, mode):
raise NotImplementedError('set_user_facl is not implemented for Powershell') raise NotImplementedError('set_user_facl is not implemented for Powershell')
def remove(self, path, recurse=False): def remove(self, path, recurse=False):

@ -15,7 +15,7 @@ test_flags="${TEST_FLAGS:-}"
force_color="${FORCE_COLOR:-1}" force_color="${FORCE_COLOR:-1}"
# FIXME: these tests fail # FIXME: these tests fail
skip_tags='test_copy,test_template,test_unarchive,test_command_shell,test_sudo,test_become,test_service,test_postgresql,test_mysql_db,test_mysql_user,test_mysql_variables,test_uri,test_get_url' skip_tags='test_copy,test_template,test_unarchive,test_command_shell,test_service,test_postgresql,test_mysql_db,test_mysql_user,test_mysql_variables,test_uri,test_get_url'
cd ~/ cd ~/

Loading…
Cancel
Save