Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

synchronize with password authentication does not work with python 2 #56629

Closed
kostrzewa9ld opened this issue May 20, 2019 · 27 comments · Fixed by #66542
Closed

synchronize with password authentication does not work with python 2 #56629

kostrzewa9ld opened this issue May 20, 2019 · 27 comments · Fixed by #66542
Labels
affects_2.8 This issue/PR affects Ansible v2.8 bug This issue/PR relates to a bug. easyfix This issue is considered easy to fix by aspiring contributors. files Files category has_pr This issue has an associated PR. module This issue/PR relates to a module. P3 Priority 3 - Approved, No Time Limitation python3 support:core This issue/PR relates to code supported by the Ansible Engineering Team. traceback This issue/PR includes a traceback.

Comments

@kostrzewa9ld
Copy link

kostrzewa9ld commented May 20, 2019

SUMMARY

In Ansible 2.8 (in 1403744), synchronize module wraps rsync command in sshpass automatically if password authentication is enabled. It is a great improvement, however, the way it was implemented works only with Python 3 (due to usage of pass_fds of subprocess.Popen, I suppose).
Previously there was a way to force synchronize to work with password-based authentication with Python 2 by passing proper rsync_opts, now it fails with "Broken pipe" error.

ISSUE TYPE
  • Bug Report
COMPONENT NAME

synchronize

ANSIBLE VERSION
ansible 2.8.0
  config file = /work/bigdata/bigdata/platform/ansible/ansible.cfg
  configured module search path = [u'/work/bigdata/bigdata/platform/ansible/library']
  ansible python module location = /work/virtualenv/bigdata/local/lib/python2.7/site-packages/ansible
  executable location = /work/virtualenv/bigdata/bin/ansible
  python version = 2.7.15rc1 (default, Apr 15 2018, 21:51:34) [GCC 7.3.0]
CONFIGURATION

OS / ENVIRONMENT

Ubuntu 18.04 (4.15.0-50-generic)

STEPS TO REPRODUCE
mkdir -p /tmp/foo
echo bar > /tmp/foo/bar
docker run --name rsync-server -d --rm -p 9000:22 -e PASSWORD=password axiom/rsync-server
ansible -m synchronize -a 'src=/tmp/foo dest=/tmp' -i localhost, -e ansible_password=password -e ansible_port=9000 -u root all
docker rm -f rsync-server
EXPECTED RESULTS

I expected synchronize module to succeed.

ACTUAL RESULTS

synchronize module fails with:

The full traceback is:
WARNING: The below traceback may *not* be related to the actual failure.
  File "/tmp/ansible_synchronize_payload_vWRlFT/ansible_synchronize_payload.zip/ansible/module_utils/basic.py", line 2563, in run_command
    before_communicate_callback(cmd)
  File "/tmp/ansible_synchronize_payload_vWRlFT/__main__.py", line 581, in _write_********_to_pipe
    os.write(_sshpass_pipe[1], to_bytes(rsync_********) + b'\n')

localhost | FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false, 
    "cmd": "sshpass -d11 /usr/bin/rsync --delay-updates -F --compress --archive '--rsh=/usr/bin/ssh -S none -o Port=9000 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' '--out-format=<<CHANGED>>%i %n%L' /tmp/foo ''", 
    "invocation": {
        "module_args": {
            "_local_rsync_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "_local_rsync_path": "rsync", 
            "_substitute_controller": false, 
            "archive": true, 
            "checksum": false, 
            "compress": true, 
            "copy_links": false, 
            "delete": false, 
            "dest": "root@localhost:/tmp", 
            "dest_port": 9000, 
            "dirs": false, 
            "existing_only": false, 
            "group": null, 
            "link_dest": null, 
            "links": null, 
            "mode": "push", 
            "owner": null, 
            "partial": false, 
            "perms": null, 
            "private_key": null, 
            "recursive": null, 
            "rsync_opts": [], 
            "rsync_path": null, 
            "rsync_timeout": 0, 
            "set_remote_user": true, 
            "src": "/tmp/foo", 
            "ssh_args": null, 
            "times": null, 
            "verify_host": false
        }
    }, 
    "msg": "[Errno 32] Broken pipe", 
    "rc": 32
}

