mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Do not hang on salt-ssh with sudo and sanitize log output
This commit is contained in:
parent
ea35cb527e
commit
a09b4f4450
5 changed files with 79 additions and 2 deletions
1
changelog/62603.fixed
Normal file
1
changelog/62603.fixed
Normal file
|
@ -0,0 +1 @@
|
|||
Fix a hang on salt-ssh when using sudo.
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue