File enable-keepalive-probes-for-salt-ssh-executions-bsc-.patch of Package salt
From e5a0ccc0e3825bd12353df1e8bbc850df4ec2082 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
<psuarezhernandez@suse.com>
Date: Fri, 1 Dec 2023 10:59:30 +0000
Subject: [PATCH] Enable "KeepAlive" probes for Salt SSH executions
(bsc#1211649) (#610)
* Enable KeepAlive probes for Salt SSH connections (bsc#1211649)
* Add tests for Salt SSH keepalive options
* Add changelog file
* Make changes suggested by pre-commit
---
changelog/65488.added.md | 1 +
salt/client/ssh/__init__.py | 20 ++++++-
salt/client/ssh/client.py | 13 ++++-
salt/client/ssh/shell.py | 12 +++++
salt/config/__init__.py | 6 +++
salt/utils/parsers.py | 19 +++++++
tests/pytests/unit/client/ssh/test_single.py | 55 ++++++++++++++++++++
tests/pytests/unit/client/ssh/test_ssh.py | 3 ++
8 files changed, 127 insertions(+), 2 deletions(-)
create mode 100644 changelog/65488.added.md
diff --git a/changelog/65488.added.md b/changelog/65488.added.md
new file mode 100644
index 0000000000..78476cec11
--- /dev/null
+++ b/changelog/65488.added.md
@@ -0,0 +1 @@
+Enable "KeepAlive" probes for Salt SSH executions
diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py
index cd5592ff86..1832f5fd21 100644
--- a/salt/client/ssh/__init__.py
+++ b/salt/client/ssh/__init__.py
@@ -50,8 +50,8 @@ import salt.utils.thin
import salt.utils.url
import salt.utils.verify
from salt._logging import LOG_LEVELS
-from salt._logging.mixins import MultiprocessingStateMixin
from salt._logging.impl import LOG_LOCK
+from salt._logging.mixins import MultiprocessingStateMixin
from salt.template import compile_template
from salt.utils.process import Process
from salt.utils.zeromq import zmq
@@ -360,6 +360,18 @@ class SSH(MultiprocessingStateMixin):
"ssh_timeout", salt.config.DEFAULT_MASTER_OPTS["ssh_timeout"]
)
+ self.opts.get("timeout", salt.config.DEFAULT_MASTER_OPTS["timeout"]),
+ "keepalive": self.opts.get(
+ "ssh_keepalive",
+ salt.config.DEFAULT_MASTER_OPTS["ssh_keepalive"],
+ ),
+ "keepalive_interval": self.opts.get(
+ "ssh_keepalive_interval",
+ salt.config.DEFAULT_MASTER_OPTS["ssh_keepalive_interval"],
+ ),
+ "keepalive_count_max": self.opts.get(
+ "ssh_keepalive_count_max",
+ salt.config.DEFAULT_MASTER_OPTS["ssh_keepalive_count_max"],
+ ),
"sudo": self.opts.get(
"ssh_sudo", salt.config.DEFAULT_MASTER_OPTS["ssh_sudo"]
),
@@ -1163,6 +1175,9 @@ class Single:
remote_port_forwards=None,
winrm=False,
ssh_options=None,
+ keepalive=True,
+ keepalive_interval=60,
+ keepalive_count_max=3,
**kwargs,
):
# Get mine setting and mine_functions if defined in kwargs (from roster)
@@ -1223,6 +1238,9 @@ class Single:
"priv": priv,
"priv_passwd": priv_passwd,
"timeout": timeout,
+ "keepalive": keepalive,
+ "keepalive_interval": keepalive_interval,
+ "keepalive_count_max": keepalive_count_max,
"sudo": sudo,
"tty": tty,
"mods": self.mods,
diff --git a/salt/client/ssh/client.py b/salt/client/ssh/client.py
index 72646569e2..dcbe3a1795 100644
--- a/salt/client/ssh/client.py
+++ b/salt/client/ssh/client.py
@@ -56,6 +56,9 @@ class SSHClient:
("ssh_priv_passwd", str),
("ssh_identities_only", bool),
("ssh_remote_port_forwards", str),
+ ("ssh_keepalive", bool),
+ ("ssh_keepalive_interval", int),
+ ("ssh_keepalive_count_max", int),
("ssh_options", list),
("ssh_max_procs", int),
("ssh_askpass", bool),
@@ -113,7 +116,15 @@ class SSHClient:
return sane_kwargs
def _prep_ssh(
- self, tgt, fun, arg=(), timeout=None, tgt_type="glob", kwarg=None, context=None, **kwargs
+ self,
+ tgt,
+ fun,
+ arg=(),
+ timeout=None,
+ tgt_type="glob",
+ kwarg=None,
+ context=None,
+ **kwargs
):
"""
Prepare the arguments
diff --git a/salt/client/ssh/shell.py b/salt/client/ssh/shell.py
index 0cdf2ec30a..78dc8c5709 100644
--- a/salt/client/ssh/shell.py
+++ b/salt/client/ssh/shell.py
@@ -90,6 +90,9 @@ class Shell:
remote_port_forwards=None,
winrm=False,
ssh_options=None,
+ keepalive=True,
+ keepalive_interval=None,
+ keepalive_count_max=None,
):
self.opts = opts
# ssh <ipv6>, but scp [<ipv6>]:/path
@@ -100,6 +103,9 @@ class Shell:
self.priv = priv
self.priv_passwd = priv_passwd
self.timeout = timeout
+ self.keepalive = keepalive
+ self.keepalive_interval = keepalive_interval
+ self.keepalive_count_max = keepalive_count_max
self.sudo = sudo
self.tty = tty
self.mods = mods
@@ -135,6 +141,9 @@ class Shell:
if self.opts.get("_ssh_version", (0,)) > (4, 9):
options.append("GSSAPIAuthentication=no")
options.append(f"ConnectTimeout={self.timeout}")
+ if self.keepalive:
+ options.append(f"ServerAliveInterval={self.keepalive_interval}")
+ options.append(f"ServerAliveCountMax={self.keepalive_count_max}")
if self.opts.get("ignore_host_keys"):
options.append("StrictHostKeyChecking=no")
if self.opts.get("no_host_keys"):
@@ -170,6 +179,9 @@ class Shell:
if self.opts["_ssh_version"] > (4, 9):
options.append("GSSAPIAuthentication=no")
options.append(f"ConnectTimeout={self.timeout}")
+ if self.keepalive:
+ options.append(f"ServerAliveInterval={self.keepalive_interval}")
+ options.append(f"ServerAliveCountMax={self.keepalive_count_max}")
if self.opts.get("ignore_host_keys"):
options.append("StrictHostKeyChecking=no")
if self.opts.get("no_host_keys"):
diff --git a/salt/config/__init__.py b/salt/config/__init__.py
index ea87f10fee..046491fa73 100644
--- a/salt/config/__init__.py
+++ b/salt/config/__init__.py
@@ -834,6 +834,9 @@ VALID_OPTS = immutabletypes.freeze(
"ssh_scan_ports": str,
"ssh_scan_timeout": float,
"ssh_identities_only": bool,
+ "ssh_keepalive": bool,
+ "ssh_keepalive_interval": int,
+ "ssh_keepalive_count_max": int,
"ssh_log_file": str,
"ssh_config_file": str,
"ssh_merge_pillar": bool,
@@ -1619,6 +1622,9 @@ DEFAULT_MASTER_OPTS = immutabletypes.freeze(
"ssh_scan_ports": "22",
"ssh_scan_timeout": 0.01,
"ssh_identities_only": False,
+ "ssh_keepalive": True,
+ "ssh_keepalive_interval": 60,
+ "ssh_keepalive_count_max": 3,
"ssh_log_file": os.path.join(salt.syspaths.LOGS_DIR, "ssh"),
"ssh_config_file": os.path.join(salt.syspaths.HOME_DIR, ".ssh", "config"),
"cluster_mode": False,
diff --git a/salt/utils/parsers.py b/salt/utils/parsers.py
index 6822f9fe46..d37bebf770 100644
--- a/salt/utils/parsers.py
+++ b/salt/utils/parsers.py
@@ -3384,6 +3384,25 @@ class SaltSSHOptionParser(
"-R parameters."
),
)
+ ssh_group.add_option(
+ "--disable-keepalive",
+ default=True,
+ action="store_false",
+ dest="ssh_keepalive",
+ help=(
+ "Disable KeepAlive probes (ServerAliveInterval) for the SSH connection."
+ ),
+ )
+ ssh_group.add_option(
+ "--keepalive-interval",
+ dest="ssh_keepalive_interval",
+ help=("Define the value for ServerAliveInterval option."),
+ )
+ ssh_group.add_option(
+ "--keepalive-count-max",
+ dest="ssh_keepalive_count_max",
+ help=("Define the value for ServerAliveCountMax option."),
+ )
ssh_group.add_option(
"--ssh-option",
dest="ssh_options",
diff --git a/tests/pytests/unit/client/ssh/test_single.py b/tests/pytests/unit/client/ssh/test_single.py
index 5e5357bf22..156fefe4c0 100644
--- a/tests/pytests/unit/client/ssh/test_single.py
+++ b/tests/pytests/unit/client/ssh/test_single.py
@@ -74,6 +74,61 @@ def test_single_opts(opts, target, mock_bin_paths):
**target,
)
+ assert single.shell._ssh_opts() == ""
+ expected_cmd = (
+ "ssh login1 "
+ "-o KbdInteractiveAuthentication=no -o "
+ "PasswordAuthentication=yes -o ConnectTimeout=65 -o ServerAliveInterval=60 "
+ "-o ServerAliveCountMax=3 -o Port=22 "
+ "-o IdentityFile=/etc/salt/pki/master/ssh/salt-ssh.rsa "
+ "-o User=root date +%s"
+ )
+ assert single.shell._cmd_str("date +%s") == expected_cmd
+
+
+def test_single_opts_custom_keepalive_options(opts, target):
+ """Sanity check for ssh.Single options with custom keepalive"""
+
+ single = ssh.Single(
+ opts,
+ opts["argv"],
+ "localhost",
+ mods={},
+ fsclient=None,
+ thin=salt.utils.thin.thin_path(opts["cachedir"]),
+ mine=False,
+ keepalive_interval=15,
+ keepalive_count_max=5,
+ **target,
+ )
+
+ assert single.shell._ssh_opts() == ""
+ expected_cmd = (
+ "ssh login1 "
+ "-o KbdInteractiveAuthentication=no -o "
+ "PasswordAuthentication=yes -o ConnectTimeout=65 -o ServerAliveInterval=15 "
+ "-o ServerAliveCountMax=5 -o Port=22 "
+ "-o IdentityFile=/etc/salt/pki/master/ssh/salt-ssh.rsa "
+ "-o User=root date +%s"
+ )
+ assert single.shell._cmd_str("date +%s") == expected_cmd
+
+
+def test_single_opts_disable_keepalive(opts, target):
+ """Sanity check for ssh.Single options with custom keepalive"""
+
+ single = ssh.Single(
+ opts,
+ opts["argv"],
+ "localhost",
+ mods={},
+ fsclient=None,
+ thin=salt.utils.thin.thin_path(opts["cachedir"]),
+ mine=False,
+ keepalive=False,
+ **target,
+ )
+
assert single.shell._ssh_opts() == ""
expected_cmd = (
"ssh login1 "
diff --git a/tests/pytests/unit/client/ssh/test_ssh.py b/tests/pytests/unit/client/ssh/test_ssh.py
index 3a43805cff..e99c787dde 100644
--- a/tests/pytests/unit/client/ssh/test_ssh.py
+++ b/tests/pytests/unit/client/ssh/test_ssh.py
@@ -78,6 +78,9 @@ def roster():
("ssh_scan_ports", "test", True),
("ssh_scan_timeout", 1.0, True),
("ssh_timeout", 1, False),
+ ("ssh_keepalive", True, True),
+ ("ssh_keepalive_interval", 30, True),
+ ("ssh_keepalive_count_max", 3, True),
("ssh_log_file", "/tmp/test", True),
("raw_shell", True, True),
("refresh_cache", True, True),
--
2.47.0