It works when I set python interpreter to python3 like so:

ansible -m synchronize -a 'src=/tmp/foo dest=/tmp' -i localhost, -e ansible_password=password -e ansible_port=9000 -u root -e ansible_python_interpreter=python3 all
@ansibot
Copy link
Contributor

ansibot commented May 20, 2019

Files identified in the description:

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

click here for bot help

@ansibot
Copy link
Contributor

ansibot commented May 20, 2019

cc @tima
click here for bot help

@ansibot ansibot added affects_2.8 This issue/PR affects Ansible v2.8 bug This issue/PR relates to a bug. files Files category module This issue/PR relates to a module. needs_triage Needs a first human triage before being processed. python3 support:core This issue/PR relates to code supported by the Ansible Engineering Team. traceback This issue/PR includes a traceback. labels May 20, 2019
@ahuffman
Copy link
Contributor

👍 Also seeing this issue on RHEL7:

{
    "changed": false,
    "_ansible_no_log": false,
    "cmd": "sshpass",
    "invocation": {
        "module_args": {
            "partial": false,
            "links": null,
            "copy_links": false,
            "perms": null,
            "owner": null,
            "rsync_path": null,
            "dest_port": null,
            "_local_rsync_path": "rsync",
            "group": null,
            "link_dest": null,
            "archive": true,
            "_local_rsync_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "_substitute_controller": false,
            "verify_host": false,
            "dirs": false,
            "private_key": null,
            "dest": "/home/my-user/webservice/install/ng/",
            "compress": true,
            "rsync_timeout": 0,
            "rsync_opts": [],
            "set_remote_user": true,
            "existing_only": false,
            "src": "/home/my-user/webservice/repo/web-front/",
            "recursive": null,
            "times": null,
            "mode": "push",
            "checksum": false,
            "ssh_args": null,
            "delete": false
        }
    },
    "rc": 2,
    "msg": "[Errno 2] No such file or directory"
}

My task looks like this:

- name: "Ensure front-end is deployed from cache"
  synchronize:
    src: "{{ ws_user_home }}/webservice/repo/web-front/"
    dest: "{{ ws_user_home }}/webservice/install/ng/"
  delegate_to: "{{ inventory_hostname }}"
  become: True
  become_user: "{{ ws_user }}"
ansible 2.8.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Mar 26 2019, 22:13:06) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]

@samdoran samdoran added P3 Priority 3 - Approved, No Time Limitation and removed needs_triage Needs a first human triage before being processed. labels May 23, 2019
@ahuffman
Copy link
Contributor

After some more testing to determine if I could figure out a workaround:
1.) sshpass was not installed on my target host
2.) After installing sshpass, which seems to now be an undocumented prerequisite, cmd: sshpass turns into sshpass -d6 /bin/rsync --delay-updates -F --compress --archive '--out-format=<<CHANGED>>%i %n%L' /home/my-user/webservice/repo/web-front/ /home/my-user/webservice/install/ng/
3.) In item 2.) above, there is an option '--out-format', which is single-quoted, and doubt would work, so probably yet another issue.
4.) Long-story, still does not work with sshpass on the node.
5.) Tried passing params to the synchronize task of set_remote_user: False and use_ssh_args: True but did not help either.

dw added a commit to mitogen-hq/mitogen that referenced this issue May 27, 2019
dw added a commit to mitogen-hq/mitogen that referenced this issue May 27, 2019
* origin/issue587:
  issue #587: docs: update Changelog.
  issue #587: disable SSH key setup, it breaks unit tests
  issue #587: attempt to fix Mac Azure job
  issue #587: Add 2.8.0/Py2.7 job to Azure
  issue #587: use deadsnakes PPA Python because VSTS version is junk
  issue #587: workaround for ansible/ansible#56629
  issue #587: enable spawn_reverse_shell and tidy up Azure step names
  issue #587: ensure Azure worker has a working SSH configuration
  issue #587: import spawn_reverse_shell.py script.
  issue #587: support pausing ansible_tests if flag file exists
  issue #587: "state: absent" was removed in 2.8.0
  issue #587: consistent become_exe() behaviour for older Ansibles.
  issue #587: update stub_connections/ test to use new doas var
  issue #587: update MODULE FAILURE message format for post >2.7
  issue #587: fix syntax error due to presence of comment
  issue #587: update mitogen_doas doc to match varible change.
  issue #587: disable deprecation_warnings for CI.
  issue #587: mitogen_doas should not become_exe for doas_path
  issue #587: 2.8 whitespace handling was improved.
  issue #587: 2.8 PlayContext lacks sudo_flags attribute.
  issue #587: 2.8 PluginLoader.get() introduced new collection_list kwarg
  issue #587: 2.8 PlayContext.connection no longer contains connection name
  issue #587: bump max Ansible version
