diff --git a/napalm_procurve/procurve.py b/napalm_procurve/procurve.py index 4a1432c..234955d 100644 --- a/napalm_procurve/procurve.py +++ b/napalm_procurve/procurve.py @@ -590,30 +590,58 @@ class ProcurveDriver(ConfigLifecycleMixin, SwitchDriver): """Open a one-shot SSH session to retrieve running-config. Used as fallback when the REST API does not expose the config endpoint - (e.g. HP 2530 / YA firmware). Tries legacy KEX first (required for - older ProCurve firmware), then standard. Each attempt is capped at - 8 seconds so this fallback never pushes the overall poll over budget. - Returns empty string on any failure. + (e.g. HP 2530 / YA firmware with Mocana SSH). + + Uses paramiko directly (not netmiko) to: + - Explicitly enable diffie-hellman-group1-sha1 (required by Mocana SSH 6.3) + - Keep total blocking time ≤ 10 seconds """ - _FALLBACK_TIMEOUT = 8 # seconds per attempt — keeps total ≤ 16 s - for legacy in (True, False): + import paramiko + + _FALLBACK_TIMEOUT = 8 + + # KEX list that includes the old group1-sha1 required by Mocana SSH 6.3 + _LEGACY_KEX = [ + "diffie-hellman-group1-sha1", + "diffie-hellman-group14-sha1", + "diffie-hellman-group14-sha256", + "diffie-hellman-group-exchange-sha256", + ] + _LEGACY_DISABLED = { + "pubkeys": ["rsa-sha2-256", "rsa-sha2-512"], + } + + try: + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + # Patch the preferred kex list on the class before connecting so + # paramiko will negotiate group1-sha1 with the old Mocana SSH server. + orig_kex = paramiko.Transport._preferred_kex try: - kwargs = self._netmiko_kwargs(legacy=legacy) - kwargs["timeout"] = _FALLBACK_TIMEOUT - conn = ConnectHandler(**kwargs) - try: - return conn.send_command("show running-config") - finally: - conn.disconnect() - except Exception as exc: - logger.debug( - "SSH config fallback (%s) failed for %s: %s", - "legacy" if legacy else "standard", - self.hostname, - exc, + paramiko.Transport._preferred_kex = tuple(_LEGACY_KEX) + client.connect( + hostname=self.hostname, + port=self.port or 22, + username=self.username, + password=self.password, + timeout=_FALLBACK_TIMEOUT, + banner_timeout=_FALLBACK_TIMEOUT, + auth_timeout=_FALLBACK_TIMEOUT, + look_for_keys=False, + allow_agent=False, + disabled_algorithms=_LEGACY_DISABLED, ) - logger.warning("SSH config fallback failed for %s (all transports)", self.hostname) - return "" + finally: + paramiko.Transport._preferred_kex = orig_kex + + _, stdout, _ = client.exec_command("show running-config", timeout=_FALLBACK_TIMEOUT) + config = stdout.read().decode("utf-8", errors="replace") + client.close() + return config + except Exception as exc: + logger.warning("SSH config fallback failed for %s: %s", self.hostname, exc) + return "" # ------------------------------------------------------------------ # NAPALM: get_environment