Использование утилит timeout & strace для мониторинга неактивности пользователя для разрыва соединен
- пятница, 7 июля 2017 г. в 03:11:57
Недавно я занимался тем, что исследовал какие существуют решения для реализации web-ssh прокси-сервера. Суть задачи заключается в том, чтобы дать пользователям возможность соединяться с произвольным ssh-сервером посредством web-интерфейса. Обычно, решения web-ssh предназначены для соединения с сервером, на котором они развернуты, но в рамках моей задачи мне хотелось, чтобы пользователь мог указать IP, порт, имя и пароль пользователя (или ключ) и выполнить соединение с произвольным сервером. С ходу найти подобного решения мне не удалось.
Вообще-то, конечно, есть Guacamole, но для моей задачи использование этого приложения было слишком затратным как по ресурсам разработки, так и по функциям и их организации, поэтому от Guacamole я отказался.
Однако, для открытого пакета shellinabox я обнаружил решение на блоге на немецком языке, которое я и решил довести до нужного мне уровня. В итоге, получился симпатичный контейнер Docker, который можно найти как на GitHub так и на Dockerhub, который решает все необходимые задачи.
Но, статья не об этом, а о сопутствующем коде на Python, который мне пришлось написать. Дело в том, что мне не нравилось, что если пользователь открыл web ssh и куда-то ушел, то сессия будет висеть бесконечно, что на мой взгляд неприемлемо. Это ведет к следующим отрицательным последствиям:
В общем, я решил, что хочу добиться того, чтобы shellinabox разрывал соединение в том случае, если пользователь несколько минут не пишет ничего на консоль (stdin) и на stdout не поступают данные.
Достаточно продолжительный поиск в Google показал мне, что цели можно добиться с использованием команды timeout и strace. Команда timeout оказалась для меня новой, ее назначение — обеспечить прерывание процесса по достижению некоторого таймаута в том случае, если он сам не завершился.
Команду strace я использую часто, однако, обычно применяю ее для того, чтобы отслеживать причину, по которой какая-то служба или команда не работает как ожидается. В рамках моего поиска на тему того, как осуществить мониторинг активности на каналах stdin, stdout процесса я обнаружил, что strace так же может это обеспечить:
strace -e write=1,2 -e trace=write -p <PID>
В общем, данные команды были тем, что мне было необходимо для осуществления задуманного. Общая схема выглядит так:
Для тех, кто хочет сразу посмотреть как же устроен весь скрипт, отправляю сюда. Для остальных далее по частям.
Кратко, код можно представить следующим образом.
monitor_daemon(inactivity_interval, identity_file)
...
...
os.execv("/usr/bin/ssh", ["/usr/bin/ssh"] + identity_args + ["-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-p", str(peer_port), "%s@%s" % (peer_login, peer_ip)])
Код простой и самоочевидный. Вся магия находится внутри функции monitor_daemon
:
def monitor_daemon(inactivity_interval, identity_file):
orig_pid = os.getpid()
try:
pid = os.fork()
if pid > 0:
return
except OSError as e:
print("Fork #1 failed: %d (%s)" % (e.errno, e.strerror))
sys.exit(1)
os.chdir("/")
os.setsid()
os.umask(0)
try:
pid = os.fork()
if pid > 0:
sys.exit(0)
except OSError as e:
print("Fork #2 failed: %d (%s)" % (e.errno, e.strerror))
sys.exit(1)
if identity_file != "":
time.sleep(1)
os.unlink(identity_file)
try:
while True:
proc = subprocess.Popen('timeout %d strace -e write=1,2 -e trace=write -p %d' % (inactivity_interval, orig_pid), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
proc.poll()
counter = 0
for line in proc.stderr.readlines():
counter += 1
if(counter <= 3):
os.kill(orig_pid, signal.SIGKILL)
sys.exit(0)
except Exception as e:
pass
sys.exit(0)
где нас более всего интересует часть:
while True:
proc = subprocess.Popen('timeout %d strace -e write=1,2 -e trace=write -p %d' % (inactivity_interval, orig_pid), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
proc.poll()
counter = 0
for line in proc.stderr.readlines():
counter += 1
if(counter <= 3):
os.kill(orig_pid, signal.SIGKILL)
sys.exit(0)
в которой и происходит в цикле запуск мониторинга, который после заданного периода ожидания выполняет завершение процесса ssh, если требуется.
Вот собственно и все. Решение оказалось достаточно простым, но потребовало некоторое время на изучение доступных инструментов.
PS: Я не являюсь профессиональным python-разработчиком, код субоптимален.
PPS: Если у кого-то возникнет желание улучшить код, добро пожаловать в репозиторий, я с удовольствием приму PR-ы.