@falcon78921
Copy link
Contributor

falcon78921 commented Jun 10, 2019

I also see this error on CentOS 7 (7.6.1810 (Core)):

{
    "exception": "WARNING: The below traceback may *not* be related to the actual failure.\n  File \"/tmp/ansible_synchronize_payload_hRW8JF/ansible_synchronize_payload.zip/ansible/module_utils/basic.py\", line 2563, in run_command\n    before_communicate_callback(cmd)\n  File \"/tmp/ansible_synchronize_payload_hRW8JF/__main__.py\", line 581, in _write_password_to_pipe\n    os.write(_sshpass_pipe[1], to_bytes(rsync_password) + b'\\n')\n",
    "cmd": "sshpass -d12 /usr/bin/rsync --delay-updates -F --compress --archive '--rsh=/usr/bin/ssh -S none -o Port=2002 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' '--out-format=<<CHANGED>>%i %n%L' /opt/test.txt ''",
    "_ansible_no_log": false,
    "changed": false,
    "rc": 32,
    "invocation": {
        "module_args": {
            "dirs": false,
            "private_key": null,
            "partial": false,
            "links": null,
            "copy_links": false,
            "perms": null,
            "compress": true,
            "rsync_timeout": 0,
            "src": "/opt/test.txt",
            "rsync_opts": [],
            "owner": null,
            "existing_only": false,
            "set_remote_user": true,
            "_local_rsync_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "archive": true,
            "dest_port": 2002,
            "_local_rsync_path": "rsync",
            "group": null,
            "link_dest": null,
            "dest": "root@CentOS1:/opt/",
            "recursive": null,
            "times": null,
            "rsync_path": null,
            "_substitute_controller": false,
            "mode": "push",
            "checksum": false,
            "verify_host": false,
            "ssh_args": null,
            "delete": false
        }
    },
    "msg": "[Errno 32] Broken pipe"
}

I had success with Python3 and Ansible 2.8:

{
    "changed": true,
    "msg": "<f+++++++++ test.txt\n",
    "rc": 0,
    "cmd": "sshpass -d4 /usr/bin/rsync --delay-updates -F --compress --archive --rsh=/usr/bin/ssh -S none -o Port=2000 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null --out-format=<<CHANGED>>%i %n%L /opt/test.txt root@CentOS1:/opt/",
    "stdout_lines": [
        "<f+++++++++ test.txt"
    ],
    "invocation": {
        "module_args": {
            "src": "/opt/test.txt",
            "dest": "root@CentOS1:/opt/",
            "_local_rsync_path": "rsync",
            "_local_rsync_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "dest_port": 2000,
            "rsync_path": null,
            "delete": false,
            "_substitute_controller": false,
            "archive": true,
            "checksum": false,
            "compress": true,
            "existing_only": false,
            "dirs": false,
            "copy_links": false,
            "set_remote_user": true,
            "rsync_timeout": 0,
            "rsync_opts": [],
            "partial": false,
            "verify_host": false,
            "mode": "push",
            "private_key": null,
            "recursive": null,
            "links": null,
            "perms": null,
            "times": null,
            "owner": null,
            "group": null,
            "ssh_args": null,
            "link_dest": null
        }
    },
    "_ansible_no_log": false
}

