From eccc8d88b6015e0901d2968e60064cd6549f9be9 Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Wed, 24 Mar 2021 17:48:53 +0100 Subject: [PATCH] Add support for `sudo su -` using password auth (#2054) (#2097) * Add support for `sudo su -` using password auth Allow users to run Ansible tasks through `sudo su -` using password auth - Feature Pull Request sudosu So I have been using this at various customers for bootstrapping Ansible mostly. Often you have an existing setup where there is a user that has root-access enabled through sudo, but only to run `su` to log using the user's password. In these specific cases the root password is unique to the system and therefore not an easy way to automate bootstrapping. Having a `sudo su -` become option **with password prompt** is not possible with the existing become methods (neither sudo nor su can be used) by abusing `become_exe` or `become_flags`. This fixes ansible/ansible#12686 * Fix all reported issues * Add unit tests * Apply suggestions from code review * Update plugins/become/sudosu.py Co-authored-by: Felix Fontein * Update tests/unit/plugins/become/test_sudosu.py Co-authored-by: Felix Fontein * Update tests/unit/plugins/become/test_sudosu.py Co-authored-by: Felix Fontein Co-authored-by: Felix Fontein (cherry picked from commit db26514bf11ec64ca44d99d8b27d6b5518c51483) Co-authored-by: Dag Wieers --- plugins/become/sudosu.py | 91 ++++++++++++++++++++++++ tests/unit/plugins/become/test_sudosu.py | 45 ++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 plugins/become/sudosu.py create mode 100644 tests/unit/plugins/become/test_sudosu.py diff --git a/plugins/become/sudosu.py b/plugins/become/sudosu.py new file mode 100644 index 0000000000..e9668e6522 --- /dev/null +++ b/plugins/become/sudosu.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2021, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = """ + become: sudosu + short_description: Run tasks using sudo su - + description: + - This become plugins allows your remote/login user to execute commands as another user via the C(sudo) and C(su) utilities combined. + author: + - Dag Wieers (@dagwieers) + version_added: 2.4.0 + options: + become_user: + description: User you 'become' to execute the task. + default: root + ini: + - section: privilege_escalation + key: become_user + - section: sudo_become_plugin + key: user + vars: + - name: ansible_become_user + - name: ansible_sudo_user + env: + - name: ANSIBLE_BECOME_USER + - name: ANSIBLE_SUDO_USER + become_flags: + description: Options to pass to C(sudo). + default: -H -S -n + ini: + - section: privilege_escalation + key: become_flags + - section: sudo_become_plugin + key: flags + vars: + - name: ansible_become_flags + - name: ansible_sudo_flags + env: + - name: ANSIBLE_BECOME_FLAGS + - name: ANSIBLE_SUDO_FLAGS + become_pass: + description: Password to pass to C(sudo). + required: false + vars: + - name: ansible_become_password + - name: ansible_become_pass + - name: ansible_sudo_pass + env: + - name: ANSIBLE_BECOME_PASS + - name: ANSIBLE_SUDO_PASS + ini: + - section: sudo_become_plugin + key: password +""" + + +from ansible.plugins.become import BecomeBase + + +class BecomeModule(BecomeBase): + + name = 'community.general.sudosu' + + # messages for detecting prompted password issues + fail = ('Sorry, try again.',) + missing = ('Sorry, a password is required to run sudo', 'sudo: a password is required') + + def build_become_command(self, cmd, shell): + super(BecomeModule, self).build_become_command(cmd, shell) + + if not cmd: + return cmd + + becomecmd = 'sudo' + + flags = self.get_option('become_flags') or '' + prompt = '' + if self.get_option('become_pass'): + self.prompt = '[sudo via ansible, key=%s] password:' % self._id + if flags: # this could be simplified, but kept as is for now for backwards string matching + flags = flags.replace('-n', '') + prompt = '-p "%s"' % (self.prompt) + + user = self.get_option('become_user') or '' + if user: + user = '%s' % (user) + + return ' '.join([becomecmd, flags, prompt, 'su -l', user, self._build_success_command(cmd, shell)]) diff --git a/tests/unit/plugins/become/test_sudosu.py b/tests/unit/plugins/become/test_sudosu.py new file mode 100644 index 0000000000..4e5c998f09 --- /dev/null +++ b/tests/unit/plugins/become/test_sudosu.py @@ -0,0 +1,45 @@ +# (c) 2012-2014, Michael DeHaan +# (c) 2021 Ansible Project +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible import context +from ansible.playbook.play_context import PlayContext +from ansible.plugins.loader import become_loader + + +def test_sudosu(mocker, parser, reset_cli_args): + options = parser.parse_args([]) + context._init_global_context(options) + play_context = PlayContext() + + default_cmd = "/bin/foo" + default_exe = "/bin/bash" + sudo_exe = 'sudo' + sudo_flags = '-H -s -n' + + cmd = play_context.make_become_cmd(cmd=default_cmd, executable=default_exe) + assert cmd == default_cmd + + success = 'BECOME-SUCCESS-.+?' + + play_context.become = True + play_context.become_user = 'foo' + play_context.set_become_plugin(become_loader.get('community.general.sudosu')) + play_context.become_flags = sudo_flags + cmd = play_context.make_become_cmd(cmd=default_cmd, executable=default_exe) + + assert (re.match("""%s %s su -l %s %s -c 'echo %s; %s'""" % (sudo_exe, sudo_flags, play_context.become_user, + default_exe, success, default_cmd), cmd) is not None) + + play_context.become_pass = 'testpass' + cmd = play_context.make_become_cmd(cmd=default_cmd, executable=default_exe) + assert (re.match("""%s %s -p "%s" su -l %s %s -c 'echo %s; %s'""" % (sudo_exe, sudo_flags.replace('-n', ''), + r"\[sudo via ansible, key=.+?\] password:", play_context.become_user, + default_exe, success, default_cmd), cmd) is not None)