diff options
author | Rick Elrod <rick@elrod.me> | 2020-05-26 19:58:31 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-26 19:58:31 -0500 |
commit | 304c3e57e88c3bfb9a8f912b85e1184b836e0eb2 (patch) | |
tree | 248e0bd5537e9da7baac491abe0a3f1d2413056a | |
parent | ea04e0048dbb3b63f876aad7020e1de8eee9f362 (diff) | |
download | ansible-304c3e57e88c3bfb9a8f912b85e1184b836e0eb2.tar.gz |
[ssh] Add new sshpass_prompt option (#68874)
Change:
Allows the user to configure sshpass (1.06+) to look for a different
substring than the default "assword" that it comes with.
Test Plan:
Set a custom ssh password prompt on a VM with PAM and tried connecting to
it. Without `ansible_sshpass_prompt` set in inventory: experienced hang.
With `ansible_sshpass_prompt` in inventory: connected successfully.
Tried setting `ansible_sshpass_prompt` with an older `sshpass` in PATH
and got a loud error, as expected.
Tickets:
Fixes #34722, fixes #54743, refs #11565.
Signed-off-by: Rick Elrod <rick@elrod.me>
-rw-r--r-- | changelogs/fragments/34722-ssh-sshpass-prompt-variable.yml | 2 | ||||
-rw-r--r-- | lib/ansible/plugins/connection/ssh.py | 25 | ||||
-rwxr-xr-x | test/integration/targets/connection_ssh/runme.sh | 44 |
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 |