stefannica added a commit to stefannica/automation that referenced this issue Jun 17, 2019
Some fixes for the local runner for the unified CI:
  - ansible 2.8.0 introduces a breakage in the synchronization
  module [1]. Freezing the version below 2.8.0 temporarily,
  until the issue is resolved.
  - using an input.yml file extracted from one of the Jenkins
  jobs to run steps locally has an issue when the `os_project_name`
  variable isn't set
  - more recent versions of ansible complain about the indentation
  of the python code in heat-generator/tasks/main.yml

[1] ansible/ansible#56629
@juanvalino
Copy link
Contributor

I'm having this issue too.

@notoriousbsd
Copy link

notoriousbsd commented Jun 21, 2019

I'm also having this issue. I believe that adding sshpass as a requirement to the delegate_to server will have to be a requirement, as I've confirmed sshpass is not installed on the server I'm using as the 'destination' in this run:

Please note: The hosts do switch between issue 1 and issue 2.

In issue 1 the variable is set such that:

  hosts: '{{ source }}'

But in issue 2, it's set such that:

  hosts: '{{ destination }}'

Issue (sshpass is installed):

Playbook snippet:

  - name: Synchronize Home directory for users
    #ansible_ssh_user: root
    become: yes
    synchronize:
      mode: push
      src: "~/test_file"
      dest: "/tmp/"
    delegate_to: '{{ destination }}'

Result:

The full traceback is:
WARNING: The below traceback may *not* be related to the actual failure.
  File "/tmp/ansible_synchronize_payload_Wh9iDr/ansible_synchronize_payload.zip/ansible/module_utils/basic.py", line 2563, in run_command
    before_communicate_callback(cmd)
  File "/tmp/ansible_synchronize_payload_Wh9iDr/__main__.py", line 581, in _write_password_to_pipe
    os.write(_sshpass_pipe[1], to_bytes(rsync_password) + b'\n')

fatal: [inderscience_Web]: FAILED! => {
    "changed": false, 
    "cmd": "sshpass -d10 /usr/bin/rsync --delay-updates -F --compress --archive '--rsh=/usr/bin/ssh -S none -i /home/bsierra/.ssh/id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' '--rsync-path=sudo rsync' -avz '--out-format=<<CHANGED>>%i %n%L' '' /tmp/", 
    "invocation": {
        "module_args": {
            "_local_rsync_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "_local_rsync_path": "rsync", 
            "_substitute_controller": false, 
            "archive": true, 
            "checksum": false, 
            "compress": true, 
            "copy_links": false, 
            "delete": false, 
            "dest": "/tmp/", 
            "dest_port": null, 
            "dirs": false, 
            "existing_only": false, 
            "group": null, 
            "link_dest": null, 
            "links": null, 
            "mode": "pull", 
            "owner": null, 
            "partial": false, 
            "perms": null, 
            "private_key": "/home/bsierra/.ssh/id_rsa", 
            "recursive": null, 
            "rsync_opts": [
                "-avz"
            ], 
            "rsync_path": "sudo rsync", 
            "rsync_timeout": 0, 
            "set_remote_user": true, 
            "src": "admin@IP.OF.SERVER:~/test_file", 
            "ssh_args": null, 
            "times": null, 
            "verify_host": false
        }
    }, 
    "msg": "[Errno 32] Broken pipe", 
    "rc": 32
}

Honestly, not sure why this one is failing. When I go in and do a manual run via the user it becomes, it works.


Issue (sshpass is NOT installed)

Playbook Snippet:

 - name: Synchronize Home directory for users
    synchronize:
      mode: push
      src: "test_file"
      dest: "/tmp"
    delegate_to: '{{ source }}'

Result:

The full traceback is:
WARNING: The below traceback may *not* be related to the actual failure.
  File "/tmp/ansible_synchronize_payload_SXLr03/ansible_synchronize_payload.zip/ansible/module_utils/basic.py", line 2561, in run_command
    cmd = subprocess.Popen(args, **kwargs)
  File "/usr/lib64/python2.6/subprocess.py", line 642, in __init__
    errread, errwrite)
  File "/usr/lib64/python2.6/subprocess.py", line 1238, in _execute_child
    raise child_exception

