ssh/sftp Server via Paramiko ?

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
maha
User
Beiträge: 2
Registriert: Montag 9. Dezember 2019, 16:14

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)
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@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()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
maha
User
Beiträge: 2
Registriert: Montag 9. Dezember 2019, 16:14

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?
Antworten