diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:04:21 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:04:21 +0000 |
commit | 8a754e0858d922e955e71b253c139e071ecec432 (patch) | |
tree | 527d16e74bfd1840c85efd675fdecad056c54107 /lib/ansible/plugins/become | |
parent | Initial commit. (diff) | |
download | ansible-core-upstream/2.14.3.tar.xz ansible-core-upstream/2.14.3.zip |
Adding upstream version 2.14.3.upstream/2.14.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/ansible/plugins/become')
-rw-r--r-- | lib/ansible/plugins/become/__init__.py | 108 | ||||
-rw-r--r-- | lib/ansible/plugins/become/runas.py | 75 | ||||
-rw-r--r-- | lib/ansible/plugins/become/su.py | 168 | ||||
-rw-r--r-- | lib/ansible/plugins/become/sudo.py | 121 |
4 files changed, 472 insertions, 0 deletions
diff --git a/lib/ansible/plugins/become/__init__.py b/lib/ansible/plugins/become/__init__.py new file mode 100644 index 0000000..9dacf22 --- /dev/null +++ b/lib/ansible/plugins/become/__init__.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2018, 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 + +import shlex + +from abc import abstractmethod +from random import choice +from string import ascii_lowercase +from gettext import dgettext + +from ansible.errors import AnsibleError +from ansible.module_utils._text import to_bytes +from ansible.plugins import AnsiblePlugin + + +def _gen_id(length=32): + ''' return random string used to identify the current privilege escalation ''' + return ''.join(choice(ascii_lowercase) for x in range(length)) + + +class BecomeBase(AnsiblePlugin): + + name = None # type: str | None + + # messages for detecting prompted password issues + fail = tuple() # type: tuple[str, ...] + missing = tuple() # type: tuple[str, ...] + + # many connection plugins cannot provide tty, set to True if your become + # plugin requires a tty, i.e su + require_tty = False + + # prompt to match + prompt = '' + + def __init__(self): + super(BecomeBase, self).__init__() + self._id = '' + self.success = '' + + def get_option(self, option, hostvars=None, playcontext=None): + """ Overrides the base get_option to provide a fallback to playcontext vars in case a 3rd party plugin did not + implement the base become options required in Ansible. """ + # TODO: add deprecation warning for ValueError in devel that removes the playcontext fallback + try: + return super(BecomeBase, self).get_option(option, hostvars=hostvars) + except KeyError: + pc_fallback = ['become_user', 'become_pass', 'become_flags', 'become_exe'] + if option not in pc_fallback: + raise + + return getattr(playcontext, option, None) + + def expect_prompt(self): + """This function assists connection plugins in determining if they need to wait for + a prompt. Both a prompt and a password are required. + """ + return self.prompt and self.get_option('become_pass') + + def _build_success_command(self, cmd, shell, noexe=False): + if not all((cmd, shell, self.success)): + return cmd + + try: + cmd = shlex.quote('%s %s %s %s' % (shell.ECHO, self.success, shell.COMMAND_SEP, cmd)) + except AttributeError: + # TODO: This should probably become some more robust functionality used to detect incompat + raise AnsibleError('The %s shell family is incompatible with the %s become plugin' % (shell.SHELL_FAMILY, self.name)) + exe = getattr(shell, 'executable', None) + if exe and not noexe: + cmd = '%s -c %s' % (exe, cmd) + return cmd + + @abstractmethod + def build_become_command(self, cmd, shell): + self._id = _gen_id() + self.success = 'BECOME-SUCCESS-%s' % self._id + + def check_success(self, b_output): + b_success = to_bytes(self.success) + return any(b_success in l.rstrip() for l in b_output.splitlines(True)) + + def check_password_prompt(self, b_output): + ''' checks if the expected password prompt exists in b_output ''' + if self.prompt: + b_prompt = to_bytes(self.prompt).strip() + return any(l.strip().startswith(b_prompt) for l in b_output.splitlines()) + return False + + def _check_password_error(self, b_out, msg): + ''' returns True/False if domain specific i18n version of msg is found in b_out ''' + b_fail = to_bytes(dgettext(self.name, msg)) + return b_fail and b_fail in b_out + + def check_incorrect_password(self, b_output): + for errstring in self.fail: + if self._check_password_error(b_output, errstring): + return True + return False + + def check_missing_password(self, b_output): + for errstring in self.missing: + if self._check_password_error(b_output, errstring): + return True + return False diff --git a/lib/ansible/plugins/become/runas.py b/lib/ansible/plugins/become/runas.py new file mode 100644 index 0000000..0b7d466 --- /dev/null +++ b/lib/ansible/plugins/become/runas.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2018, 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 = """ + name: runas + short_description: Run As user + description: + - This become plugin allows your remote/login user to execute commands as another user via the windows runas facility. + author: ansible (@core) + version_added: "2.8" + options: + become_user: + description: User you 'become' to execute the task + ini: + - section: privilege_escalation + key: become_user + - section: runas_become_plugin + key: user + vars: + - name: ansible_become_user + - name: ansible_runas_user + env: + - name: ANSIBLE_BECOME_USER + - name: ANSIBLE_RUNAS_USER + keyword: + - name: become_user + required: True + become_flags: + description: Options to pass to runas, a space delimited list of k=v pairs + default: '' + ini: + - section: privilege_escalation + key: become_flags + - section: runas_become_plugin + key: flags + vars: + - name: ansible_become_flags + - name: ansible_runas_flags + env: + - name: ANSIBLE_BECOME_FLAGS + - name: ANSIBLE_RUNAS_FLAGS + keyword: + - name: become_flags + become_pass: + description: password + ini: + - section: runas_become_plugin + key: password + vars: + - name: ansible_become_password + - name: ansible_become_pass + - name: ansible_runas_pass + env: + - name: ANSIBLE_BECOME_PASS + - name: ANSIBLE_RUNAS_PASS + notes: + - runas is really implemented in the powershell module handler and as such can only be used with winrm connections. + - This plugin ignores the 'become_exe' setting as it uses an API and not an executable. + - The Secondary Logon service (seclogon) must be running to use runas +""" + +from ansible.plugins.become import BecomeBase + + +class BecomeModule(BecomeBase): + + name = 'runas' + + def build_become_command(self, cmd, shell): + # this is a noop, the 'real' runas is implemented + # inside the windows powershell execution subsystem + return cmd diff --git a/lib/ansible/plugins/become/su.py b/lib/ansible/plugins/become/su.py new file mode 100644 index 0000000..3a6fdea --- /dev/null +++ b/lib/ansible/plugins/become/su.py @@ -0,0 +1,168 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2018, 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 = """ + name: su + short_description: Substitute User + description: + - This become plugin allows your remote/login user to execute commands as another user via the su utility. + author: ansible (@core) + version_added: "2.8" + options: + become_user: + description: User you 'become' to execute the task + default: root + ini: + - section: privilege_escalation + key: become_user + - section: su_become_plugin + key: user + vars: + - name: ansible_become_user + - name: ansible_su_user + env: + - name: ANSIBLE_BECOME_USER + - name: ANSIBLE_SU_USER + keyword: + - name: become_user + become_exe: + description: Su executable + default: su + ini: + - section: privilege_escalation + key: become_exe + - section: su_become_plugin + key: executable + vars: + - name: ansible_become_exe + - name: ansible_su_exe + env: + - name: ANSIBLE_BECOME_EXE + - name: ANSIBLE_SU_EXE + keyword: + - name: become_exe + become_flags: + description: Options to pass to su + default: '' + ini: + - section: privilege_escalation + key: become_flags + - section: su_become_plugin + key: flags + vars: + - name: ansible_become_flags + - name: ansible_su_flags + env: + - name: ANSIBLE_BECOME_FLAGS + - name: ANSIBLE_SU_FLAGS + keyword: + - name: become_flags + become_pass: + description: Password to pass to su + required: False + vars: + - name: ansible_become_password + - name: ansible_become_pass + - name: ansible_su_pass + env: + - name: ANSIBLE_BECOME_PASS + - name: ANSIBLE_SU_PASS + ini: + - section: su_become_plugin + key: password + prompt_l10n: + description: + - List of localized strings to match for prompt detection + - If empty we'll use the built in one + - Do NOT add a colon (:) to your custom entries. Ansible adds a colon at the end of each prompt; + if you add another one in your string, your prompt will fail with a "Timeout" error. + default: [] + type: list + elements: string + ini: + - section: su_become_plugin + key: localized_prompts + vars: + - name: ansible_su_prompt_l10n + env: + - name: ANSIBLE_SU_PROMPT_L10N +""" + +import re +import shlex + +from ansible.module_utils._text import to_bytes +from ansible.plugins.become import BecomeBase + + +class BecomeModule(BecomeBase): + + name = 'su' + + # messages for detecting prompted password issues + fail = ('Authentication failure',) + + SU_PROMPT_LOCALIZATIONS = [ + 'Password', + '암호', + 'パスワード', + 'Adgangskode', + 'Contraseña', + 'Contrasenya', + 'Hasło', + 'Heslo', + 'Jelszó', + 'Lösenord', + 'Mật khẩu', + 'Mot de passe', + 'Parola', + 'Parool', + 'Pasahitza', + 'Passord', + 'Passwort', + 'Salasana', + 'Sandi', + 'Senha', + 'Wachtwoord', + 'ססמה', + 'Лозинка', + 'Парола', + 'Пароль', + 'गुप्तशब्द', + 'शब्दकूट', + 'సంకేతపదము', + 'හස්පදය', + '密码', + '密碼', + '口令', + ] + + def check_password_prompt(self, b_output): + ''' checks if the expected password prompt exists in b_output ''' + + prompts = self.get_option('prompt_l10n') or self.SU_PROMPT_LOCALIZATIONS + b_password_string = b"|".join((br'(\w+\'s )?' + to_bytes(p)) for p in prompts) + # Colon or unicode fullwidth colon + b_password_string = b_password_string + to_bytes(u' ?(:|:) ?') + b_su_prompt_localizations_re = re.compile(b_password_string, flags=re.IGNORECASE) + return bool(b_su_prompt_localizations_re.match(b_output)) + + def build_become_command(self, cmd, shell): + super(BecomeModule, self).build_become_command(cmd, shell) + + # Prompt handling for ``su`` is more complicated, this + # is used to satisfy the connection plugin + self.prompt = True + + if not cmd: + return cmd + + exe = self.get_option('become_exe') or self.name + flags = self.get_option('become_flags') or '' + user = self.get_option('become_user') or '' + success_cmd = self._build_success_command(cmd, shell) + + return "%s %s %s -c %s" % (exe, flags, user, shlex.quote(success_cmd)) diff --git a/lib/ansible/plugins/become/sudo.py b/lib/ansible/plugins/become/sudo.py new file mode 100644 index 0000000..fb285f0 --- /dev/null +++ b/lib/ansible/plugins/become/sudo.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2018, 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 = """ + name: sudo + short_description: Substitute User DO + description: + - This become plugin allows your remote/login user to execute commands as another user via the sudo utility. + author: ansible (@core) + version_added: "2.8" + 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 + keyword: + - name: become_user + become_exe: + description: Sudo executable + default: sudo + ini: + - section: privilege_escalation + key: become_exe + - section: sudo_become_plugin + key: executable + vars: + - name: ansible_become_exe + - name: ansible_sudo_exe + env: + - name: ANSIBLE_BECOME_EXE + - name: ANSIBLE_SUDO_EXE + keyword: + - name: become_exe + become_flags: + description: Options to pass to 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 + keyword: + - name: become_flags + become_pass: + description: Password to pass to 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 +""" + +import re +import shlex + +from ansible.plugins.become import BecomeBase + + +class BecomeModule(BecomeBase): + + name = 'sudo' + + # 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 = self.get_option('become_exe') or self.name + + 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 + reflag = [] + for flag in shlex.split(flags): + if flag in ('-n', '--non-interactive'): + continue + elif not flag.startswith('--'): + # handle -XnxxX flags only + flag = re.sub(r'^(-\w*)n(\w*.*)', r'\1\2', flag) + reflag.append(flag) + flags = shlex.join(reflag) + + prompt = '-p "%s"' % (self.prompt) + + user = self.get_option('become_user') or '' + if user: + user = '-u %s' % (user) + + return ' '.join([becomecmd, flags, prompt, user, self._build_success_command(cmd, shell)]) |