- PVSM.RU - https://www.pvsm.ru -

Использование утилит timeout & strace для мониторинга неактивности пользователя для разрыва соединения Shellinabox

Недавно я занимался тем, что исследовал какие существуют решения для реализации web-ssh прокси-сервера. Суть задачи заключается в том, чтобы дать пользователям возможность соединяться с произвольным ssh-сервером посредством web-интерфейса. Обычно, решения web-ssh предназначены для соединения с сервером, на котором они развернуты, но в рамках моей задачи мне хотелось, чтобы пользователь мог указать IP, порт, имя и пароль пользователя (или ключ) и выполнить соединение с произвольным сервером. С ходу найти подобного решения мне не удалось.

Вообще-то, конечно, есть Guacamole, но для моей задачи использование этого приложения было слишком затратным как по ресурсам разработки, так и по функциям и их организации, поэтому от Guacamole я отказался.

Однако, для открытого пакета shellinabox я обнаружил решение на блоге [1] на немецком языке, которое я и решил довести до нужного мне уровня. В итоге, получился симпатичный контейнер Docker, который можно найти как на GitHub [2] так и на Dockerhub [3], который решает все необходимые задачи.

Но, статья не об этом, а о сопутствующем коде на Python, который мне пришлось написать. Дело в том, что мне не нравилось, что если пользователь открыл web ssh и куда-то ушел, то сессия будет висеть бесконечно, что на мой взгляд неприемлемо. Это ведет к следующим отрицательным последствиям:

  1. ресурсы прокси-сервера терминалов используются неэффективно, поскольку он вынужден обслуживать неиспользуемые сессии;
  2. открытое в браузере без присмотра окно с ssh навевает чувство тоски и уязвимости.

В общем, я решил, что хочу добиться того, чтобы shellinabox разрывал соединение в том случае, если пользователь несколько минут не пишет ничего на консоль (stdin) и на stdout не поступают данные.

Достаточно продолжительный поиск в Google показал мне, что цели можно добиться с использованием команды timeout и strace. Команда timeout оказалась для меня новой, ее назначение — обеспечить прерывание процесса по достижению некоторого таймаута в том случае, если он сам не завершился.

Команду strace я использую часто, однако, обычно применяю ее для того, чтобы отслеживать причину, по которой какая-то служба или команда не работает как ожидается. В рамках моего поиска на тему того, как осуществить мониторинг активности на каналах stdin, stdout процесса я обнаружил, что strace так же может это обеспечить:

strace -e write=1,2 -e trace=write -p <PID>

В общем, данные команды были тем, что мне было необходимо для осуществления задуманного. Общая схема выглядит так:

  1. При открытии нового соединения shellinabox запускает python-скрипт;
  2. Python-скрипт запускает (fork) процесс-демон мониторинга активности процесса, который осуществляет мониторинг своего же PID;
  3. python-скрипт выполняет (exec) ssh (замена себя на ssh).

Для тех, кто хочет сразу посмотреть как же устроен весь скрипт, отправляю сюда [4]. Для остальных далее по частям.

Кратко, код можно представить следующим образом.

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: Если у кого-то возникнет желание улучшить код, добро пожаловать в репозиторий [5], я с удовольствием приму PR-ы.

Автор: Иван Кудрявцев

Источник [6]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/ssh/259754

Ссылки в тексте:

[1] блоге: https://tools.bartlweb.net/webssh/

[2] GitHub: https://github.com/bwsw/webshell

[3] Dockerhub: https://hub.docker.com/r/bwsw/webshell/

[4] сюда: https://github.com/bwsw/webshell/blob/master/shellinabox.py

[5] репозиторий: https://github.com/bwsw/webshell/blob/master/Dockerfile

[6] Источник: https://habrahabr.ru/post/332544/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best