diff --git a/changelog/68790.fixed.md b/changelog/68790.fixed.md new file mode 100644 index 000000000000..dad042de12ed --- /dev/null +++ b/changelog/68790.fixed.md @@ -0,0 +1 @@ +Make `salt-ssh` work without issues using `domain\user` notation for remote user with SSH. diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py index cad76ab639ea..b5b40c8f80ea 100644 --- a/salt/client/ssh/__init__.py +++ b/salt/client/ssh/__init__.py @@ -1070,7 +1070,10 @@ def __init__( self.python_env = kwargs.get("ssh_python_env") else: if user: - thin_dir = DEFAULT_THIN_DIR.replace("%%USER%%", user) + thin_dir = DEFAULT_THIN_DIR.replace( + "%%USER%%", + re.sub(r"[^a-zA-Z0-9\._\-@]", "_", user), + ) else: thin_dir = DEFAULT_THIN_DIR.replace("%%USER%%", "root") self.thin_dir = thin_dir.replace( diff --git a/salt/client/ssh/shell.py b/salt/client/ssh/shell.py index cdefe9a29b4a..10ba155bf342 100644 --- a/salt/client/ssh/shell.py +++ b/salt/client/ssh/shell.py @@ -156,7 +156,7 @@ def _key_opts(self): if self.priv and self.priv != "agent-forwarding": options.append(f"IdentityFile={self.priv}") if self.user: - options.append(f"User={self.user}") + options.append(f"User={shlex.quote(self.user)}") if self.identities_only: options.append("IdentitiesOnly=yes") @@ -202,7 +202,7 @@ def _passwd_opts(self): if self.port: options.append(f"Port={self.port}") if self.user: - options.append(f"User={self.user}") + options.append(f"User={shlex.quote(self.user)}") if self.identities_only: options.append("IdentitiesOnly=yes") diff --git a/tests/pytests/unit/client/ssh/test_shell.py b/tests/pytests/unit/client/ssh/test_shell.py index 50b1a4b58508..eb818d16a9cd 100644 --- a/tests/pytests/unit/client/ssh/test_shell.py +++ b/tests/pytests/unit/client/ssh/test_shell.py @@ -273,3 +273,18 @@ def test_scp_command_execution_uses_custom_path(): args, _ = mock_run_cmd.call_args assert "/custom/scp" in args[0] assert "source_file.txt example.com:/path/dest_file.txt" in args[0] + + +def test_ssh_using_user_with_backslash(): + _shell = shell.Shell( + opts={"_ssh_version": (4, 9)}, + host="host.example.org", + user="exampledomain\\user", + passwd="password", + ) + with patch.object( + _shell, "_run_cmd", return_value=(None, None, None) + ) as mock_run_cmd: + cmd_string = _shell.exec_cmd("whoami") + args, _ = mock_run_cmd.call_args + assert " User='exampledomain\\user' " in args[0] diff --git a/tests/pytests/unit/client/ssh/test_single.py b/tests/pytests/unit/client/ssh/test_single.py index 3dba5b74199c..256d87b1f837 100644 --- a/tests/pytests/unit/client/ssh/test_single.py +++ b/tests/pytests/unit/client/ssh/test_single.py @@ -958,3 +958,21 @@ def test_run_integration_with_no_pre_hook(opts, target): with patch.object(single_instance, "cmd_block", mock_cmd_block): stdout, stderr, retcode = single_instance.run() assert retcode == 0 + + +def test_check_thin_dir_with_backslash_user(opts): + """ + Test `thin_dir` path generation for the user with backslash in the name + """ + single = ssh.Single( + opts, + opts["argv"], + "host.example.org", + "host.example.org", + user="exampledomain\\user", + mods={}, + fsclient=None, + mine=False, + ) + assert single.thin_dir == single.opts["thin_dir"] + assert ".exampledomain_user_" in single.thin_dir