ssh: Ensure debug messages are properly converted to text (#57850)

* ssh: Ensure debug messages are properly converted to text. Fixes #57843

* surrogate_then_replace is default, be less explicit

* We only needed to_text for display, not exceptions
pull/58125/head
Matt Martz 5 years ago committed by GitHub
parent 3a3465c47d
commit 375ac76e58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,2 @@
bugfixes:
- ssh connection plugin - Ensure that debug messages are properly encoded as text

@ -327,7 +327,7 @@ def _handle_error(remaining_retries, command, return_tuple, no_log, host, displa
if no_log: if no_log:
msg = '{0} <error censored due to no log>'.format(msg) msg = '{0} <error censored due to no log>'.format(msg)
else: else:
msg = '{0} {1}'.format(msg, to_native(return_tuple[2].rstrip())) msg = '{0} {1}'.format(msg, to_native(return_tuple[2]).rstrip())
raise AnsibleAuthenticationFailure(msg) raise AnsibleAuthenticationFailure(msg)
# sshpass returns codes are 1-6. We handle 5 previously, so this catches other scenarios. # sshpass returns codes are 1-6. We handle 5 previously, so this catches other scenarios.
@ -337,7 +337,7 @@ def _handle_error(remaining_retries, command, return_tuple, no_log, host, displa
if no_log: if no_log:
msg = '{0} <error censored due to no log>'.format(msg) msg = '{0} <error censored due to no log>'.format(msg)
else: else:
msg = '{0} {1}'.format(msg, to_native(return_tuple[2].rstrip())) msg = '{0} {1}'.format(msg, to_native(return_tuple[2]).rstrip())
if return_tuple[0] == 255: if return_tuple[0] == 255:
SSH_ERROR = True SSH_ERROR = True
@ -356,11 +356,11 @@ def _handle_error(remaining_retries, command, return_tuple, no_log, host, displa
# For other errors, no execption is raised so the connection is retried and we only log the messages # For other errors, no execption is raised so the connection is retried and we only log the messages
if 1 <= return_tuple[0] <= 254: if 1 <= return_tuple[0] <= 254:
msg = "Failed to connect to the host via ssh:" msg = u"Failed to connect to the host via ssh:"
if no_log: if no_log:
msg = '{0} <error censored due to no log>'.format(msg) msg = u'{0} <error censored due to no log>'.format(msg)
else: else:
msg = '{0} {1}'.format(msg, to_native(return_tuple[2]).rstrip()) msg = u'{0} {1}'.format(msg, to_text(return_tuple[2]).rstrip())
display.vvv(msg, host=host) display.vvv(msg, host=host)
@ -379,7 +379,7 @@ def _ssh_retry(func):
@wraps(func) @wraps(func)
def wrapped(self, *args, **kwargs): def wrapped(self, *args, **kwargs):
remaining_tries = int(C.ANSIBLE_SSH_RETRIES) + 1 remaining_tries = int(C.ANSIBLE_SSH_RETRIES) + 1
cmd_summary = "%s..." % args[0] cmd_summary = u"%s..." % to_text(args[0])
for attempt in range(remaining_tries): for attempt in range(remaining_tries):
cmd = args[0] cmd = args[0]
if attempt != 0 and self._play_context.password and isinstance(cmd, list): if attempt != 0 and self._play_context.password and isinstance(cmd, list):
@ -391,7 +391,7 @@ def _ssh_retry(func):
try: try:
return_tuple = func(self, *args, **kwargs) return_tuple = func(self, *args, **kwargs)
if self._play_context.no_log: if self._play_context.no_log:
display.vvv('rc=%s, stdout and stderr censored due to no log' % return_tuple[0], host=self.host) display.vvv(u'rc=%s, stdout and stderr censored due to no log' % return_tuple[0], host=self.host)
else: else:
display.vvv(return_tuple, host=self.host) display.vvv(return_tuple, host=self.host)
# 0 = success # 0 = success
@ -427,9 +427,10 @@ def _ssh_retry(func):
pause = 30 pause = 30
if isinstance(e, AnsibleConnectionFailure): if isinstance(e, AnsibleConnectionFailure):
msg = "ssh_retry: attempt: %d, ssh return code is 255. cmd (%s), pausing for %d seconds" % (attempt + 1, cmd_summary, pause) msg = u"ssh_retry: attempt: %d, ssh return code is 255. cmd (%s), pausing for %d seconds" % (attempt + 1, cmd_summary, pause)
else: else:
msg = "ssh_retry: attempt: %d, caught exception(%s) from cmd (%s), pausing for %d seconds" % (attempt + 1, e, cmd_summary, pause) msg = (u"ssh_retry: attempt: %d, caught exception(%s) from cmd (%s), "
u"pausing for %d seconds" % (attempt + 1, to_text(e), cmd_summary, pause))
display.vv(msg, host=self.host) display.vv(msg, host=self.host)
@ -681,7 +682,7 @@ class Connection(ConnectionBase):
just hang forever waiting for more commands.) just hang forever waiting for more commands.)
''' '''
display.debug('Sending initial data') display.debug(u'Sending initial data')
try: try:
fh.write(to_bytes(in_data)) fh.write(to_bytes(in_data))
@ -697,7 +698,7 @@ class Connection(ConnectionBase):
'over ssh: %s' % (self.host, to_native(e)), orig_exc=e 'over ssh: %s' % (self.host, to_native(e)), orig_exc=e
) )
display.debug('Sent initial data (%d bytes)' % len(in_data)) display.debug(u'Sent initial data (%d bytes)' % len(in_data))
# Used by _run() to kill processes on failures # Used by _run() to kill processes on failures
@staticmethod @staticmethod
@ -727,18 +728,18 @@ class Connection(ConnectionBase):
# display.debug("Examining line (source=%s, state=%s): '%s'" % (source, state, display_line)) # display.debug("Examining line (source=%s, state=%s): '%s'" % (source, state, display_line))
if self.become.expect_prompt() and self.become.check_password_prompt(b_line): if self.become.expect_prompt() and self.become.check_password_prompt(b_line):
display.debug("become_prompt: (source=%s, state=%s): '%s'" % (source, state, display_line)) display.debug(u"become_prompt: (source=%s, state=%s): '%s'" % (source, state, display_line))
self._flags['become_prompt'] = True self._flags['become_prompt'] = True
suppress_output = True suppress_output = True
elif self.become.success and self.become.check_success(b_line): elif self.become.success and self.become.check_success(b_line):
display.debug("become_success: (source=%s, state=%s): '%s'" % (source, state, display_line)) display.debug(u"become_success: (source=%s, state=%s): '%s'" % (source, state, display_line))
self._flags['become_success'] = True self._flags['become_success'] = True
suppress_output = True suppress_output = True
elif sudoable and self.become.check_incorrect_password(b_line): elif sudoable and self.become.check_incorrect_password(b_line):
display.debug("become_error: (source=%s, state=%s): '%s'" % (source, state, display_line)) display.debug(u"become_error: (source=%s, state=%s): '%s'" % (source, state, display_line))
self._flags['become_error'] = True self._flags['become_error'] = True
elif sudoable and self.become.check_missing_password(b_line): elif sudoable and self.become.check_missing_password(b_line):
display.debug("become_nopasswd_error: (source=%s, state=%s): '%s'" % (source, state, display_line)) display.debug(u"become_nopasswd_error: (source=%s, state=%s): '%s'" % (source, state, display_line))
self._flags['become_nopasswd_error'] = True self._flags['become_nopasswd_error'] = True
if not suppress_output: if not suppress_output:
@ -762,8 +763,8 @@ class Connection(ConnectionBase):
''' '''
# We don't use _shell.quote as this is run on the controller and independent from the shell plugin chosen # We don't use _shell.quote as this is run on the controller and independent from the shell plugin chosen
display_cmd = list(map(shlex_quote, map(to_text, cmd))) display_cmd = u' '.join(shlex_quote(to_text(c)) for c in cmd)
display.vvv(u'SSH: EXEC {0}'.format(u' '.join(display_cmd)), host=self.host) display.vvv(u'SSH: EXEC {0}'.format(display_cmd), host=self.host)
# Start the given command. If we don't need to pipeline data, we can try # Start the given command. If we don't need to pipeline data, we can try
# to use a pseudo-tty (ssh will have been invoked with -tt). If we are # to use a pseudo-tty (ssh will have been invoked with -tt). If we are
@ -835,12 +836,12 @@ class Connection(ConnectionBase):
# We're requesting escalation with a password, so we have to # We're requesting escalation with a password, so we have to
# wait for a password prompt. # wait for a password prompt.
state = states.index('awaiting_prompt') state = states.index('awaiting_prompt')
display.debug(u'Initial state: %s: %s' % (states[state], prompt)) display.debug(u'Initial state: %s: %s' % (states[state], to_text(prompt)))
elif self._play_context.become and self._play_context.success_key: elif self._play_context.become and self.become.success:
# We're requesting escalation without a password, so we have to # We're requesting escalation without a password, so we have to
# detect success/failure before sending any initial data. # detect success/failure before sending any initial data.
state = states.index('awaiting_escalation') state = states.index('awaiting_escalation')
display.debug(u'Initial state: %s: %s' % (states[state], self._play_context.success_key)) display.debug(u'Initial state: %s: %s' % (states[state], to_text(self.become.success)))
# We store accumulated stdout and stderr output from the process here, # We store accumulated stdout and stderr output from the process here,
# but strip any privilege escalation prompt/confirmation lines first. # but strip any privilege escalation prompt/confirmation lines first.
@ -911,7 +912,7 @@ class Connection(ConnectionBase):
# not going to arrive until the persisted connection closes. # not going to arrive until the persisted connection closes.
timeout = 1 timeout = 1
b_tmp_stdout += b_chunk b_tmp_stdout += b_chunk
display.debug("stdout chunk (state=%s):\n>>>%s<<<\n" % (state, to_text(b_chunk))) display.debug(u"stdout chunk (state=%s):\n>>>%s<<<\n" % (state, to_text(b_chunk)))
elif key.fileobj == p.stderr: elif key.fileobj == p.stderr:
b_chunk = p.stderr.read() b_chunk = p.stderr.read()
if b_chunk == b'': if b_chunk == b'':
@ -945,7 +946,7 @@ class Connection(ConnectionBase):
if states[state] == 'awaiting_prompt': if states[state] == 'awaiting_prompt':
if self._flags['become_prompt']: if self._flags['become_prompt']:
display.debug('Sending become_password in response to prompt') display.debug(u'Sending become_password in response to prompt')
stdin.write(to_bytes(self._play_context.become_pass) + b'\n') stdin.write(to_bytes(self._play_context.become_pass) + b'\n')
# On python3 stdin is a BufferedWriter, and we don't have a guarantee # On python3 stdin is a BufferedWriter, and we don't have a guarantee
# that the write will happen without a flush # that the write will happen without a flush
@ -960,23 +961,23 @@ class Connection(ConnectionBase):
if states[state] == 'awaiting_escalation': if states[state] == 'awaiting_escalation':
if self._flags['become_success']: if self._flags['become_success']:
display.vvv('Escalation succeeded') display.vvv(u'Escalation succeeded')
self._flags['become_success'] = False self._flags['become_success'] = False
state += 1 state += 1
elif self._flags['become_error']: elif self._flags['become_error']:
display.vvv('Escalation failed') display.vvv(u'Escalation failed')
self._terminate_process(p) self._terminate_process(p)
self._flags['become_error'] = False self._flags['become_error'] = False
raise AnsibleError('Incorrect %s password' % self._play_context.become_method) raise AnsibleError('Incorrect %s password' % self._play_context.become_method)
elif self._flags['become_nopasswd_error']: elif self._flags['become_nopasswd_error']:
display.vvv('Escalation requires password') display.vvv(u'Escalation requires password')
self._terminate_process(p) self._terminate_process(p)
self._flags['become_nopasswd_error'] = False self._flags['become_nopasswd_error'] = False
raise AnsibleError('Missing %s password' % self._play_context.become_method) raise AnsibleError('Missing %s password' % self._play_context.become_method)
elif self._flags['become_prompt']: elif self._flags['become_prompt']:
# This shouldn't happen, because we should see the "Sorry, # This shouldn't happen, because we should see the "Sorry,
# try again" message first. # try again" message first.
display.vvv('Escalation prompt repeated') display.vvv(u'Escalation prompt repeated')
self._terminate_process(p) self._terminate_process(p)
self._flags['become_prompt'] = False self._flags['become_prompt'] = False
raise AnsibleError('Incorrect %s password' % self._play_context.become_method) raise AnsibleError('Incorrect %s password' % self._play_context.become_method)
@ -1126,9 +1127,9 @@ class Connection(ConnectionBase):
else: else:
# If not in smart mode, the data will be printed by the raise below # If not in smart mode, the data will be printed by the raise below
if len(methods) > 1: if len(methods) > 1:
display.warning('%s transfer mechanism failed on %s. Use ANSIBLE_DEBUG=1 to see detailed information' % (method, host)) display.warning(u'%s transfer mechanism failed on %s. Use ANSIBLE_DEBUG=1 to see detailed information' % (method, host))
display.debug('%s' % to_text(stdout)) display.debug(u'%s' % to_text(stdout))
display.debug('%s' % to_text(stderr)) display.debug(u'%s' % to_text(stderr))
if returncode == 255: if returncode == 255:
raise AnsibleConnectionFailure("Failed to connect to the host via %s: %s" % (method, to_native(stderr))) raise AnsibleConnectionFailure("Failed to connect to the host via %s: %s" % (method, to_native(stderr)))
@ -1237,12 +1238,12 @@ class Connection(ConnectionBase):
run_reset = True run_reset = True
if run_reset: if run_reset:
display.vvv(u'sending stop: %s' % cmd) display.vvv(u'sending stop: %s' % to_text(cmd))
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate() stdout, stderr = p.communicate()
status_code = p.wait() status_code = p.wait()
if status_code != 0: if status_code != 0:
display.warning("Failed to reset connection:%s" % stderr) display.warning(u"Failed to reset connection:%s" % to_text(stderr))
self.close() self.close()

Loading…
Cancel
Save