fatal: [Inderscience.Web.New2019 -> None]: FAILED! => {
    "changed": false, 
    "cmd": "sshpass", 
    "invocation": {
        "module_args": {
            "_local_rsync_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "_local_rsync_path": "rsync", 
            "_substitute_controller": false, 
            "archive": true, 
            "checksum": false, 
            "compress": true, 
            "copy_links": false, 
            "delete": false, 
            "dest": "admin@IP.OF.SERVER:/tmp", 
            "dest_port": null, 
            "dirs": false, 
            "existing_only": false, 
            "group": null, 
            "link_dest": null, 
            "links": null, 
            "mode": "push", 
            "owner": null, 
            "partial": false, 
            "perms": null, 
            "private_key": null, 
            "recursive": null, 
            "rsync_opts": [
                "-avz"
            ], 
            "rsync_path": "sudo rsync", 
            "rsync_timeout": 0, 
            "set_remote_user": true, 
            "src": "/home/bsierra/test_file", 
            "ssh_args": null, 
            "times": null, 
            "verify_host": false
        }
    }, 
    "msg": "[Errno 2] No such file or directory", 
    "rc": 2
}

@notoriousbsd
Copy link

From my own testing, this may have something to do with _local_rsync_password as it's passed to the payload.

I noticed that, even using a sudoers entry for rsync, it attempts to still use the password.

Removing the password from the tuple just results in a "msg": "[Errno 2] No such file or directory"} result.

@bitchkat
Copy link
Contributor

As a temporary work around to get deploys working, I grabbed the ansible 2.7 synchronize module, added the _local_rsync_password parameter to it, and dropped it in a library directory where all our playbooks are located. This basically restored the ansible 2.7 behavior which is good enough for now until this issue is resolved.

@notoriousbsd
Copy link

That does appear to work with password-less sudo or using the root user; however using a passworded sudoer user I was still unable to connect.

Were you able to connect utilizing a user that has to pass a password @bitchkat ?

@bitchkat
Copy link
Contributor

bitchkat commented Jun 21, 2019

@notoriousbsd no, this was explicitly re-enabling the ansible 2.7 behavior which didn't support running rsync via sudo with a password. https://docs.ansible.com/ansible/2.7/modules/synchronize_module.html says "Currently, synchronize is limited to elevating permissions via passwordless sudo. This is because rsync itself is connecting to the remote machine and rsync doesn’t give us a way to pass sudo credentials in."

We have an sudoers entry that allows running via sudo without a password:
%ansibleusers ALL=(ALL) NOPASSWD:/usr/bin/rsync

@notoriousbsd
Copy link

Yes, we had to do this as well.

I just made the playbook add the line to the sudoers file, and remove it after it's done, but that's a fine work around for me.

@kakawait
Copy link

kakawait commented Jul 19, 2019

As AWX user (that is running on Python2) other potential workaround is rollbacking

pip install --upgrade ansible==2.7.12

Currently I prefer fixing version on 2.7 rather than other workaround about copying piece of code from 2.7 inside 2.8 looks like much more complicated for me.

@J-F-Liu
Copy link

J-F-Liu commented Jul 26, 2019

The recent change of lib/ansible/modules/files/synchronize.py file is:

-            _local_rsync_password=dict(type='str', default=None, no_log=True),
+            _local_rsync_password=dict(type='str', no_log=True),

I think this is the root cause.

@ansibot ansibot added the has_pr This issue has an associated PR. label Jul 26, 2019
@JSGUYOT
Copy link

JSGUYOT commented Aug 27, 2019

Same issue ... what's news ?

@romaincabassot
Copy link

Any news on this?

@Aech1977
Copy link

Aech1977 commented Oct 1, 2019

X?
I started having this issue after either going thru the painstaking process of upgrading AWX or updating the CentOS image's packages.

@akalongman
Copy link

On my Ubuntu 18.04 I fixed it via adding ansible_python_interpreter = /usr/bin/python3 in my inventory file

@schowave
Copy link
Contributor

Is there any news on that topic? I cannot change python version to python3, because of SLES 12 and the missing support of python 3.5. And with python 2.x it comes to the behaviour as described for the synchronize module:

FAILED! => {"changed": false, "cmd": "sshpass", "msg": "[Errno 2] No such file or directory", "rc": 2}

@hevisko
Copy link

hevisko commented Nov 6, 2019

