summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/ansible/constants.py1
-rw-r--r--lib/ansible/plugins/action/__init__.py93
-rw-r--r--lib/ansible/plugins/action/assemble.py10
-rw-r--r--lib/ansible/plugins/action/async.py17
-rw-r--r--lib/ansible/plugins/action/copy.py12
-rw-r--r--lib/ansible/plugins/action/patch.py9
-rw-r--r--lib/ansible/plugins/action/script.py12
-rw-r--r--lib/ansible/plugins/action/template.py6
-rw-r--r--lib/ansible/plugins/action/unarchive.py15
-rw-r--r--lib/ansible/plugins/shell/__init__.py35
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)