Mechanical: ruff format, and ruff check --fix with the rule set every NAPALM driver uses (E, F, I, UP, B at line length 100, Python 3.10). What ruff cannot fix comes in the next commit.
89 lines
2.9 KiB
Python
89 lines
2.9 KiB
Python
"""open() hands netmiko the host key to expect, and reports the one it saw (NetOrk/netork#1167).
|
|
|
|
OpenMediaVault and QNAP open through ``LinuxDriver.open()`` and get this too.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import paramiko
|
|
import pytest
|
|
from napalm_device_types.ssh_host_keys import (
|
|
KNOWN_KEYS_ARG,
|
|
OBSERVER_ARG,
|
|
SshHostKey,
|
|
SshHostKeyMismatch,
|
|
)
|
|
from netmiko.exceptions import NetmikoTimeoutException
|
|
|
|
from napalm_linux.linux import LinuxDriver
|
|
|
|
SERVER_KEY = paramiko.ECDSAKey.generate()
|
|
OTHER_KEY = paramiko.ECDSAKey.generate()
|
|
|
|
|
|
def _device() -> MagicMock:
|
|
device = MagicMock()
|
|
device.send_command.return_value = "/usr/bin/apt"
|
|
device.remote_conn_pre.get_transport.return_value.get_remote_server_key.return_value = (
|
|
SERVER_KEY
|
|
)
|
|
return device
|
|
|
|
|
|
def _driver(seen: list, known: SshHostKey | None = None) -> LinuxDriver:
|
|
args: dict = {"port": 2222, OBSERVER_ARG: seen.append}
|
|
if known is not None:
|
|
args[KNOWN_KEYS_ARG] = {"2222": known.as_dict()}
|
|
return LinuxDriver("10.0.0.8", "admin", "s3cret", optional_args=args)
|
|
|
|
|
|
def test_first_use_lets_netmiko_accept_and_reports_the_key():
|
|
seen: list = []
|
|
with patch("napalm_linux.linux.ConnectHandler", return_value=_device()) as handler:
|
|
_driver(seen).open()
|
|
kwargs = handler.call_args.kwargs
|
|
assert kwargs["ssh_strict"] is False
|
|
assert kwargs["alt_host_keys"] is False
|
|
assert kwargs["system_host_keys"] is False
|
|
assert seen[0]["accepted"] is True
|
|
assert seen[0]["seen"]["fingerprint"] == SshHostKey.of(SERVER_KEY).fingerprint
|
|
assert (seen[0]["host"], seen[0]["port"]) == ("10.0.0.8", 2222)
|
|
|
|
|
|
def test_a_known_key_is_the_only_one_netmiko_accepts():
|
|
seen: list = []
|
|
known = SshHostKey.of(SERVER_KEY)
|
|
files: list[str] = []
|
|
|
|
def connect(**kwargs):
|
|
files.append(kwargs["alt_key_file"])
|
|
with open(kwargs["alt_key_file"]) as f:
|
|
assert f.read().strip() == known.known_hosts_line("10.0.0.8", 2222)
|
|
assert kwargs["ssh_strict"] is True and kwargs["alt_host_keys"] is True
|
|
return _device()
|
|
|
|
with patch("napalm_linux.linux.ConnectHandler", side_effect=connect):
|
|
_driver(seen, known).open()
|
|
assert not os.path.exists(files[0])
|
|
assert seen[0]["accepted"] is True
|
|
|
|
|
|
def test_a_changed_key_is_refused():
|
|
seen: list = []
|
|
|
|
def refuse(**kwargs):
|
|
try:
|
|
raise paramiko.BadHostKeyException("10.0.0.8", SERVER_KEY, OTHER_KEY)
|
|
except paramiko.SSHException as exc: # what netmiko does with it
|
|
raise NetmikoTimeoutException(f"A paramiko SSHException occurred: {exc}")
|
|
|
|
driver = _driver(seen, SshHostKey.of(OTHER_KEY))
|
|
with patch("napalm_linux.linux.ConnectHandler", side_effect=refuse):
|
|
with pytest.raises(SshHostKeyMismatch):
|
|
driver.open()
|
|
assert driver._device is None
|
|
assert seen[0]["accepted"] is False
|