RDP over SSH. Как я писал клиент для удаленки под винду
Когда в кино показывают, как на Земле разражается эпидемия страшного вируса, людям там нужны в основном тушенка и патроны. Когда эта фантастика воплотилась в реальности, оказалось, что нужнее всего — туалетная бумага и удаленные рабочие места. И если с первым все понятно, второй пункт вызывает вопросы с точки зрения безопасности и выбора ПО. Когда готовое решение не подходит, нужно браться за сборку своего велосипеда.
Сервисы, с помощью которых можно организовать подключение сотрудников на удаленке, обычно работают через свои серверы. Почти всегда соединение получается медленнее прямых подключений, да и безопасность таких служб тоже под большим вопросом. Чаще всего архитектура этих решений построена вокруг реализации VNC (Virtual Network Computing). Система базируется на протоколе RFB (Remote FrameBuffer). Управление устроено так: с одного компьютера на другой передаются нажатия клавиш и движения мыши и содержимое экрана ретранслируется через сеть. Сам VNC не шифрует передаваемые данные. Если требуется обеспечить повышенную безопасность, сессия может быть установлена через SSL, SSH или VPN-туннели, что несколько усложняет задачу.
На публичных сервисах все соединения устанавливаются через сервер, который выдает ID клиентов, подключая их либо через VPN напрямую друг к другу, либо через собственный канал. Конечно, можно поднять свой VPN-сервер, настроить маршрутизацию VPN-сети и локальной сети предприятия и подключать сотрудников по прямым IP-адресам. В этом случае клиенту нужно, помимо логина, пароля и адреса подключения, передать еще клиент VPN и данные для авторизации. Для некоторых пользователей все это сложновато, а это, в свою очередь усложнит поддержку.
Пробросить порты на локальные ПК тоже не вариант. Это небезопасно, да и настраивать маршрутизацию замучаешься, когда клиентов становится больше двух. В Linux есть замечательный клиент Remmina, который позволяет пробрасывать сессии RDP/VNC через SSH-соединение без дополнительных клиентов. В Windows можно организовать SSH-туннели через клиентские приложения, которые необходимо настраивать на удаленных пользовательских машинах. SSH-клиент «из коробки» есть только в Windows 10, но как быть с юзерами семерки и восьмерки? Да и для Windows 10 придется писать батник, и не один. Все это не добавляет баллов стандартным решениям. Но всегда можно придумать нестандартное. Чем мы прямо сейчас и займемся.
ПОСТАНОВКА ЗАДАЧИ
Итак, задача у нас будет следующая. Подключать пользователя по RDP (как выяснилось, это намного привычнее для большинства из них). Самое главное преимущество RDP по сравнению с VNC — это скорость. RDP быстрее потому, что этот протокол перерисовывает на стороне клиента только измененную часть экрана, а значит, данных передается меньше. Подключение должно быть безопасным. Подключение должно выполняться с минимальными настройками и не требовать от пользователя никаких дополнительных действий.
А ЧТО СКАЖЕТ GOOGLE?
В общем‑то, задача не новая, и реализаций построения SSH-туннеля существует довольно много. В Google можно с ходу найти решения на базе PuTTY или варианты для Windows 10. Мы своих пользователей любим (и свои нервы тоже). А значит, надо дать им такой инструмент, который не нужно настраивать и который будет работать надежно.
Иными словами, решение должно отвечать следующим требованиям:
- простота для пользователя;
- легкость поддержки;
- простая и понятная подготовка и настройка «серверных частей».
Решение будет основано на технологии RDP over SSH. Технически мы организуем это так:
- клиент SSH для организации туннеля с пробросом порта до целевого ПК подключения;
- автоматический старт RDP-сессии без дополнительного ввода параметров подключения.
Система должна быть легко встраиваемой, и должен быть клиент для продвинутых пользователей (опционально).
ГОТОВИМ СЕРВЕРНУЮ ЧАСТЬ
Простые вещи вроде настройки RDP или SSH-сервера мы рассматривать не будем. Инструкций в интернете имеется тьма, а у нас объем ограничен, да и перегружать статью не хочется. Также я не стану подробно рассказывать, как реализовать получение данных с Kerio Control: на странице проекта можно найти готовый код.
Первым делом нам нужно разрешить RDP-подключения на клиентских машинах в локальной сети. Если там реализовано централизованное управление типа Active Directory, тебе повезло. Разрешаем подключение к RDP в групповых политиках. Если нет, обходим рабочие места ногами и разрешаем на целевых локальных машинах RDP. Не забываем и о файрволах.
Вторым шагом нам понадобится доступный из интернета SSH-сервер. Технически подойдет любое решение. Я использовал VPS с Debian 10 (один мой знакомый поднимал такой сервак даже на роутере, что небезопасно). Дальше стоит разделить решения на несколько версий, конкретная реализация зависит от того, как организовано получение данных для авторизации пользователей.
Первоначально у нас использовался Kerio с авторизацией пользователей через AD.
Клиент подключался по SSH, пробрасывал порт на API Kerio Control Server, затем подключался к нему, выполнял поиск по заданным параметрам (логин или фамилия сотрудника), искал IP локального ПК. Далее разрывал SSH-соединение и устанавливал новое уже с пробросом порта на найденный IP, на порт RDP (3389), после чего штатными средствами Windows поднималась сессия RDP с передачей параметров подключения.
Такое решение работало довольно быстро, но нам этого стало мало, и мы разделили его на две части. Серверный скрипт стал работать на SSH-сервере и сам время от времени ходить в Kerio за информацией. Клиентская часть подключалась к серверу по SFTP, искала нужные данные, оформленные в JSON, и выполняла подключение. В итоге скорость работы увеличилась.
Рекомендую сразу настроить сервер OpenSSH с доступом по ключам и подготовить RSA-ключи для авторизации. Для этого нужно создать отдельного пользователя и ограничить его в правах, затем отдать ему публичную часть ключа. Ниже приведу часть /etc/ssh/sshd_config
с настройками этих двух пользователей:
Match User sftp PubkeyAuthentication yes # PasswordAuthentication yes ChrootDirectory /srv/sftp ForceCommand internal-sftp AllowTcpForwarding no Match User user1 X11Forwarding no ForceCommand /usr/bin/cmatrix # Подойдет и любая другая заглушка (можно заморочиться и отправлять пользователя в песочный bash) PasswordAuthentication yes
Первому пользователю SFTP разрешено подключаться только к этому самому SFTP. Второму — лишь для проброса портов. Если у тебя используется Kerio для получения данных о пользователях, Active Directory или еще что‑то централизованное, рекомендую завести отдельную учетку и ограничить ее в правах на всякий случай.
РЕАЛИЗАЦИЯ
Итак, мы приблизились к реализации намеченной цели. Писать будем все это дело на Python 3.8. Во‑первых, это мультиплатформенный язык, во‑вторых, собирается быстро и просто. В‑третьих, он легкий в освоении, в‑четвертых, включает огромное количество библиотек.
В проекте используются четыре библиотеки: sshtunnel, PyQt5, threading и (в нашем случае) API Kerio Control, но без него можно обойтись. На самом деле я уже все написал и протестировал, поэтому просто покажу, где какую строку нужно поправить, чтобы программа запустилась. Ты можешь скачать написанную мною программу с GitHub и посмотреть, как она устроена, а в статье я дам необходимые пояснения.
INFO
Разрабатываемая в текущий момент ветка называетсяdevelop
. Веткаmaster
реализована раньше, в этой версии подключение происходит на API Kerio и поиск IP по логину (фамилии пользователя) выполняется с помощью этого API. Функция поиска описана в файлеkerio/keriofunction.py
.
Библиотеки лучше устанавливать в отдельное окружение Python. Я собирал всю описываемую здесь конструкцию в Debian 10, это стоит учитывать.
Итак, создаем окружение с Python 3.8 и заходим в него.
virtualenv --python=3.8 tmp/venv/ source tmp/venv/bin/activate
Устанавливаем все необходимые библиотеки:
pip install PyQt5 rhreading, sshtunnel
Далее запускаем «Qt Дизайнер» и рисуем форму входа. В нашей библиотеке он доступен вот так:
tmp/venv/bin/pyqt5designer
Создаем новое окно, подготавливаем окно авторизации и сохраняем его. Результат показан на скриншоте ниже. Мы не меняли стандартные имена объектов форм, весь приведенный ниже код сохранил штатные названия объектов.
«QT Дизайнер» сохраняет файлы в формате .ui
. Конвертируем их в .py
.
pyuic5 name.ui -o name.py
python -m PyQt5.uic.pyuic -x [FILENAME].ui -o [FILENAME].py
В итоге получаем понятный для Рython файл дизайна desing.py
. Теперь о том, как устроена программа start.py
. Сначала подключаем все необходимые модули:
import sys, threading, time from PyQt5 import QtWidgets, uic from desing import Ui_MainWindow from PyQt5.QtCore import QCoreApplication
Далее идет стандартный класс отображения окна PyQt:
class mywindow(QtWidgets.QMainWindow): def __init__(self): super(mywindow, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.pushButton.clicked.connect(self.connectionstart) self.ui.statusbar.showMessage("Программа готова к работе") # Отображаем в статус-баре состояние программы
Затем следует несколько функций, которые будут разделены по разным потокам. Сделано это для того, чтобы не блокировать интерфейс программы во время выполнения кода этих функций. Основная функция connectionstart
запускается нажатием кнопки pushButton
.
Эта функция запускает в отдельном потоке функцию мониторинга состояния программы, для отображения его (состояния) в статус‑баре. Функция проверяет заполненность полей логина и пароля. Если одно из полей пустое, происходит возврат и программа останавливается. Подключается к серверу СУБД или к серверу с файлом JSON и передает введенную фамилию в качестве параметра для поиска IP-адреса.
def connectionstart(self): potok = threading.Thread (target=self.writelabelstatus, daemon=True) potok.start () from client import sshconnect sshconnect.login = self.ui.lineEdit.text () if sshconnect.login == '' or sshconnect.login is None: sshconnect.setstatus = "emptylogin" return sshconnect.password = self.ui.lineEdit_2.text () if sshconnect.password == '' or sshconnect.password is None: sshconnect.setstatus = "emptypassword" return if sshconnect.login != "emptylogin" or sshconnect.local is not None and sshconnect.password != "emptypassword" or sshconnect.password is not None: if sshconnect.setstatus == "ready": self.starttun()
Полный файл с кодом доступен по ссылке. Функция starttun
вызывает в отдельном потоке функцию sshconnect.connecttopc
. Отдельный поток используется для исключения блокировки модуля форм.
Далее обратимся к файлу sshconnect.py
. Остановимся только на строках с настройками, полный текст кода доступен на GitHub.
publicipadress = ('Public_IP', PORT) # Публичный IP-адрес и порт SSH-сервера
Это переменная публичного белого адреса и порта SSH-сервера. Порт может быть открытым или располагаться внутри локальной сети и пробрасываться через файрвол (DNAT). Обрати внимание на функцию sshtungetip
. Именно она получает IP-адрес из файла JSON.
Файл JSON доступен на SFTP-сервере в директории SFTP. Пример:
[ { "User": { "login": "Логин 1", "FullName": "Логин1 Иван", "ipaddress": { "ip": [ "192.168.0.2" ] } } }, { "User": { "login": "Логин2", "FullName": "Логин2 Степан Борисович", "ipaddress": { "ip": [ "192.168.0.3" ] } } }, ... ]
После передачи параметров подключения открываем соединение, подключаем библиотеку JSON, трансформируем файл в JSON-объект:
sftp = getipsftp.open_sftp() import json fip = None with sftp.file('ip-client.json', 'r') as f: dataip = json.load(f) getipsftp.close()
Дальше ищем в нем нужную информацию по заданному ключу и возвращаем адрес подключения в качестве параметра.
for x in dataip: if login == x["User"]["login"] or login in x["User"]["FullName"]: fip = x["User"]["ipaddress"]["ip"][0] fullname = x["User"]["login"] if fip == "Не найдено" or fip is None: fip = "IP не найден" time.sleep(1) return(fip)
Теперь рассмотрим функцию sshtungetip
. Укажем имя файла закрытой части ключа (ключ будет лежать в одном каталоге с программой).
pk = RSAKey.from_private_key_file('srv.key')
WARNING
Ключ srv.key
здесь приведен исключительно в ознакомительных целях. Категорически не рекомендую его использовать, иначе любой пользователь сможет подключиться к системе, в которой имеется такой ключ.
На последнем этапе в отдельном потоке вызывается функция rdpdataconnection
, которая передает в систему управления учетными данными Windows введенные в форму значения. Помимо указанных пользователем фамилии и пароля (фамилия используется в качестве логина), передается адрес домена (если требуется), адрес сервера (localhost), проброшенный порт (2222). После этого функция инициирует новый процесс mstsc
, которому передаются параметры подключения и адрес сервера.
subprocess.call("mstsc /v:localhost:2222")
Учетные данные берутся из системы управления учетными данными Windows. Через некоторое время вызывается функция, которая удаляет данные подключения.
ИТОГ
Мы получили клиент RDP over SSH, который устанавливает туннель SSH с пробросом порта до целевого локального ПК и поднимает «штатный» клиент RDP из комплекта поставки Windows. От пользователя требуется минимальное количество данных: только логин и пароль от его рабочего компа. Версию для продвинутых пользователей описывать детально смысла нет, вся разница — нет функций поиска IP и введенные данные передаются в качестве параметров подключения.
Если ключ скомпрометирован, мы можем заменить его новым и заново раздать пользователям. Можно реализовать резервное подключение по логину и паролю SFTP для получения файла ключа или передавать его через систему обновлений. Простор для творчества здесь ограничен только силой твоего воображения.
Один хакер может причинить столько же вреда, сколько 10 000 солдат! Подпишись на наш Телеграм канал, чтобы узнать первым, как выжить в цифровом кошмаре!