adamcharnock / netbox-routeros

36 stars 7 forks source link

Fail to get config #1

Open peterslopes opened 3 years ago

peterslopes commented 3 years ago

I tried to get a Mikrotik config using Netbox and also directly from python and I got this errors:

Netbox error:

Server Error There was a problem with your request. Please contact an administrator.

The complete exception is provided below:

<class 'RuntimeError'>

Model class napalm_ros.models.SshHostKey doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.

Python version: 3.8.10 NetBox version: 2.10.10 If further assistance is required, please post to the NetBox mailing list.

Python error:

print(device.get_config ()) Traceback (most recent call last): File "", line 1, in File "/opt/netbox-2.10.10/venv/lib/python3.8/site-packages/napalm_ros/ros.py", line 494, in get_config with self.ssh_client: File "/opt/netbox-2.10.10/venv/lib/python3.8/site-packages/napalm_ros/ssh_client.py", line 34, in enter from napalm_ros.models import SshHostKey File "/opt/netbox-2.10.10/venv/lib/python3.8/site-packages/napalm_ros/models.py", line 21, in class SshHostKey(models.Model): File "/opt/netbox-2.10.10/venv/lib/python3.8/site-packages/django/db/models/base.py", line 108, in new app_config = apps.get_containing_app_config(module) File "/opt/netbox-2.10.10/venv/lib/python3.8/site-packages/django/apps/registry.py", line 253, in get_containing_app_config self.check_apps_ready() File "/opt/netbox-2.10.10/venv/lib/python3.8/site-packages/django/apps/registry.py", line 135, in check_apps_ready settings.INSTALLED_APPS File "/opt/netbox-2.10.10/venv/lib/python3.8/site-packages/django/conf/init.py", line 82, in getattr self._setup(name) File "/opt/netbox-2.10.10/venv/lib/python3.8/site-packages/django/conf/init.py", line 63, in _setup raise ImproperlyConfigured( django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.

peterslopes commented 2 years ago

I was able to get this working changing the ssh_client.py file as bellow:

import base64
import socket
from pathlib import Path
from typing import List, Union

import paramiko

class SshClient:

    def __init__(
            self,
            host: str,
            username: str,
            host_key: str = None,
            private_key: Path = None,
            password: str = None,
            timeout: int = 10,
    ):
        self.client = None
        # Use an open count to this can be used in nested contexts
        # (handy when passing an already open client into functions)
        self._open_count = 0

        self.host = host
        self.username = username
        self.host_key = host_key
        self.private_key = private_key
        self.password = password
        self.timeout = timeout
        teste = paramiko.SSHClient()
        teste.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    def __enter__(self):
        #if not self.host_key:
        #    from napalm_ros.models import SshHostKey
        #    self.host_key = SshHostKey.objects.for_hostname(self.host).host_key
        self.open()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def open(self):
        self._open_count += 1

        #host_key_type, host_key_data = self.host_key.split(" ", 1)
        #if host_key_type == "ssh-rsa":
             #host_key = paramiko.RSAKey(data=base64.b64decode(host_key_data))  # noqa
        #else:
             #host_key = paramiko.ECDSAKey(data=base64.b64decode(host_key_data))  # noqa

        kwargs = {}
        if self.private_key:
            kwargs["pkey"] = paramiko.RSAKey.from_private_key_file(str(self.private_key))
        if self.password:
            kwargs["password"] = self.password

        self.client = paramiko.SSHClient()
        self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) #Auto add host key
        #self.client.get_host_keys().add(self.host, host_key_type, host_key)
        self.client.connect(self.host, username=self.username, timeout=self.timeout, allow_agent=False, look_for_keys=False, **kwargs)

    def close(self):
        self._open_count -= 1
        if not self._open_count:
            self.client.close()

    def _assert_open(self):
        if not self._open_count:
            raise SshClientNotOpen("SSH client needs to be opened first. Call client.open()")

    def exec(self, command, **kwargs):
        self._assert_open()
        stdin, stdout, stderr = self.client.exec_command(command, **kwargs)
        return stdin, stdout, stderr, stdout.channel.recv_exit_status()

    def run(self, command, raise_exceptions=True) -> List[str]:
        """Like exec(), but just returns stdout for quick and easy use"""
        self._assert_open()
        stdin, stdout, stderr, exit_code = self.exec(command)
        if exit_code == 0 or not raise_exceptions:
            return [s.strip() for s in stdout.readlines()]
        else:
            error = (stderr.read() or stdout.read()).decode("utf8")
            raise SshCommandException(f"Error executing: {command}\nError: {error}")

    def write_file(self, path: Union[Path, str], data: bytes):
        """Write the given data to the specified path on the server"""
        self._assert_open()
        sftp_client: paramiko.SFTPClient = self.client.open_sftp()
        try:
            with sftp_client.open(str(path), "w") as f:
                f.write(data)
        finally:
            sftp_client.close()

    def read_file(self, path: str) -> bytes:
        """Read the data from the given path on the server"""
        self._assert_open()
        sftp_client: paramiko.SFTPClient = self.client.open_sftp()
        try:
            with sftp_client.open(path, "r") as f:
                return f.read()
        finally:
            sftp_client.close()

def get_fingerprint(host, port=22):
    ssh_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    ssh_socket.settimeout(5)
    ssh_socket.connect((host, port))

    with ssh_socket:
        transport = paramiko.Transport(ssh_socket)  # noqa
        # Out SSH client only supports ECDSA & RSA host keys, so only use those
        transport._preferred_keys = [
            "ecdsa-sha2-nistp256",
            "ecdsa-sha2-nistp384",
            "ecdsa-sha2-nistp521",
            "ssh-rsa",
        ]

        try:
            transport.start_client()
            ssh_key = transport.get_remote_server_key()
        finally:
            transport.close()

    printable_type = ssh_key.get_name()
    printable_key = base64.encodebytes(ssh_key.asbytes()).strip()
    return f"{printable_type} {printable_key.decode('utf8')}"

class SshClientAlreadyOpen(Exception):
    pass

class SshClientNotOpen(Exception):
    pass

class SshCommandException(Exception):
    pass
adamcharnock commented 2 years ago

Thank you for this @peterslopes. Unfortunately I don't have time to look into this at the moment. However, I will gladly look at any pull requests which address this issue.

If anyone else encounters this can you add you experience here please? Thanks.

peterslopes commented 2 years ago

Ok, I got it.