ansible_python_interpreter = /usr/bin/python3

doesn't seem to work for me ;(
also "had" to upgrade to 2.9 today and hit by this ;(

@Rogach
Copy link

Rogach commented Nov 27, 2019

It seems that this issue can arise when Ansible on the control host is running via Python 2 interpreter. To avoid passing the password to sshpass in the command line it uses pipes and Popen(pass_fds=), which are only available in Python 3.

I'm running Ubuntu 18.04 LTS on the control machine, and it seems that it still runs Ansible using python2 interpreter, so it is unable to pass the password to sshpass and the process fails.

It is relatively simple to patch /usr/lib/python2.7/dist-packages/ansible/modules/files/synchronize.py to pass the password on the command line instead of using pipes. Here's the diff (base file is from Ansible version 2.9.1):

463,464c463,464
<         _sshpass_pipe = os.pipe()
<         cmd = ['sshpass', '-d' + to_native(_sshpass_pipe[0], errors='surrogate_or_strict')] + cmd
---
>         cmd = ['sshpass', '-p' + rsync_password] + cmd
> 
577,592c577
<     # If we are using password authentication, write the password into the pipe
<     if rsync_password:
<         def _write_password_to_pipe(proc):
<             os.close(_sshpass_pipe[0])
<             try:
<                 os.write(_sshpass_pipe[1], to_bytes(rsync_password) + b'\n')
<             except OSError as exc:
<                 # Ignore broken pipe errors if the sshpass process has exited.
<                 if exc.errno != errno.EPIPE or proc.poll() is None:
<                     raise
< 
<         (rc, out, err) = module.run_command(
<             cmd, pass_fds=_sshpass_pipe,
<             before_communicate_callback=_write_password_to_pipe)
<     else:
<         (rc, out, err) = module.run_command(cmd)
---
>     (rc, out, err) = module.run_command(cmd)

Also I used module option rsync_path: rsync to avoid problems with sudo password on remote machine (I did not need elevated privileges there).

P.S. I think it could be nice and quite easy to add option to pass password to sshpass on the command line to accomodate python2 users. I can contribute the PR if needed.

@sugitk
Copy link
Contributor

sugitk commented Dec 2, 2019

@Rogach
Could you please make a PR for devel branch and backport to stable-2.9?
My customer has the same problem on this issue, so they would very appreciate it if your fix is merged.

@Rogach
Copy link

Rogach commented Dec 13, 2019

@sugitk Unfortunately ansible team is opposed to such PR being merged, citing security issues. (see: https://groups.google.com/forum/#!topic/ansible-devel/jk7ixOTb6rQ)

Thus it seems that the only way to proceed is to patch local ansible installation yourself.

@sivel
Copy link
Member

sivel commented Dec 13, 2019

It's possible that what needs to happen is in AnsibleModule.run_command, if pass_fds is provided, and we are on PY2, set close_fds = False. They are obviously not exactly equivalent, but without the ability to prevent specific fds from being closed, we could leave them all open, which is the py2 default, just not the run_command default.

This would bring synchronize in alignment with how the ssh connection plugin works.

It should be as simple as:

diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py
index 4756381ac6..e0a616b5f6 100644
--- a/lib/ansible/module_utils/basic.py
+++ b/lib/ansible/module_utils/basic.py
@@ -2454,7 +2454,8 @@ class AnsibleModule(object):
             ``use_unsafe_shell=False`` no path or variable expansion will be done.
         :kw pass_fds: When running on python3 this argument
             dictates which file descriptors should be passed
-            to an underlying ``Popen`` constructor.
+            to an underlying ``Popen`` constructor. On python2,
+            this toggles `close_fds` to False
         :kw before_communicate_callback: This function will be called
             after ``Popen`` object will be created
             but before communicating to the process.
@@ -2565,6 +2566,8 @@ class AnsibleModule(object):
         )
         if PY3 and pass_fds:
             kwargs["pass_fds"] = pass_fds
+        elif PY2 and pass_fds:
+            kwargs["close_fds"] = False
 
         # store the pwd
         prev_dir = os.getcwd()
diff --git a/test/units/module_utils/basic/test_run_command.py b/test/units/module_utils/basic/test_run_command.py
index e41a4960b4..2fd721e5de 100644
--- a/test/units/module_utils/basic/test_run_command.py
+++ b/test/units/module_utils/basic/test_run_command.py
@@ -13,6 +13,7 @@ from io import BytesIO
 import pytest
 
 from ansible.module_utils._text import to_native
+from ansible.module_utils.six import PY2
 
 
 class OpenBytesIO(BytesIO):
@@ -197,3 +198,21 @@ class TestRunCommandOutput:
         # bytes because it's returning native strings
         assert stdout == to_native(u'Žarn§')
         assert stderr == to_native(u'لرئيسية')
+
+
+@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+def test_run_command_fds(mocker, rc_am):
+    subprocess = mocker.patch('ansible.module_utils.basic.subprocess')
+    subprocess.Popen.side_effect = AssertionError  # short circuit execution, we don't what happens after
+
+    try:
+        rc_am.run_command('/bin/true', pass_fds=(1,))
+    except SystemExit:
+        pass
+
+    if PY2:
+        assert subprocess.Popen.call_args[1]['close_fds'] == False
+        assert 'pass_fds' not in subprocess.Popen.call_args[1]
+    else:
+        assert subprocess.Popen.call_args[1]['close_fds'] == True
+        assert subprocess.Popen.call_args[1]['pass_fds'] == (1,)

We may also want to extend the docstring for run_command to indicate that pass_fds on Py2 results in close_fds=False.

I believe doing this in run_command makes the most sense, as it removes the complexity from the caller of doing py2 vs py3 checking in the module.

@sivel sivel added the easyfix This issue is considered easy to fix by aspiring contributors. label Jan 16, 2020
@samdoran
Copy link
Contributor

I'll get a PR together to fix this. Thanks for doing the hard work, @sivel!

@sohnaeo
Copy link

sohnaeo commented Jan 20, 2020

Hi,
I had exactly the same issue while installing kubespray. My ssh user don't have ssh trust setup and am using ansible_remote_user which has sudo access to do admin tasks on the system.

kubepray uses synchronize module to copy the files but ansible_ssh_pass doesn't pass to rsync and had to install sshpass on my clients to fix this but despite of that was having "broken pipe" issue but I applied that patched and it fix the issue.

May I know when is this PR going to be merged? Secondly, is there anyway ansible_ssh_pass can be used with rsync without installing sshpass?

Thanks in advance

@viniciusbona
Copy link

hi,

i have the same problem with ansible version 2.7.8 and i fix by update to ansible 2.9.4 and run with python3.

$ ansible --version
ansible 2.7.8
config file = /etc/ansible/ansible.cfg
configured module search path = ['/home/viniciusbona/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
executable location = /usr/bin/ansible
python version = 3.7.3 (default, Oct 7 2019, 12:56:13) [GCC 8.3.0]

I update the ansible to version 2.9.4 and run playbook with pytnhon 3 interpreter option "-e ansible_python_interpreter=python3"

$ sudo apt update
$ sudo apt install software-properties-common
$ sudo apt-add-repository --yes --update ppa:ansible/ansible
$ sudo apt install ansible

$ansible --version

ansible 2.9.4
config file = /etc/ansible/ansible.cfg
configured module search path = [u'/home/viniciusbona/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python2.7/dist-packages/ansible
executable location = /usr/bin/ansible
python version = 2.7.16 (default, Oct 7 2019, 17:36:04) [GCC 8.3.0]

ansible-playbook -i inventory -b ZabbixAgent/main.yml -e ansible_python_interpreter=python3

after doing this step the problem was solved to me.

@ansible ansible locked and limited conversation to collaborators Feb 19, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
affects_2.8 This issue/PR affects Ansible v2.8 bug This issue/PR relates to a bug. easyfix This issue is considered easy to fix by aspiring contributors. files Files category has_pr This issue has an associated PR. module This issue/PR relates to a module. P3 Priority 3 - Approved, No Time Limitation python3 support:core This issue/PR relates to code supported by the Ansible Engineering Team. traceback This issue/PR includes a traceback.
Projects
None yet
Development

Successfully merging a pull request may close this issue.