Спойлеры
В этом посте я расскажу, как игрался и создавал на python сокеты, к которым можно подключаться через ssh клиенты.
Для начала (если оно не сломается добрыми людьми) можно попробовать подключиться к примеру:
ssh root@ssh.vsecoder.dev -p 2200

Это стена "Был здесь", поле ввода принимает ник, сохраняет, и потом выводит другим.
Предисловие
Что такое ssh уже хорошо рассказал один автор на хабре: Памятка пользователям ssh.
Я лишь покажу красивую схемку от рег.ру:

И скажу что мы заменим SSH-сервер своим скриптом на Python, а за tcp соединение будет отвечать модуль socket.
Начнём
Socket - встроенный модуль для создания низкоуровневых сетевых интерфейсов. Он включает в себя функции создания объекта сокета Socket, который и обрабатывает канал данных, а также функции, связанных с сетевыми задачами, такими как преобразование имени сервера в IP адрес и форматирование данных для отправки по сети.
Сокеты можно настроить для работы в качестве сервера и прослушивания входящих сообщений или для подключения к другим приложениям в качестве клиента. После подключения обоих концов сокета TCP/IP обмен данными становится двунаправленным.
В пример поднимем простой сервер и клиент:
# сервер import socket import sys # создаем TCP/IP сокет sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Привязываем сокет к порту server_address = ('localhost', 1234) sock.bind(server_address) # Слушаем входящие подключения sock.listen(1) while True: # ждем соединения connection, client_address = sock.accept() try: # Принимаем данные порциями и ретранслируем их while True: data = connection.recv(16) if data: data = data.upper() print(data) connection.sendall(data) else: break finally: # Очищаем соединение connection.close()
# клиент import socket import sys # Создаем TCP/IP сокет sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Подключаем сокет к порту, через который прослушивается сервер server_address = ('localhost', 10000) print('Подключено к {} порт {}'.format(*server_address)) sock.connect(server_address) try: # Отправка данных mess = 'Hello Wоrld!' print(f'Отправка: {mess}') message = mess.encode() sock.sendall(message) # Смотрим ответ amount_received = 0 amount_expected = len(message) while amount_received < amount_expected: data = sock.recv(16) amount_received += len(data) mess = data.decode() print(f'Получено: {data.decode()}') finally: sock.close()
Но в нашем случае нам придётся писать только серверный сокет, т.к. в роли клиента у нас выступает ssh клиент.
Вроде всё легко, подними сокет, да общайся, но не всё так однозначно. Попробуем создать сокет и подключиться к нему:

Клиент хочет с нами пообщаться, и если просто попробовать слать ему не то, что он ожидает, клиент принудительно разорвёт соединение:
Connection reset by 127.0.0.1 port 1234
Конечно можно попробовать со всем этим разобраться ручками, но я нашёл другой модуль, решающий эту проблему - paramiko.
Он позволяет как создавать ssh клиентские подключения, так и сервера. На гитхабе у него уже есть несколько готовых примеров, так и один с созданием сервера, но мне он показался перегруженным, поэтому покажу свой:
import socket import paramiko import threading from paramiko import Transport, RSAKey # ssh-keygen good_pub_key = RSAKey(filename="test.rsa") class Server(paramiko.ServerInterface): def __init__(self): self.event = threading.Event() self.user = 'root' self.password = 'root' def check_channel_request(self, kind, chanid): if kind == "session": return paramiko.OPEN_SUCCEEDED return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED def check_auth_password(self, username, password): if (username == self.user) and (password == self.password): return paramiko.AUTH_SUCCESSFUL return paramiko.AUTH_FAILED def check_auth_publickey(self, username, key): if (username == self.user) and (key == good_pub_key): return paramiko.AUTH_SUCCESSFUL return paramiko.AUTH_FAILED def enable_auth_gssapi(self): return False def get_allowed_auths(self, username): return "password,publickey" def check_channel_shell_request(self, channel): self.event.set() return True def check_channel_pty_request( self, channel, term, width, height, pixelwidth, pixelheight, modes ): return True if __name__ == "__main__": server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(('', 2200)) server.listen(5) while True: try: client, addr = server.accept() t = Transport(client) t.load_server_moduli() t.add_server_key(good_pub_key) t.start_server(server=Server()) chan = t.accept(20) if not chan: continue chan.send( "Welcome to my test server!\n \r" ) chan.send("\n \n \r") chan.close() except Exception as e: print(e)
В примере я создаю серверный интерфейс (класс на строке №10), на основе которого будет создана сессия подключения, аутентификация и тд.
Для запуска примера необходимо создать публичный ключ командой ssh-keygen и указать на 7й строчке путь к нему. Вот что будет, если подключиться к этому серверу:
> ssh root@localhost -p 2200 root@localhost's password: Permission denied, please try again. root@localhost's password: Welcome to my test server! Connection to localhost closed.
Для подключения нам нужно указать пользователя и пароль, так же paramiko поддерживает закрытые ключи, и, вроде должна быть возможность пропустить аутентификацию, но я такого не нашёл.
Что дальше?
Дальше можно:
Прикалываться, представьте реакцию, если человек подключится к серверу и получит подобное (но не переборщите, для запуска можно поменять порт ssh, а сокет запустить на 22м порте):

Устраивать опросы или создать свою ASCII галерею:

Да и просто интересная практика для изучения сокетов, tcp подключений и тд.