diff --git a/docs/docsite/rst/user_guide/become.rst b/docs/docsite/rst/user_guide/become.rst index 8e44bad7a04..11552dc4b45 100644 --- a/docs/docsite/rst/user_guide/become.rst +++ b/docs/docsite/rst/user_guide/become.rst @@ -72,7 +72,7 @@ Become connection variables You can define different ``become`` options for each managed node or group. You can define these variables in inventory or use them as normal variables. ansible_become - equivalent of the become directive, decides if privilege escalation is used or not. + overrides the ``become`` directive, decides if privilege escalation is used or not. ansible_become_method which privilege escalation method should be used @@ -83,6 +83,9 @@ ansible_become_user ansible_become_password set the privilege escalation password. See :ref:`playbooks_vault` for details on how to avoid having secrets in plain text +ansible_common_remote_group + determines if Ansible should try to ``chgrp`` its temporary files to a group if ``setfacl`` and ``chown`` both fail. See `Risks of becoming an unprivileged user`_ for more information. Added in version 2.10. + For example, if you want to run all tasks as ``root`` on a server named ``webserver``, but you can only connect as the ``manager`` user, you could use an inventory entry like this: .. code-block:: text @@ -125,20 +128,57 @@ and finally executing it there. Everything is fine if the module file is executed without using ``become``, when the ``become_user`` is root, or when the connection to the remote machine -is made as root. In these cases Ansible creates the module file with permissions -that only allow reading by the user and root, or only allow reading by the unprivileged -user being switched to. +is made as root. In these cases Ansible creates the module file with +permissions that only allow reading by the user and root, or only allow reading +by the unprivileged user being switched to. However, when both the connection user and the ``become_user`` are unprivileged, -the module file is written as the user that Ansible connects as, but the file needs to -be readable by the user Ansible is set to ``become``. In this case, Ansible makes -the module file world-readable for the duration of the Ansible module execution. -Once the module is done executing, Ansible deletes the temporary file. +the module file is written as the user that Ansible connects as (the +``remote_user``), but the file needs to be readable by the user Ansible is set +to ``become``. The details of how Ansible solves this can vary based on platform. +However, on POSIX systems, Ansible solves this problem in the following way: + +First, if :command:`setfacl` is installed and available in the remote ``PATH``, +and the temporary directory on the remote host is mounted with POSIX.1e +filesystem ACL support, Ansible will use POSIX ACLs to share the module file +with the second unprivileged user. + +Next, if POSIX ACLs are **not** available or :command:`setfacl` could not be +run, Ansible will attempt to change ownership of the module file using +:command:`chown` for systems which support doing so as an unprivileged user. + +New in Ansible 2.10, if the :command:`chown` fails, Ansible will then check the +value of the configuration setting ``ansible_common_remote_group``. Many +systems will allow a given user to change the group ownership of a file to a +group the user is in. As a result, if the second unprivileged user (the +``become_user``) has a UNIX group in common with the user Ansible is connected +as (the ``remote_user``), and if ``ansible_common_remote_group`` is defined to +be that group, Ansible can try to change the group ownership of the module file +to that group by using :command:`chgrp`, thereby likely making it readable to +the ``become_user``. + +At this point, if ``ansible_common_remote_group`` was defined and a +:command:`chgrp` was attempted and returned successfully, Ansible assumes (but, +importantly, does not check) that the new group ownership is enough and does not +fall back further. That is, Ansible **does not check** that the ``become_user`` +does in fact share a group with the ``remote_user``; so long as the command +exits successfully, Ansible considers the result successful and does not proceed +to check ``allow_world_readable_tmpfiles`` per below. + +If ``ansible_common_remote_group`` is **not** set and the chown above it failed, +or if ``ansible_common_remote_group`` *is* set but the :command:`chgrp` (or +following group-permissions :command:`chmod`) returned a non-successful exit +code, Ansible will lastly check the value of +``allow_world_readable_tmpfiles``. If this is set, Ansible will place the module +file in a world-readable temporary directory, with world-readable permissions to +allow the ``become_user`` (and incidentally any other user on the system) to +read the contents of the file. **If any of the parameters passed to the module +are sensitive in nature, and you do not trust the remote machines, then this is +a potential security risk.** -If any of the parameters passed to the module are sensitive in nature, and you do -not trust the client machines, then this is a potential danger. +Once the module is done executing, Ansible deletes the temporary file. -Ways to resolve this include: +Several ways exist to avoid the above logic flow entirely: * Use `pipelining`. When pipelining is enabled, Ansible does not save the module to a temporary file on the client. Instead it pipes the module to @@ -146,12 +186,6 @@ Ways to resolve this include: python modules involving file transfer (for example: :ref:`copy `, :ref:`fetch `, :ref:`template `), or for non-python modules. -* Install POSIX.1e filesystem acl support on the - managed host. If the temporary directory on the remote host is mounted with - POSIX acls enabled and the :command:`setfacl` tool is in the remote ``PATH`` - then Ansible will use POSIX acls to share the module file with the second - unprivileged user instead of having to make the file readable by everyone. - * Avoid becoming an unprivileged user. Temporary files are protected by UNIX file permissions when you ``become`` root or do not use ``become``. In Ansible 2.1 and above, UNIX @@ -167,14 +201,32 @@ Ways to resolve this include: Ansible makes it hard to unknowingly use ``become`` insecurely. Starting in Ansible 2.1, Ansible defaults to issuing an error if it cannot execute securely with ``become``. -If you cannot use pipelining or POSIX ACLs, you must connect as an unprivileged user, -you must use ``become`` to execute as a different unprivileged user, -and you decide that your managed nodes are secure enough for the +If you cannot use pipelining or POSIX ACLs, must connect as an unprivileged user, +must use ``become`` to execute as a different unprivileged user, +and decide that your managed nodes are secure enough for the modules you want to run there to be world readable, you can turn on ``allow_world_readable_tmpfiles`` in the :file:`ansible.cfg` file. Setting ``allow_world_readable_tmpfiles`` will change this from an error into a warning and allow the task to run as it did prior to 2.1. +.. versionchanged:: 2.10 + +Ansible 2.10 introduces the above-mentioned ``ansible_common_remote_group`` +fallback. As mentioned above, if enabled, it is used when ``remote_user`` and +``become_user`` are both unprivileged users. Refer to the text above for details +on when this fallback happens. + +.. warning:: As mentioned above, if ``ansible_common_remote_group`` and + ``allow_world_readable_tmpfiles`` are both enabled, it is unlikely that the + world-readable fallback will ever trigger, and yet Ansible might still be + unable to access the module file. This is because after the group ownership + change is successful, Ansible does not fall back any further, and also does + not do any check to ensure that the ``become_user`` is actually a member of + the "common group". This is a design decision made by the fact that doing + such a check would require another round-trip connection to the remote + machine, which is a time-expensive operation. Ansible does, however, emit a + warning in this case. + Not supported by all connection plugins --------------------------------------- diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py index f08a084608f..199f04d29c2 100644 --- a/lib/ansible/plugins/action/__init__.py +++ b/lib/ansible/plugins/action/__init__.py @@ -510,11 +510,24 @@ class ActionBase(with_metaclass(ABCMeta, object)): file with chown which only works in case the remote_user is privileged or the remote systems allows chown calls by unprivileged users (e.g. HP-UX) - * If the chown fails we can set the file to be world readable so that + * If the chown fails, we check if ansible_common_remote_group is set. + If it is, we attempt to chgrp the file to its value. This is useful + if the remote_user has a group in common with the become_user. As the + remote_user, we can chgrp the file to that group and allow the + become_user to read it. + * If (the chown fails AND ansible_common_remote_group is not set) OR + (ansible_common_remote_group is set AND the chgrp (or following chmod) + returned non-zero), we can set the file to be world readable so that the second unprivileged user can read the file. Since this could allow other users to get access to private information we only do this if ansible is configured with - "allow_world_readable_tmpfiles" in the ansible.cfg + "allow_world_readable_tmpfiles" in the ansible.cfg. Also note that + when ansible_common_remote_group is set this final fallback is very + unlikely to ever be triggered, so long as chgrp was successful. But + just because the chgrp was successful, does not mean Ansible can + necessarily access the files (if, for example, the variable was set + to a group that remote_user is in, and can chgrp to, but does not have + in common with become_user). """ if remote_user is None: remote_user = self._get_remote_user() @@ -528,6 +541,8 @@ class ActionBase(with_metaclass(ABCMeta, object)): # Unprivileged user that's different than the ssh user. Let's get # to work! + become_user = self.get_become_option('become_user') + # Try to use file system acls to make the files readable for sudo'd # user if execute: @@ -540,7 +555,7 @@ class ActionBase(with_metaclass(ABCMeta, object)): # start to we'll have to fix this. setfacl_mode = 'r-X' - res = self._remote_set_user_facl(remote_paths, self.get_become_option('become_user'), setfacl_mode) + res = self._remote_set_user_facl(remote_paths, become_user, setfacl_mode) if res['rc'] != 0: # File system acls failed; let's try to use chown next # Set executable bit first as on some systems an @@ -550,12 +565,48 @@ class ActionBase(with_metaclass(ABCMeta, object)): if res['rc'] != 0: raise AnsibleError('Failed to set file mode on remote temporary files (rc: {0}, err: {1})'.format(res['rc'], to_native(res['stderr']))) - res = self._remote_chown(remote_paths, self.get_become_option('become_user')) - if res['rc'] != 0 and remote_user in self._get_admin_users(): - # chown failed even if remote_user is administrator/root - raise AnsibleError('Failed to change ownership of the temporary files Ansible needs to create despite connecting as a privileged user. ' - 'Unprivileged become user would be unable to read the file.') - elif res['rc'] != 0: + res = self._remote_chown(remote_paths, become_user) + if res['rc'] != 0: + # First check if we are an admin/root user. If we are + # and failed here, something weird has happened. + if remote_user in self._get_admin_users(): + # chown failed even if remote_user is administrator/root + raise AnsibleError('Failed to change ownership of the temporary files Ansible needs to create despite connecting as a privileged user. ' + 'Unprivileged become user would be unable to read the file.') + + # Otherwise, we're a normal user. We failed to chown the + # paths to the unprivileged user, but if we have a common + # group with them, we should be able to chown it to that. + # + # Note that we have no way of knowing if this will actually + # work... just because chgrp exits successfully does not + # mean that Ansible will work. We could check if the become + # user is in the group, but this would create an extra + # round trip. + # + # Also note that due to the above, this can prevent the + # ALLOW_WORLD_READABLE_TMPFILES logic below from ever + # getting called. We leave this up to the user to rectify + # if they have both of these features enabled. + group = self.get_shell_option('common_remote_group') + if group is not None: + res = self._remote_chgrp(remote_paths, group) + if res['rc'] == 0: + # If ALLOW_WORLD_READABLE_TMPFILES is set, we should warn the user + # that something might go weirdly here. + if C.ALLOW_WORLD_READABLE_TMPFILES: + display.warning('Both common_remote_group and allow_world_readable_tmpfiles are set. chgrp was successful, but there is no ' + 'guarantee that Ansible will be able to read the files after this operation, particularly if ' + 'common_remote_group was set to a group of which the unprivileged become user is not a member. In this ' + 'situation, allow_world_readable_tmpfiles is a no-op. See the "Risks of becoming an unprivileged user" section ' + 'of the "Understanding privilege escalation: become" user guide documentation for more information') + if execute: + group_mode = 'g+rwx' + else: + group_mode = 'g+rw' + res = self._remote_chmod(remote_paths, group_mode) + + if res['rc'] != 0: if self.get_shell_option('world_readable_temp', C.ALLOW_WORLD_READABLE_TMPFILES): # chown and fs acls failed -- do things this insecure # way only if the user opted in in the config file @@ -595,6 +646,14 @@ class ActionBase(with_metaclass(ABCMeta, object)): res = self._low_level_execute_command(cmd, sudoable=sudoable) return res + def _remote_chgrp(self, paths, group, sudoable=False): + ''' + Issue a remote chgrp command + ''' + cmd = self._connection._shell.chgrp(paths, group) + res = self._low_level_execute_command(cmd, sudoable=sudoable) + return res + def _remote_set_user_facl(self, paths, user, mode, sudoable=False): ''' Issue a remote call to setfacl diff --git a/lib/ansible/plugins/doc_fragments/shell_common.py b/lib/ansible/plugins/doc_fragments/shell_common.py index 7ba4049ca63..27f93c5bdbe 100644 --- a/lib/ansible/plugins/doc_fragments/shell_common.py +++ b/lib/ansible/plugins/doc_fragments/shell_common.py @@ -19,6 +19,19 @@ options: key: remote_tmp vars: - name: ansible_remote_tmp + common_remote_group: + name: Enables changing the group ownership of temporary files and directories + default: null + description: + - Checked when Ansible needs to execute a module as a different user. + - If setfacl and chown both fail and do not let the different user access the module's files, they will be chgrp'd to this group. + - In order for this to work, the remote_user and become_user must share a common group and this setting must be set to that group. + env: [{name: ANSIBLE_COMMON_REMOTE_GROUP}] + vars: + - name: ansible_common_remote_group + ini: + - {key: common_remote_group, section: defaults} + version_added: "2.10" system_tmpdirs: description: - "List of valid system temporary directories for Ansible to choose when it cannot use diff --git a/lib/ansible/plugins/shell/__init__.py b/lib/ansible/plugins/shell/__init__.py index 4050d8227b5..3db3bcc5322 100644 --- a/lib/ansible/plugins/shell/__init__.py +++ b/lib/ansible/plugins/shell/__init__.py @@ -107,6 +107,13 @@ class ShellBase(AnsiblePlugin): return ' '.join(cmd) + def chgrp(self, paths, group): + cmd = ['chgrp', group] + cmd.extend(paths) + cmd = [shlex_quote(c) for c in cmd] + + return ' '.join(cmd) + def set_user_facl(self, paths, user, mode): """Only sets acls for users as that's really all we need""" cmd = ['setfacl', '-m', 'u:%s:%s' % (user, mode)] diff --git a/test/integration/targets/become_unprivileged/aliases b/test/integration/targets/become_unprivileged/aliases new file mode 100644 index 00000000000..1067ca06a55 --- /dev/null +++ b/test/integration/targets/become_unprivileged/aliases @@ -0,0 +1,4 @@ +destructive +shippable/posix/group1 +skip/aix +needs/ssh diff --git a/test/integration/targets/become_unprivileged/cleanup.yml b/test/integration/targets/become_unprivileged/cleanup.yml new file mode 100644 index 00000000000..d0e3d53bc65 --- /dev/null +++ b/test/integration/targets/become_unprivileged/cleanup.yml @@ -0,0 +1,8 @@ +- name: Clean up host + hosts: ssh + gather_facts: yes + + # Default, just noted here to be explicit about what is happening: + remote_user: root + roles: + - cleanup_become_unprivileged diff --git a/test/integration/targets/become_unprivileged/inventory b/test/integration/targets/become_unprivileged/inventory new file mode 100644 index 00000000000..ba06ca1c78b --- /dev/null +++ b/test/integration/targets/become_unprivileged/inventory @@ -0,0 +1,7 @@ +[ssh] +ssh-pipelining ansible_ssh_pipelining=true +#ssh-no-pipelining ansible_ssh_pipelining=false +[ssh:vars] +ansible_host=localhost +ansible_connection=ssh +ansible_python_interpreter="{{ ansible_playbook_python }}" diff --git a/test/integration/targets/become_unprivileged/roles/become_unprivileged/files/baz.txt b/test/integration/targets/become_unprivileged/roles/become_unprivileged/files/baz.txt new file mode 100644 index 00000000000..40a246846b4 --- /dev/null +++ b/test/integration/targets/become_unprivileged/roles/become_unprivileged/files/baz.txt @@ -0,0 +1 @@ +hello I was copied diff --git a/test/integration/targets/become_unprivileged/roles/become_unprivileged/tasks/main.yml b/test/integration/targets/become_unprivileged/roles/become_unprivileged/tasks/main.yml new file mode 100644 index 00000000000..770a26d104b --- /dev/null +++ b/test/integration/targets/become_unprivileged/roles/become_unprivileged/tasks/main.yml @@ -0,0 +1,58 @@ +- name: Run a command + shell: whoami + register: whoami + +# TODO: We ignore_errors here because atomic_move has some really weird edge +# cases and gives different behavior based on whether the tmpdir we are copying +# from is on the same partition as the target or not, among other things. There +# is probably work to be done there to either unify the behavior if possible, or +# if not, document/add a warning. +# +# In what follows, unpriv1 is remote_user and unpriv2 is become_user. Both +# users are unprivileged. +# +# In particular, given a system (FreeBSD in my testing, but probably any *nix) +# with a single partition, when we connect (as unpriv1) and become unpriv2, +# the file ends up being unpriv1:commongroup. We can't chown it after that +# since we are become_user, so the file remains owned by unpriv1. +# +# But when we have multiple partitions, os.rename() in atomic_move fails, and +# we end up falling back to a whole new bunch of logic. In the end the file +# ends up being creted as unpriv2 and is unpriv2:unpriv2_login_group. +# +# This creates a bunch of inconsistency and really should be documented better +# but the relevant part for *this* test is that in the single-partition case, +# we cannot chmod in the `if creating` branch of atomic_move since we do not +# own the file. That will generate an error. +- name: Copy a file + copy: + src: baz.txt + dest: ~/uh-oh + owner: unpriv2 + group: notcoolenoughforroot + mode: 0644 + ignore_errors: yes + +- name: See if the file exists + stat: + path: ~/uh-oh + register: uh_oh_stat + +#- name: Get files in /var/tmp +# find: +# paths: "/var/tmp/" +# patterns: 'ansible*' +# file_type: directory +# register: found +# +#- name: Get latest ansible tmp dir +# set_fact: +# tmpdir: "{{ found.files | sort(attribute='mtime') | last }}" +# +#- debug: var=tmpdir + +- assert: + that: + - whoami.stdout == 'unpriv2' + - uh_oh_stat.stat.exists + #- tmpdir.gr_name == 'notcoolenoughforroot' diff --git a/test/integration/targets/become_unprivileged/roles/cleanup_become_unprivileged/tasks/main.yml b/test/integration/targets/become_unprivileged/roles/cleanup_become_unprivileged/tasks/main.yml new file mode 100644 index 00000000000..8de432bba34 --- /dev/null +++ b/test/integration/targets/become_unprivileged/roles/cleanup_become_unprivileged/tasks/main.yml @@ -0,0 +1,74 @@ +# Do this first so we can use tilde notation while the user still exists +- name: Delete homedirs + file: + path: '~{{ item }}' + state: absent + with_items: + - unpriv1 + - unpriv2 + +- name: Delete users + user: + name: "{{ item }}" + state: absent + force: yes # I think this is needed in case pipelining is used and the session remains open + with_items: + - unpriv1 + - unpriv2 + +- name: Delete groups + group: + name: "{{ item }}" + state: absent + with_items: + - notcoolenoughforroot + - unpriv1 + - unpriv2 + +- name: Fix sudoers.d path for FreeBSD + set_fact: + sudoers_etc: /usr/local/etc + when: ansible_distribution == 'FreeBSD' + +- name: Fix sudoers.d path for everything else + set_fact: + sudoers_etc: /etc + when: ansible_distribution != 'FreeBSD' + +- name: Undo OpenSUSE + lineinfile: + path: "{{ sudoers_etc }}/sudoers" + regexp: '^### Defaults targetpw' + line: 'Defaults targetpw' + backrefs: yes + +- name: Nuke custom sudoers file + file: + path: "{{ sudoers_etc }}/sudoers.d/unpriv1" + state: absent + +- name: Check if /usr/bin/setfacl exists + stat: + path: /usr/bin/setfacl + register: usr_bin_setfacl + +- name: Check if the /bin/setfacl exists + stat: + path: /bin/setfacl + register: bin_setfacl + +- name: Set path to setfacl + set_fact: + setfacl_path: /usr/bin/setfacl + when: usr_bin_setfacl.stat.exists + +- name: Set path to setfacl + set_fact: + setfacl_path: /bin/setfacl + when: bin_setfacl.stat.exists + +- name: chmod +x setfacl + file: + path: "{{ setfacl_path }}" + mode: +x + when: setfacl_path is defined diff --git a/test/integration/targets/become_unprivileged/roles/setup_become_unprivileged/tasks/main.yml b/test/integration/targets/become_unprivileged/roles/setup_become_unprivileged/tasks/main.yml new file mode 100644 index 00000000000..bbfe798950b --- /dev/null +++ b/test/integration/targets/become_unprivileged/roles/setup_become_unprivileged/tasks/main.yml @@ -0,0 +1,128 @@ +--- +#################################################################### +# NOTE! Any destructive changes you make here... Undo them in +# cleanup_become_unprivileged so that they don't affect other tests. +#################################################################### + +- name: Create groups for unprivileged users + group: + name: "{{ item }}" + with_items: + - notcoolenoughforroot + - unpriv1 + - unpriv2 + +# MacOS requires unencrypted password +- name: Set password for unpriv1 (MacOSX) + set_fact: + password: 'iWishIWereCoolEnoughForRoot!' + when: ansible_distribution == 'MacOSX' + +- name: Set password for unpriv1 (everything else) + set_fact: + password: $6$CRuKRUfAoVwibjUI$1IEOISMFAE/a0VG73K9QsD0uruXNPLNkZ6xWg4Sk3kZIXwv6.YJLECzfNjn6pu8ay6XlVcj2dUvycLetL5Lgx1 + when: ansible_distribution != 'MacOSX' + +# This user is special. It gets a password so we can sudo as it +# (we set the sudo password in runme.sh) and it gets wheel so it can +# `become` unpriv2 without an overly complex sudoers file. +- name: Create first unprivileged user + user: + name: unpriv1 + groups: unpriv1,notcoolenoughforroot + append: yes + password: "{{ password }}" + +- name: Create second unprivileged user + user: + name: unpriv2 + groups: unpriv2,notcoolenoughforroot + append: yes + +- name: Create .ssh for unpriv1 + file: + path: ~unpriv1/.ssh + state: directory + owner: unpriv1 + group: unpriv1 + mode: 0700 + +- name: Set authorized key for unpriv1 + copy: + src: ~root/.ssh/authorized_keys + dest: ~unpriv1/.ssh/authorized_keys + remote_src: yes + owner: unpriv1 + group: unpriv1 + mode: 0600 + +# Without this we get: +# "Failed to connect to the host via ssh: "System is booting up. Unprivileged +# users are not permitted to log in yet. Please come back later." +- name: Nuke /run/nologin + file: + path: /run/nologin + state: absent + +- name: Fix sudoers.d path for FreeBSD + set_fact: + sudoers_etc: /usr/local/etc + when: ansible_distribution == 'FreeBSD' + +- name: Fix sudoers.d path for everything else + set_fact: + sudoers_etc: /etc + when: sudoers_etc is not defined + +- name: Chown group for bsd and osx + set_fact: + chowngroup: wheel + when: ansible_distribution in ('FreeBSD', 'MacOSX') + +- name: Chown group for everything else + set_fact: + chowngroup: root + when: chowngroup is not defined + +- name: Make it so unpriv1 can sudo (Chapter 1) + copy: + dest: "{{ sudoers_etc }}/sudoers.d/unpriv1" + content: unpriv1 ALL=(ALL) ALL + owner: root + group: "{{ chowngroup }}" + mode: 0644 + +# OpenSUSE has a weird sudo default here and requires the root pw +# instead of the user pw. Undo that setting, we can clean it up later. +- name: Make it so unpriv1 can sudo (Chapter 2 - The Return Of the OpenSUSE) + lineinfile: + dest: "{{ sudoers_etc }}/sudoers" + regexp: '^Defaults targetpw' + line: '### Defaults targetpw' + backrefs: yes + +- name: Check if /usr/bin/setfacl exists + stat: + path: /usr/bin/setfacl + register: usr_bin_setfacl + +- name: Check if the /bin/setfacl exists + stat: + path: /bin/setfacl + register: bin_setfacl + +- name: Set path to setfacl + set_fact: + setfacl_path: /usr/bin/setfacl + when: usr_bin_setfacl.stat.exists + +- name: Set path to setfacl + set_fact: + setfacl_path: /bin/setfacl + when: bin_setfacl.stat.exists + +- name: chmod -x setfacl + file: + path: "{{ setfacl_path }}" + mode: -x + when: setfacl_path is defined diff --git a/test/integration/targets/become_unprivileged/runme.sh b/test/integration/targets/become_unprivileged/runme.sh new file mode 100755 index 00000000000..6d52501fcc9 --- /dev/null +++ b/test/integration/targets/become_unprivileged/runme.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -ux + +ansible-playbook setup.yml -i inventory -v "$@" + +export ANSIBLE_KEEP_REMOTE_FILES=True +export ANSIBLE_COMMON_REMOTE_GROUP=notcoolenoughforroot +export ANSIBLE_BECOME_PASS='iWishIWereCoolEnoughForRoot!' + +ansible-playbook test.yml -i inventory -v "$@" + +# Do a few cleanup tasks (nuke users, groups, and homedirs, undo config changes) +ansible-playbook cleanup.yml -i inventory -v "$@" diff --git a/test/integration/targets/become_unprivileged/setup.yml b/test/integration/targets/become_unprivileged/setup.yml new file mode 100644 index 00000000000..8f631db69c3 --- /dev/null +++ b/test/integration/targets/become_unprivileged/setup.yml @@ -0,0 +1,8 @@ +- name: Set up host + hosts: ssh + gather_facts: yes + + # Default, just noted here to be explicit about what is happening: + remote_user: root + roles: + - setup_become_unprivileged diff --git a/test/integration/targets/become_unprivileged/test.yml b/test/integration/targets/become_unprivileged/test.yml new file mode 100644 index 00000000000..6905469d25b --- /dev/null +++ b/test/integration/targets/become_unprivileged/test.yml @@ -0,0 +1,8 @@ +- name: Run the test + hosts: ssh + gather_facts: yes + remote_user: unpriv1 + become: yes + become_user: unpriv2 + roles: + - become_unprivileged