From 966db65ea25efcd1869c5b4a020b3473cccf5033 Mon Sep 17 00:00:00 2001 From: David Shrewsbury Date: Wed, 11 Feb 2026 12:48:04 -0500 Subject: [PATCH] Fix local connection interaction with become plugins (#86473) (#86509) The local connection plugin is incorrectly passing a bytearray to methods of become plugins that expect bytes. A recent change to the su plugin exposed this by breaking the become plugin for different locale settings (the bytearray was not properly converted to a str for comparison operations). This changes the local connection plugin to send bytes. (cherry picked from commit 90595736ed35a88e63b6c680d6a1daa38a2ccce1) --- .../fragments/86473_su_become_local.yml | 2 + lib/ansible/plugins/become/__init__.py | 2 +- lib/ansible/plugins/connection/local.py | 4 +- test/integration/targets/become_su/aliases | 1 + .../targets/become_su/tasks/main.yml | 43 +++++++++++++++++++ test/units/plugins/become/test_su.py | 2 +- 6 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 changelogs/fragments/86473_su_become_local.yml diff --git a/changelogs/fragments/86473_su_become_local.yml b/changelogs/fragments/86473_su_become_local.yml new file mode 100644 index 00000000000..f632b1015c2 --- /dev/null +++ b/changelogs/fragments/86473_su_become_local.yml @@ -0,0 +1,2 @@ +bugfixes: + - local connection - Pass correct type to become plugins when checking password (https://github.com/ansible/ansible/issues/86458) diff --git a/lib/ansible/plugins/become/__init__.py b/lib/ansible/plugins/become/__init__.py index 235287c07f3..6613eadff93 100644 --- a/lib/ansible/plugins/become/__init__.py +++ b/lib/ansible/plugins/become/__init__.py @@ -105,7 +105,7 @@ class BecomeBase(AnsiblePlugin): 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): + def check_password_prompt(self, b_output: bytes) -> bool: """ checks if the expected password prompt exists in b_output """ if self.prompt: b_prompt = to_bytes(self.prompt).strip() diff --git a/lib/ansible/plugins/connection/local.py b/lib/ansible/plugins/connection/local.py index 6fd1ea41fe1..5fff416e2a6 100644 --- a/lib/ansible/plugins/connection/local.py +++ b/lib/ansible/plugins/connection/local.py @@ -201,8 +201,8 @@ class Connection(ConnectionBase): raise AnsibleError(become_error_msg('Premature end of stream')) if expect_password_prompt and ( - self.become.check_password_prompt(become_stdout[last_stdout_prompt_offset:]) or - self.become.check_password_prompt(become_stderr[last_stderr_prompt_offset:]) + self.become.check_password_prompt(bytes(become_stdout[last_stdout_prompt_offset:])) or + self.become.check_password_prompt(bytes(become_stderr[last_stderr_prompt_offset:])) ): if sent_password: raise AnsibleError(become_error_msg('Duplicate become password prompt encountered')) diff --git a/test/integration/targets/become_su/aliases b/test/integration/targets/become_su/aliases index 6b92895399a..0cf10c4b018 100644 --- a/test/integration/targets/become_su/aliases +++ b/test/integration/targets/become_su/aliases @@ -5,6 +5,7 @@ gather_facts/no needs/root needs/target/setup_become_user_pair needs/target/setup_test_user +needs/target/setup_pexpect setup/always/setup_passlib_controller # required for setup_test_user skip/macos # requires a TTY skip/freebsd # appears to require a TTY (ignores password input from stdin) diff --git a/test/integration/targets/become_su/tasks/main.yml b/test/integration/targets/become_su/tasks/main.yml index fce4338a120..9f61f37ada3 100644 --- a/test/integration/targets/become_su/tasks/main.yml +++ b/test/integration/targets/become_su/tasks/main.yml @@ -61,3 +61,46 @@ - wrong_su_prompt is unreachable - ansible_connection != "local" or wrong_su_prompt.msg is contains "Timed out waiting for become success or become password prompt" - ansible_connection != "ssh" or wrong_su_prompt.msg is contains "waiting for privilege escalation prompt" + +# The bug from issue https://github.com/ansible/ansible/issues/86458 presents itself only on local connections +# and only when the password is entered interactively using -K/--ask-become-pass. +- name: Test different locale + when: ansible_os_family == 'RedHat' and ansible_connection == 'local' + block: + - import_role: + name: setup_pexpect + + - name: install language pack and pexpect + dnf: + name: + - langpacks-uk + - glibc-langpack-uk + state: present + + - name: verify language pack is enabled + command: locale -a + register: installed_languages + + - assert: + that: "'uk_UA.utf8' in installed_languages.stdout" + + - name: test su prompt basic success with alternate language on local connection + expect: + command: "ansible -m command -a whoami localhost -b --become-method su -K" + responses: + BECOME password: + - "{{ target_user_password }}" + environment: + LC_ALL: uk_UA.utf8 + ANSIBLE_BECOME_USER: "{{ target_user_name }}" + ANSIBLE_BECOME_EXE: /tmp/sushim.sh + ANSIBLE_BECOME_FLAGS: --intermediate-user {{ intermediate_user_name | quote }} + # protect our target user password + no_log: true + always: + - name: remove language pack + dnf: + name: + - langpacks-uk + - glibc-langpack-uk + state: absent diff --git a/test/units/plugins/become/test_su.py b/test/units/plugins/become/test_su.py index 9088657b934..dc662a38750 100644 --- a/test/units/plugins/become/test_su.py +++ b/test/units/plugins/become/test_su.py @@ -70,4 +70,4 @@ def test_check_password_prompt_escaping(mocker) -> None: mocker.patch.object(become, 'get_option', return_value=['(invalid regex']) - assert become.check_password_prompt('(invalid regex:') is True + assert become.check_password_prompt(b'(invalid regex:') is True