Do not hang on salt-ssh with sudo and sanitize log output

This commit is contained in:
Megan Wilhite 2022-09-08 10:15:36 -06:00 committed by Gareth J. Greenaway
parent ea35cb527e
commit a09b4f4450
5 changed files with 79 additions and 2 deletions

1
changelog/62603.fixed Normal file
View file

@ -0,0 +1 @@
Fix a hang on salt-ssh when using sudo.

View file

@ -23,8 +23,11 @@ SSH_PRIVATE_KEY_PASSWORD_PROMPT_RE = re.compile(r"Enter passphrase for key", re.
# sudo prompt is used to recognize sudo prompting for a password and should
# therefore be fairly recognizable and unique
SUDO_PROMPT = r"[salt:sudo:d11bd4221135c33324a6bdc09674146fbfdf519989847491e34a689369bbce23]passwd:"
SUDO_PROMPT_RE = re.compile(SUDO_PROMPT, re.M)
SUDO_PROMPT = "[salt:sudo:d11bd4221135c33324a6bdc09674146fbfdf519989847491e34a689369bbce23]passwd:"
SUDO_PROMPT_RE = re.compile(
r"\[salt:sudo:d11bd4221135c33324a6bdc09674146fbfdf519989847491e34a689369bbce23\]passwd:",
re.M,
)
# Keep these in sync with ./__init__.py
RSTR = "_edbc7885e4f9aac9b83b35999b68d015148caf467b78fa39c05f669c0ff89878"
@ -379,12 +382,16 @@ class Shell:
if not cmd:
return "", "No command or passphrase", 245
log_sanitize = None
if self.passwd:
log_sanitize = self.passwd
term = salt.utils.vt.Terminal(
self._split_cmd(cmd),
log_stdout=True,
log_stdout_level="trace",
log_stderr=True,
log_stderr_level="trace",
log_sanitize=log_sanitize,
stream_stdout=False,
stream_stderr=False,
)
@ -398,11 +405,15 @@ class Shell:
while term.has_unread_data:
stdout, stderr = term.recv()
if stdout:
if self.passwd:
stdout = stdout.replace(self.passwd, ("*" * 6))
ret_stdout += stdout
buff = old_stdout + stdout
else:
buff = stdout
if stderr:
if self.passwd:
stderr = stderr.replace(self.passwd, ("*" * 6))
ret_stderr += stderr
if buff and RSTR_RE.search(buff):
# We're getting results back, don't try to send passwords

View file

@ -117,6 +117,7 @@ class Terminal:
log_stdout_level="debug",
log_stderr=None,
log_stderr_level="debug",
log_sanitize=None,
# sys.stdXYZ streaming options
stream_stdout=None,
stream_stderr=None,
@ -221,7 +222,16 @@ class Terminal:
self.child_fd,
self.child_fde,
)
if log_sanitize:
if not isinstance(log_sanitize, str):
raise RuntimeError("'log_sanitize' needs to be a str type")
self.log_sanitize = log_sanitize
else:
self.log_sanitize = None
terminal_command = " ".join(self.args)
if self.log_sanitize:
terminal_command = terminal_command.replace(self.log_sanitize, ("*" * 6))
if (
'decode("base64")' in terminal_command
or "base64.b64decode(" in terminal_command
@ -579,6 +589,10 @@ class Terminal:
if self.stderr_logger:
stripped = stderr.rstrip()
if self.log_sanitize:
stripped = stripped.replace(
self.log_sanitize, ("*" * 6)
)
if stripped.startswith(os.linesep):
stripped = stripped[len(os.linesep) :]
if stripped:
@ -612,6 +626,10 @@ class Terminal:
if self.stdout_logger:
stripped = stdout.rstrip()
if self.log_sanitize:
stripped = stripped.replace(
self.log_sanitize, ("*" * 6)
)
if stripped.startswith(os.linesep):
stripped = stripped[len(os.linesep) :]
if stripped:

View file

@ -3,6 +3,7 @@ import types
import pytest
import salt.client.ssh.shell as shell
from tests.support.mock import patch
@pytest.fixture
@ -27,3 +28,26 @@ def test_ssh_shell_key_gen(keys):
timeout=30,
)
assert ret.decode().startswith("ssh-rsa")
@pytest.mark.skip_on_windows(reason="Windows does not support salt-ssh")
@pytest.mark.skip_if_binaries_missing("ssh", "ssh-keygen", check_all=True)
def test_ssh_shell_exec_cmd(caplog):
"""
Test executing a command and ensuring the password
is not in the stdout/stderr logs.
"""
passwd = "12345"
opts = {"_ssh_version": (4, 9)}
host = ""
_shell = shell.Shell(opts=opts, host=host)
_shell.passwd = passwd
with patch.object(_shell, "_split_cmd", return_value=["echo", passwd]):
ret = _shell.exec_cmd("echo {}".format(passwd))
assert not any([x for x in ret if passwd in str(x)])
assert passwd not in caplog.text
with patch.object(_shell, "_split_cmd", return_value=["ls", passwd]):
ret = _shell.exec_cmd("ls {}".format(passwd))
assert not any([x for x in ret if passwd in str(x)])
assert passwd not in caplog.text

View file

@ -26,3 +26,26 @@ def test_isalive_no_child():
aliveness = term.isalive()
assert term.exitstatus == 0
assert aliveness is False
@pytest.mark.parametrize("test_cmd", ["echo", "ls"])
@pytest.mark.skip_on_windows()
def test_log_sanitize(test_cmd, caplog):
"""
test when log_sanitize is passed in
we do not see the password in either
standard out or standard error logs
"""
password = "123456"
cmd = [test_cmd, password]
term = vt.Terminal(
cmd,
log_stdout=True,
log_stderr=True,
log_sanitize=password,
stream_stdout=False,
stream_stderr=False,
)
ret = term.recv()
assert password not in caplog.text
assert "******" in caplog.text