Repository: ansible/ansible

Issue:
'become' tasks fail (with a timeout msg) in 2.3.0
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
become

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.3.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.5 (default, Nov  6 2016, 00:28:07) [GCC 4.8.5 20150623 (Red Hat 4.8.5-11)]
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
no customization

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
Linux

##### SUMMARY
<!--- Explain the problem briefly -->
I need to execute tasks as another (let's call him Bob) on a Linux system. Thus I use 'become' for privilege escalation.
It works fine in 2.2.2 but fail in 2.3.0

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: Simple 'become' use
  hosts: all
  vars:
    ansible_become_pass: Bob_password
  tasks:
    - name: "dummy shell call"
      shell: echo foo
      become: true
      become_user: Bob
      become_method: su
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
This task runs fine when using Ansible 2.2.2
```
ansible 2.2.2.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```

```
TASK [dummy shell call] ********************************************************
changed: [my_server]
```


##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
But it fails when using Ansible 2.3.0
```
TASK [dummy shell call] **********************************************************************************************************************
fatal: [my_server]: FAILED! => {"failed": true, "msg": "Timeout (12s) waiting for privilege escalation prompt: "}
```

When using the -vvvv option:
```
TASK [dummy shell call] **********************************************************************************************************************
task path: /my_playbook_path/Issues/become_2-3-0.yml:15
Using module file /my_ansible_module_path/python2.7/site-packages/ansible/modules/commands/command.py
<my_server> ESTABLISH SSH CONNECTION FOR USER: Alice
<my_server> SSH: EXEC sshpass -d12 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o Port=22 -o User=Alice -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/db793ac6ea my_server '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /tmp/ansible-tmp-1492501296.25-151354426634090 `" && echo ansible-tmp-1492501296.25-151354426634090="` echo /tmp/ansible-tmp-1492501296.25-151354426634090 `" ) && sleep 0'"'"''
<my_server> (0, 'ansible-tmp-1492501296.25-151354426634090=/tmp/ansible-tmp-1492501296.25-151354426634090\n', 'OpenSSH_6.6.1, OpenSSL 1.0.1e-fips 11 Feb 2013\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 56: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 2872\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
<my_server> PUT /tmp/tmpejqNBr TO /tmp/ansible-tmp-1492501296.25-151354426634090/command.py
<my_server> SSH: EXEC sshpass -d12 sftp -o BatchMode=no -b - -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o Port=22 -o User=Alice -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/db793ac6ea '[my_server]'
<my_server> (0, 'sftp> put /tmp/tmpejqNBr /tmp/ansible-tmp-1492501296.25-151354426634090/command.py\n', 'OpenSSH_6.6.1, OpenSSL 1.0.1e-fips 11 Feb 2013\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 56: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 2872\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug2: Remote version: 3\r\ndebug2: Server supports extension "posix-rename@openssh.com" revision 1\r\ndebug2: Server supports extension "statvfs@openssh.com" revision 2\r\ndebug2: Server supports extension "fstatvfs@openssh.com" revision 2\r\ndebug3: Sent message fd 5 T:16 I:1\r\ndebug3: SSH_FXP_REALPATH . -> /my_user_path/user/Alice size 0\r\ndebug3: Looking up /tmp/tmpejqNBr\r\ndebug3: Sent message fd 5 T:17 I:2\r\ndebug3: Received stat reply T:101 I:2\r\ndebug1: Couldn\'t stat remote file: No such file or directory\r\ndebug3: Sent message SSH2_FXP_OPEN I:3 P:/tmp/ansible-tmp-1492501296.25-151354426634090/command.py\r\ndebug3: Sent message SSH2_FXP_WRITE I:4 O:0 S:32768\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: In write loop, ack for 4 32768 bytes at 0\r\ndebug3: Sent message SSH2_FXP_WRITE I:5 O:32768 S:25525\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: In write loop, ack for 5 25525 bytes at 32768\r\ndebug3: Sent message SSH2_FXP_CLOSE I:4\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
<my_server> ESTABLISH SSH CONNECTION FOR USER: Alice
<my_server> SSH: EXEC sshpass -d12 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o Port=22 -o User=Alice -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/db793ac6ea my_server '/bin/sh -c '"'"'setfacl -m u:Bob:r-x /tmp/ansible-tmp-1492501296.25-151354426634090/ /tmp/ansible-tmp-1492501296.25-151354426634090/command.py && sleep 0'"'"''
<my_server> (0, '', 'OpenSSH_6.6.1, OpenSSL 1.0.1e-fips 11 Feb 2013\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 56: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 2872\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
<my_server> ESTABLISH SSH CONNECTION FOR USER: Alice
<my_server> SSH: EXEC sshpass -d12 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o Port=22 -o User=Alice -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/db793ac6ea -tt my_server '/bin/sh -c '"'"'su  Bob -c '"'"'"'"'"'"'"'"'/bin/sh -c '"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-sqskdwgxdeyelswfgxyeieqekkfpbnmj; /usr/bin/python /tmp/ansible-tmp-1492501296.25-151354426634090/command.py'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"''"'"'"'"'"'"'"'"' && sleep 0'"'"''
fatal: [my_server]: FAILED! => {
    "failed": true,
    "msg": "Timeout (12s) waiting for privilege escalation prompt: "
}
```
I'm pretty new to Ansible. Am I missing something ?


Discussion:
We are also experiencing more occurrence of this issue after upgrading to ansible 2.3, any idea why?
What are the contents in /etc/ssh/sshd_config?
@snowsky please find enclosed the /etc/ssh/sshd_config file.
Please note that the same file is used (and worked) with Ansible 2.2.2

[sshd_config.txt](https://github.com/ansible/ansible/files/930050/sshd_config.txt)

Same here. Getting the privilege escalation timeout while ping with become. With 2.2 works just fine.
Ok, just confirmed my ssh config file is the same as yours using password. What is the ansible command line you used? 
@snowsky , here's what I used for both 2.2.2 and 2.3.0. It did fail on **2.3.0** and pass with no issue on **2.2.0**

ansible -m ping -i hosts all -b --become-method=su --become-user=laschlo --ask-become-pass
@snowsky I'm not using any special parameter/option.

`ansible-playbook -i /my_hosts_path/my_hosts_file /my_playbook_path/Issues/become_2-3-0.yml`
This can be reproduced with a simplest test case
1) no playbooks
2) single host "localhost" in hosts file

```
$ ansible -a : -b --become-method=su -K all                                    
SU password: 
localhost | FAILED | rc=-1 >>
Timeout (12s) waiting for privilege escalation prompt: 
```

However, this does work with "local" connection method.
The timeout actually is 10s in sshpass command line.
Still not able to reproduce, but you may want to work around it by async parameter.
Here are more workarounds, https://github.com/ansible/ansible/issues/14426, but not fix.
test case with empty configuration:

```
$ echo localhost > /tmp/hosts                                                                                                                                                                                                                                                  
$ ansible -a : -b --become-method=su -K -i /tmp/hosts all                                                                                                                                                                                                               
SU password: 
localhost | FAILED | rc=-1 >>
Timeout (12s) waiting for privilege escalation prompt: 
```
```
$ ansible --version
ansible 2.3.0.0
  config file = 
  configured module search path = Default w/o overrides
  python version = 2.7.13 (default, Apr 15 2017, 01:13:03) [GCC 4.2.1 Compatible FreeBSD Clang 3.8.0 (tags/RELEASE_380/final 262564)]
$ ssh -V                                                                                                                                                                                                                                                                       
OpenSSH_7.2p2, OpenSSL 1.0.2k-freebsd  26 Jan 2017
$ pip freeze
ansible==2.3.0.0
appdirs==1.4.3
asn1crypto==0.22.0
cffi==1.10.0
cryptography==1.8.1
enum34==1.1.6
idna==2.5
ipaddress==1.0.18
Jinja2==2.9.6
MarkupSafe==1.0
packaging==16.8
paramiko==2.1.2
pyasn1==0.2.3
pycparser==2.17
pycrypto==2.6.1
pyparsing==2.2.0
PyYAML==3.12
six==1.10.0
```
I have reproduced this issue, and have bisected the commit to 6f77498700a989c6b42f4f4d050d98df1de74f9e

I feel like I have seen this commit referenced in another issue too, but I can't seem to locate it right now.
cc @abadger 

Resolving patch:
diff --git a/lib/ansible/plugins/connection/__init__.py b/lib/ansible/plugins/connection/__init__.py
--- a/lib/ansible/plugins/connection/__init__.py
+++ b/lib/ansible/plugins/connection/__init__.py
@@ -258,6 +258,8 @@ def check_password_prompt(self, b_output):
             if not b_lines:
                 return False
             return b_lines[-1].strip().endswith(b_prompt) or b_lines[0].strip().endswith(b_prompt)
+        else:
+            return self._play_context.prompt(b_output)
 
     def check_incorrect_password(self, b_output):
         b_incorrect_password = to_bytes(gettext.dgettext(self._play_context.become_method, C.BECOME_ERROR_STRINGS[self._play_context.become_method]))


Test patch: