Mask passwords in exceptions and error messages
When a ProcessExecutionError is thrown by processutils.ssh_execute(), the exception may contain information such as password. Upstream applications that just log the message (as several appear to do) could inadvertently expose these passwords to a user with read access to the log files. It is therefore considered prudent to invoke strutils.mask_password() on the command, stdout and stderr in the exception. A test case has been added to ensure that all three are properly masked. An earlier commit (e8199714) failed to address ssh_execute(). This change set addresses ssh_execute. OSSA is aware of this change request. Change-Id: I0db9e98cbeb2a5e6f9ae0074f24717aa91cfc238 Closes-Bug: #1343604
This commit is contained in:
parent
df6614936a
commit
ae9e05bfc3
@ -242,7 +242,8 @@ def trycmd(*args, **kwargs):
|
|||||||
|
|
||||||
def ssh_execute(ssh, cmd, process_input=None,
|
def ssh_execute(ssh, cmd, process_input=None,
|
||||||
addl_env=None, check_exit_code=True):
|
addl_env=None, check_exit_code=True):
|
||||||
LOG.debug('Running cmd (SSH): %s', cmd)
|
sanitized_cmd = strutils.mask_password(cmd)
|
||||||
|
LOG.debug('Running cmd (SSH): %s', sanitized_cmd)
|
||||||
if addl_env:
|
if addl_env:
|
||||||
raise InvalidArgumentError(_('Environment not supported over SSH'))
|
raise InvalidArgumentError(_('Environment not supported over SSH'))
|
||||||
|
|
||||||
@ -256,7 +257,10 @@ def ssh_execute(ssh, cmd, process_input=None,
|
|||||||
# NOTE(justinsb): This seems suspicious...
|
# NOTE(justinsb): This seems suspicious...
|
||||||
# ...other SSH clients have buffering issues with this approach
|
# ...other SSH clients have buffering issues with this approach
|
||||||
stdout = stdout_stream.read()
|
stdout = stdout_stream.read()
|
||||||
|
sanitized_stdout = strutils.mask_password(stdout)
|
||||||
stderr = stderr_stream.read()
|
stderr = stderr_stream.read()
|
||||||
|
sanitized_stderr = strutils.mask_password(stderr)
|
||||||
|
|
||||||
stdin_stream.close()
|
stdin_stream.close()
|
||||||
|
|
||||||
exit_status = channel.recv_exit_status()
|
exit_status = channel.recv_exit_status()
|
||||||
@ -266,11 +270,11 @@ def ssh_execute(ssh, cmd, process_input=None,
|
|||||||
LOG.debug('Result was %s' % exit_status)
|
LOG.debug('Result was %s' % exit_status)
|
||||||
if check_exit_code and exit_status != 0:
|
if check_exit_code and exit_status != 0:
|
||||||
raise ProcessExecutionError(exit_code=exit_status,
|
raise ProcessExecutionError(exit_code=exit_status,
|
||||||
stdout=stdout,
|
stdout=sanitized_stdout,
|
||||||
stderr=stderr,
|
stderr=sanitized_stderr,
|
||||||
cmd=cmd)
|
cmd=sanitized_cmd)
|
||||||
|
|
||||||
return (stdout, stderr)
|
return (sanitized_stdout, sanitized_stderr)
|
||||||
|
|
||||||
|
|
||||||
def get_worker_count():
|
def get_worker_count():
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
|
import logging
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -332,3 +333,57 @@ class SshExecuteTestCase(test_base.BaseTestCase):
|
|||||||
def test_fails(self):
|
def test_fails(self):
|
||||||
self.assertRaises(processutils.ProcessExecutionError,
|
self.assertRaises(processutils.ProcessExecutionError,
|
||||||
processutils.ssh_execute, FakeSshConnection(1), 'ls')
|
processutils.ssh_execute, FakeSshConnection(1), 'ls')
|
||||||
|
|
||||||
|
def _test_compromising_ssh(self, rc, check):
|
||||||
|
fixture = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG))
|
||||||
|
fake_stdin = six.StringIO()
|
||||||
|
|
||||||
|
fake_stdout = mock.Mock()
|
||||||
|
fake_stdout.channel.recv_exit_status.return_value = rc
|
||||||
|
fake_stdout.read.return_value = 'password="secret"'
|
||||||
|
|
||||||
|
fake_stderr = six.StringIO('password="foobar"')
|
||||||
|
|
||||||
|
command = 'ls --password="bar"'
|
||||||
|
|
||||||
|
connection = mock.Mock()
|
||||||
|
connection.exec_command.return_value = (fake_stdin, fake_stdout,
|
||||||
|
fake_stderr)
|
||||||
|
|
||||||
|
if check and rc != -1 and rc != 0:
|
||||||
|
err = self.assertRaises(processutils.ProcessExecutionError,
|
||||||
|
processutils.ssh_execute,
|
||||||
|
connection, command,
|
||||||
|
check_exit_code=check)
|
||||||
|
|
||||||
|
self.assertEqual(rc, err.exit_code)
|
||||||
|
self.assertEqual(err.stdout, 'password="***"')
|
||||||
|
self.assertEqual(err.stderr, 'password="***"')
|
||||||
|
self.assertEqual(err.cmd, 'ls --password="***"')
|
||||||
|
self.assertNotIn('secret', str(err))
|
||||||
|
self.assertNotIn('foobar', str(err))
|
||||||
|
else:
|
||||||
|
o, e = processutils.ssh_execute(connection, command,
|
||||||
|
check_exit_code=check)
|
||||||
|
self.assertEqual('password="***"', o)
|
||||||
|
self.assertEqual('password="***"', e)
|
||||||
|
self.assertIn('password="***"', fixture.output)
|
||||||
|
self.assertNotIn('bar', fixture.output)
|
||||||
|
|
||||||
|
def test_compromising_ssh1(self):
|
||||||
|
self._test_compromising_ssh(rc=-1, check=True)
|
||||||
|
|
||||||
|
def test_compromising_ssh2(self):
|
||||||
|
self._test_compromising_ssh(rc=0, check=True)
|
||||||
|
|
||||||
|
def test_compromising_ssh3(self):
|
||||||
|
self._test_compromising_ssh(rc=1, check=True)
|
||||||
|
|
||||||
|
def test_compromising_ssh4(self):
|
||||||
|
self._test_compromising_ssh(rc=1, check=False)
|
||||||
|
|
||||||
|
def test_compromising_ssh5(self):
|
||||||
|
self._test_compromising_ssh(rc=0, check=False)
|
||||||
|
|
||||||
|
def test_compromising_ssh6(self):
|
||||||
|
self._test_compromising_ssh(rc=-1, check=False)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user