summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--changelogs/fragments/34722-ssh-sshpass-prompt-variable.yml2
-rw-r--r--lib/ansible/plugins/connection/ssh.py25
-rwxr-xr-xtest/integration/targets/connection_ssh/runme.sh44
3 files changed, 68 insertions, 3 deletions
diff --git a/changelogs/fragments/34722-ssh-sshpass-prompt-variable.yml b/changelogs/fragments/34722-ssh-sshpass-prompt-variable.yml
new file mode 100644
index 0000000000..b9f386bea6
--- /dev/null
+++ b/changelogs/fragments/34722-ssh-sshpass-prompt-variable.yml
@@ -0,0 +1,2 @@
+minor_changes:
+- ssh - connection plugin now supports a new variable ``sshpass_prompt`` which gets passed to ``sshpass`` allowing the user to set a custom substring to search for a password prompt (requires sshpass 1.06+)
diff --git a/lib/ansible/plugins/connection/ssh.py b/lib/ansible/plugins/connection/ssh.py
index 479e1919ce..5a51ee3ba8 100644
--- a/lib/ansible/plugins/connection/ssh.py
+++ b/lib/ansible/plugins/connection/ssh.py
@@ -48,6 +48,17 @@ DOCUMENTATION = '''
- name: ansible_password
- name: ansible_ssh_pass
- name: ansible_ssh_password
+ sshpass_prompt:
+ description: Password prompt that sshpass should search for. Supported by sshpass 1.06 and up.
+ default: ''
+ ini:
+ - section: 'ssh_connection'
+ key: 'sshpass_prompt'
+ env:
+ - name: ANSIBLE_SSHPASS_PROMPT
+ vars:
+ - name: ansible_sshpass_prompt
+ version_added: '2.10'
ssh_args:
description: Arguments to pass to all ssh cli tools
default: '-C -o ControlMaster=auto -o ControlPersist=60s'
@@ -331,13 +342,19 @@ def _handle_error(remaining_retries, command, return_tuple, no_log, host, displa
raise AnsibleAuthenticationFailure(msg)
# sshpass returns codes are 1-6. We handle 5 previously, so this catches other scenarios.
- # No exception is raised, so the connection is retried.
+ # No exception is raised, so the connection is retried - except when attempting to use
+ # sshpass_prompt with an sshpass that won't let us pass -P, in which case we fail loudly.
elif return_tuple[0] in [1, 2, 3, 4, 6]:
msg = 'sshpass error:'
if no_log:
msg = '{0} <error censored due to no log>'.format(msg)
else:
- msg = '{0} {1}'.format(msg, to_native(return_tuple[2]).rstrip())
+ details = to_native(return_tuple[2]).rstrip()
+ if "sshpass: invalid option -- 'P'" in details:
+ details = 'Installed sshpass version does not support customized password prompts. ' \
+ 'Upgrade sshpass to use sshpass_prompt, or otherwise switch to ssh keys.'
+ raise AnsibleError('{0} {1}'.format(msg, details))
+ msg = '{0} {1}'.format(msg, details)
if return_tuple[0] == 255:
SSH_ERROR = True
@@ -562,6 +579,10 @@ class Connection(ConnectionBase):
self.sshpass_pipe = os.pipe()
b_command += [b'sshpass', b'-d' + to_bytes(self.sshpass_pipe[0], nonstring='simplerepr', errors='surrogate_or_strict')]
+ password_prompt = self.get_option('sshpass_prompt')
+ if password_prompt:
+ b_command += [b'-P', to_bytes(password_prompt, errors='surrogate_or_strict')]
+
if binary == 'ssh':
b_command += [to_bytes(self._play_context.ssh_executable, errors='surrogate_or_strict')]
else:
diff --git a/test/integration/targets/connection_ssh/runme.sh b/test/integration/targets/connection_ssh/runme.sh
index a24ff048c5..e7b2b21f0b 100755
--- a/test/integration/targets/connection_ssh/runme.sh
+++ b/test/integration/targets/connection_ssh/runme.sh
@@ -1,6 +1,48 @@
#!/usr/bin/env bash
-set -eux
+set -ux
+
+# We skip this whole section if the test node doesn't have sshpass on it.
+if command -v sshpass > /dev/null; then
+ # Check if our sshpass supports -P
+ sshpass -P foo > /dev/null
+ sshpass_supports_prompt=$?
+ if [[ $sshpass_supports_prompt -eq 0 ]]; then
+ # If the prompt is wrong, we'll end up hanging (due to sshpass hanging).
+ # We should probably do something better here, like timing out in Ansible,
+ # but this has been the behavior for a long time, before we supported custom
+ # password prompts.
+ #
+ # So we search for a custom password prompt that is clearly wrong and call
+ # ansible with timeout. If we time out, our custom prompt was successfully
+ # searched for. It's a weird way of doing things, but it does ensure
+ # that the flag gets passed to sshpass.
+ timeout 5 ansible -m ping \
+ -e ansible_connection=ssh \
+ -e ansible_sshpass_prompt=notThis: \
+ -e ansible_password=foo \
+ -e ansible_user=definitelynotroot \
+ -i test_connection.inventory \
+ ssh-pipelining
+ ret=$?
+ if [[ $ret -ne 124 ]]; then
+ echo "Expected to time out and we did not. Exiting with failure."
+ exit 1
+ fi
+ else
+ ansible -m ping \
+ -e ansible_connection=ssh \
+ -e ansible_sshpass_prompt=notThis: \
+ -e ansible_password=foo \
+ -e ansible_user=definitelynotroot \
+ -i test_connection.inventory \
+ ssh-pipelining | grep 'customized password prompts'
+ ret=$?
+ [[ $ret -eq 0 ]] || exit $ret
+ fi
+fi
+
+set -e
# temporary work-around for issues due to new scp filename checking
# https://github.com/ansible/ansible/issues/52640