Seite 1 von 1

ssh/sftp Server via Paramiko ?

Verfasst: Mittwoch 10. Juni 2020, 09:45
von maha
Hallo,

z.Zt. habe ich einen SFTP Server (via Paramiko) am Laufen.
Zusätzlich soll nun der gleiche Server auch in der Lage sein, Remote Commandos auszuführen, welche von einem ssh Client kommen.

m.E. müsste ich nun einen EXEC Channel erstellen, der derartige Anfragen abarbeiten kann.

Anbei das Code Fragement, welches für SFTP ausgelegt läuft.

Ideen?
Vorschläge?

Vielen Dank.

Code: Alles auswählen

class StubServer (ServerInterface, ):
    def check_auth_publickey(self, username, key):
            return paramiko.AUTH_SUCCESSFUL

    def check_channel_request(self, kind, chanid):
        return OPEN_SUCCEEDED

    def get_allowed_auths(self, username):
        return "publickey"

    def check_channel_exec_request ( self, channel, command ):
        print ( f'....> check_channel_exec_request commmand: {command}' )
        return True 

class ConnHandlerThd(threading.Thread):
    def __init__(self, conn, addr, serverkeyfile ):
        threading.Thread.__init__(self)
        self._conn = conn
        self._addr = addr
        self._serverkeyfile = serverkeyfile

    def run(self):
        server_key = paramiko.RSAKey.from_private_key_file(self._serverkeyfile)
        name = server_key.get_name()

        transport = paramiko.Transport(self._conn)
        transport.add_server_key(server_key)

        transport.set_subsystem_handler( 'sftp', paramiko.SFTPServer, StubSFTPServer)

        server = StubServer()
        transport.start_server(server=server)

        channel = transport.accept()
        while transport.is_active():
            time.sleep(1)

def start_server(host, port, serverkeyfile, logfile=logfile, level=level, backlog=backlog ):
    paramiko_level = getattr(paramiko.common, level)
    paramiko.common.logging.basicConfig(level=paramiko_level)

    paramiko.util.log_to_file ( logfile, level = level )

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    server_socket.bind((host, port))
    server_socket.listen(backlog)

    while True:
        conn, addr = server_socket.accept()
        srv_thd = ConnHandlerThd(conn, addr, serverkeyfile )
        srv_thd.setDaemon(True)
        srv_thd.start()

start_server(host=host, port=port, serverkeyfile=keyfile, level=level, logfile=logfile, backlog=backlog)

Re: ssh/sftp Server via Paramiko ?

Verfasst: Donnerstag 11. Juni 2020, 10:39
von __blackjack__
@maha: Anmerkungen zum Quelltext:

Die Leerzeichensetzung ist ziemlich chaotisch. Zwischen Name und öffnender Klammer bei Aufrufen gehört kein Leerzeichen. Nach öffnenden und vor schliessenden Klammern gehört kein Leerzeichen. Und um das Gleichheitszeichen bei Schlüsselwortargumenten auch nicht.

Warum wird `AUTH_SUCCESSFUL` über das `paramiko`-Modul angesprochen, `OPEN_SUCCEEDED` aber offenbar importiert?

Der Aufruf von `start_server()` sollte nicht ausgeführt werden wenn man das Modul importiert, nur wenn man es als Programm ausführt.

Konstanten werden in Python KOMPLETT_GROSS geschrieben.

Namen sollte man nicht kryptisch abkürzen. Wenn man `server_thread` meint, sollte man nicht `srv_thd` schreiben.

`setDaemon()` ist veraltet, das wird irgendwann aus der API verschwinden. Entweder setzt man das `daemon`-Property oder man übergibt das als optionales Argument an die `Thread.__init__()`. Dann braucht man das `Thread`-Objekt auch gar nicht mehr an einen Namen zu binden.

`ConnHandlerThd` ist im Grunde keine Klasse sondern einfach nur eine unnötigerweise in einer Klasse verpackte Funktion. Die Klasse bietet hier keinerlei Mehrwert.

`serverkeyfile` ist gar keine Datei, sondern ein Datei*name*. Gleiches gilt für `logfile`.

Die Adresse (Client-IP, Port) wird im Thread gar nicht verwendet. Und es werden in der `run()` zwei Namen definiert (`name` und `channel`) die nicht verwendet werden.

Code: Alles auswählen

#!/usr/bin/env python3
import socket
import threading
import time

import paramiko

HOST = ...
PORT = ...
KEY_FILENAME = ...
LOG_FILENAME = ...
LEVEL = ...
BACKLOG = ...


class StubSFTPServer:
    ...


class StubServer(paramiko.ServerInterface):
    def check_auth_publickey(self, username, key):
        return paramiko.AUTH_SUCCESSFUL

    def check_channel_request(self, kind, chanid):
        return paramiko.OPEN_SUCCEEDED

    def get_allowed_auths(self, username):
        return "publickey"

    def check_channel_exec_request(self, channel, command):
        print(f"....> check_channel_exec_request commmand: {command}")
        return True


def handle_connection(connection, server_key_filename):
    server_key = paramiko.RSAKey.from_private_key_file(server_key_filename)

    transport = paramiko.Transport(connection)
    transport.add_server_key(server_key)
    transport.set_subsystem_handler(
        "sftp", paramiko.SFTPServer, StubSFTPServer
    )
    transport.start_server(server=StubServer())
    transport.accept()

    while transport.is_active():
        time.sleep(1)


def start_server(host, port, server_key_filename, log_filename, level, backlog):
    paramiko_level = getattr(paramiko.common, level)
    paramiko.common.logging.basicConfig(level=paramiko_level)
    paramiko.util.log_to_file(log_filename, level=level)

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    server_socket.bind((host, port))
    server_socket.listen(backlog)

    while True:
        connection, _address = server_socket.accept()
        threading.Thread(
            target=handle_connection,
            args=[connection, server_key_filename],
            daemon=True,
        ).start()


def main():
    start_server(HOST, PORT, KEY_FILENAME, LOG_FILENAME, LEVEL, BACKLOG)


if __name__ == "__main__":
    main()

Re: ssh/sftp Server via Paramiko ?

Verfasst: Donnerstag 11. Juni 2020, 11:52
von maha
Vielen Dank für die Kommentare und den Code Review.
Ich bin dabei das entsprechend einzupflegen.

Am meisten brennt mir unter den Nägeln, wie ich hier noch einen EXEC channel hinzufüge, damit dieser per sftp und auch per ssh (rexec command) angesprochen werden kann.
Vorschläge?