fix(ssh): verify the host key netOrk expects, report the one seen (NetOrk/netork#1167)
CI / test (3.10) (push) Successful in 32s
CI / test (3.11) (push) Successful in 37s
CI / test (3.12) (push) Successful in 35s
CI / test (3.10) (pull_request) Successful in 29s
CI / test (3.11) (pull_request) Successful in 28s
CI / test (3.12) (pull_request) Successful in 28s
CI / test (3.10) (push) Successful in 32s
CI / test (3.11) (push) Successful in 37s
CI / test (3.12) (push) Successful in 35s
CI / test (3.10) (pull_request) Successful in 29s
CI / test (3.11) (pull_request) Successful in 28s
CI / test (3.12) (pull_request) Successful in 28s
The driver accepted any SSH host key. It now verifies through napalm-device-types' HostKeyCheck (>= 4.19.0): with a known key in optional_args["ssh_host_keys"] any other key is refused before login, and every key seen is reported to optional_args["ssh_host_key_observer"]. Without either argument it connects as before (first use).
This commit is contained in:
1 parent
382519f780
commit
fe0aec9432
3 files changed
+149
-7
No files matched your search
@@ -44,6 +44,12 @@ from napalm.base.exceptions import (
|
||||
ReplaceConfigException,
|
||||
)
|
||||
from napalm.base.netmiko_helpers import netmiko_args
|
||||
from napalm_device_types.ssh_host_keys import (
|
||||
KNOWN_KEYS_ARG,
|
||||
OBSERVER_ARG,
|
||||
HostKeyCheck,
|
||||
SshHostKeyMismatch,
|
||||
)
|
||||
|
||||
from napalm_procurve.api_client import ProcurveApiClient
|
||||
from napalm_procurve import parsers
|
||||
@@ -153,6 +159,10 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
self.netmiko_optional_args = netmiko_args(optional_args)
|
||||
# The host keys to expect and where the one seen is reported (netork#1167).
|
||||
self._host_key_args: Dict[str, Any] = {
|
||||
k: optional_args[k] for k in (KNOWN_KEYS_ARG, OBSERVER_ARG) if k in optional_args
|
||||
}
|
||||
|
||||
# --- runtime state ---
|
||||
# Active transport: "api" | "ssh" | "ssh_legacy" | "telnet"
|
||||
@@ -318,17 +328,36 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
|
||||
**extra,
|
||||
)
|
||||
|
||||
def _ssh_connect(self, legacy: bool = False, port: Optional[int] = None) -> ConnectHandler:
|
||||
"""A netmiko SSH session, its host key verified and reported (netork#1167).
|
||||
|
||||
Every SSH session of this driver opens here. A refused key raises
|
||||
:class:`SshHostKeyMismatch` before the login.
|
||||
"""
|
||||
kwargs = self._netmiko_kwargs(legacy)
|
||||
if port is not None:
|
||||
kwargs["port"] = port
|
||||
check = HostKeyCheck(self._host_key_args, self.hostname, kwargs["port"])
|
||||
with check.netmiko(kwargs) as verified:
|
||||
conn = ConnectHandler(**verified)
|
||||
check.observe(conn.remote_conn_pre.get_transport())
|
||||
return conn
|
||||
|
||||
def _try_ssh(self, legacy: bool = False) -> bool:
|
||||
"""Probe and connect via SSH. Returns True on success."""
|
||||
label = "SSH-legacy" if legacy else "SSH"
|
||||
transport = "ssh_legacy" if legacy else "ssh"
|
||||
logger.debug("Trying %s for %s", label, self.hostname)
|
||||
try:
|
||||
conn = ConnectHandler(**self._netmiko_kwargs(legacy))
|
||||
conn = self._ssh_connect(legacy)
|
||||
self._device = conn
|
||||
self._transport = transport
|
||||
logger.info("Connected to %s via %s", self.hostname, label)
|
||||
return True
|
||||
except SshHostKeyMismatch:
|
||||
# Not another transport: the legacy KEX or Telnet would reach the same
|
||||
# host, and Telnet would hand it the password in clear text.
|
||||
raise
|
||||
except NetmikoAuthenticationException as exc:
|
||||
raise self._auth_failure(transport, exc) from exc
|
||||
except NetmikoTimeoutException:
|
||||
@@ -638,9 +667,7 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
|
||||
self.port is the REST API port (443) in API transport mode.
|
||||
"""
|
||||
try:
|
||||
kwargs = self._netmiko_kwargs()
|
||||
kwargs["port"] = 22 # always SSH — self.port is the REST API port
|
||||
conn = ConnectHandler(**kwargs)
|
||||
conn = self._ssh_connect(port=22) # always SSH — self.port is the REST API port
|
||||
try:
|
||||
return conn.send_command("show running-config")
|
||||
finally:
|
||||
@@ -1248,9 +1275,12 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver):
|
||||
ssh_error = ""
|
||||
for legacy in (False, True):
|
||||
try:
|
||||
self._device = ConnectHandler(**self._netmiko_kwargs(legacy))
|
||||
self._device = self._ssh_connect(legacy)
|
||||
self._transport = "ssh_legacy" if legacy else "ssh"
|
||||
break
|
||||
except SshHostKeyMismatch as exc: # the same host either way
|
||||
ssh_error = str(exc)
|
||||
break
|
||||
except Exception as exc: # noqa: BLE001 - the next form, then report
|
||||
ssh_error = str(exc)
|
||||
try:
|
||||
|
||||
Reference in new issue
Block a user