Repository: ansible/ansible
Pull request: better handling of sudo flag replacement
fixes #76357

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
- Bugfix Pull Request

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below -->
become/sudo

Problem statement:
sudo module doesn't handle --non-interactive flag
### Summary

Setting ansible_become_flags to a value that includes "--non-interactive" results in "-on-interactive" being given to sudo because the sudo module is removing "-n" from flags, but isn't accounting for the long argument equivalent:
`flags = flags.replace('-n', '')`

### Issue Type

Bug Report

### Component Name

lib/ansible/plugins/become/sudo.py

### Ansible Version

```console
$ ansible --version
ansible 2.9.25
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/<redacted>/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /bin/ansible
  python version = 2.7.5 (default, Nov 16 2020, 22:23:17) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]
```


### Configuration

```console
$ ansible-config dump --only-changed
DEFAULT_KEEP_REMOTE_FILES(env: ANSIBLE_KEEP_REMOTE_FILES) = False
```


### OS / Environment

CentOS Linux release 7.9.2009 (Core)

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
ansible_become_flags: '--set-home --non-interactive --stdin'

# Run a play that requires escalation via sudo
ansible-playbook <play> --ask-become-pass
```


### Expected Results

The run will have a fatal failure, whose output message will include this:
`"module_stdout": "sudo: invalid option -- 'o'`

If the ansible-playbook command is run with the -vvv option then the relevant ssh connection will include this in the command that it runs:
`'/bin/sh -c '"'"'sudo --set-home -on-interactive --stdin -p "[sudo via ansible`

### Actual Results

```console
<target-server-1> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o 'User="myuser"' -o ConnectTimeout=10 -o ControlPath=/home/myuser/.ansible/cp/e98147b6a6 -tt target-server-1 '/bin/sh -c '"'"'sudo -on-interactive --set-home --stdin -p "[sudo via ansible, key=uldzwcgvhxenmnykzovhhyqccxmihgqv] password:" -u escalateuser /bin/sh -c '"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-uldzwcgvhxenmnykzovhhyqccxmihgqv ; /usr/bin/python /var/tmp/ansible-tmp-1637749767.73-18596-268317325457036/AnsiballZ_command.py'"'"'"'"'"'"'"'"' && sleep 0'"'"''
<target-server-1> (1, "sudo: invalid option -- 'o'\r\nusage: sudo -h | -K | -k | -V\r\nusage: sudo -v [-AknS] [-g group] [-h host] [-p prompt] [-u user]\r\nusage: sudo -l [-AknS] [-g group] [-h host] [-p prompt] [-U user] [-u user]\r\n            [command]\r\nusage: sudo [-AbEHknPS] [-r role] [-t type] [-C num] [-g group] [-h host] [-p\r\n            prompt] [-T timeout] [-u user] [VAR=value] [-i|-s] [<command>]\r\nusage: sudo -e [-AknS] [-r role] [-t type] [-C num] [-g group] [-h host] [-p\r\n            prompt] [-T timeout] [-u user] file ...\r\n", 'Shared connection to target-server-1 closed.\r\n')
<target-server-1> Failed to connect to the host via ssh: Shared connection to target-server-1 closed.
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct

Hints from issue discussion:
Files identified in the description:
* [`lib/ansible/plugins/become/sudo.py`](https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/become/sudo.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
```
flags = ' '.join(f for f in shlex.split(flags) if f != '-n')
```
`if  f not in ('-n', '--non-interactive')` , the problem is things like `-ln` as you can combine multiple switches

Commit message: slight optimization

Patch to review:
diff --git a/lib/ansible/plugins/become/sudo.py b/lib/ansible/plugins/become/sudo.py
index a4093b85f0bbca..9e7c78d3bdd19a 100644
--- a/lib/ansible/plugins/become/sudo.py
+++ b/lib/ansible/plugins/become/sudo.py
@@ -94,7 +94,13 @@ def build_become_command(self, cmd, shell):
         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', '')
+                for flag in list(flags.split(' ')):
+                    if flag.startswith('--'):
+                        continue
+                    elif 'n' in flag:
+                        flags = flags.replace(flag, flag.replace('n', ''))
+                flags = flags.replace('--non-interactive', '')
+
             prompt = '-p "%s"' % (self.prompt)
 
         user = self.get_option('become_user') or ''


Code review comments:
diff_hunk: @@ -94,7 +94,13 @@ def build_become_command(self, cmd, shell):
         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', '')
+                for flag in list(flags.split(' ')):
path: lib/ansible/plugins/become/sudo.py
text: @user1:
This could cause issues if there is anything in the args that might be a path with a space in it.  I'd really suggest using `shlex.split` instead, which will respect any quoting.  I think spliting into a list via `shlex.split`, and then rejoining will ultimately be the most safe.