Настройка webssh с ограничением сетевых подключений

Прежде чем начать

Приведённый вариант настройки хоть и имеет некоторую условную степень защиты от посторонних, тем не менее он не предполагает публичного использования, включая обычных пользователей, он рассматривается как запасной канал управления для домашнего сервера и только для него в случае, если штатные и надёжные каналы нет возможности использовать, например, если доступен только браузер в телефоне.
Хотя, для телефона есть другие варианты, тот же Termux, и телефон не может в полной мере являться доверенным устройством, тем не менее, пример есть пример.

Несмотря на то, что сам wssh-клиент может использовать one-time-password, основной защитой должен являться nginx с auth_basic и связкой логин-пароль для location /webssh/ которые я советую генерировать новые после использования старых. Так как они могут быть использованы для авторизации с оборудования к которому нет полного доверия.
Так, конечно же, делать ни в коем случае не надо.
Но если очень хочется, что-то включить/выключить/перезапустить, то можно.

Начнём

Задача, иметь возможность использовать ssh в условиях недоступности полноценной и защищённой рабочей среды (ноут, openvpn, ssh), а веб-интерфейс управления, или из существующих, или самописный всё равно не даёт всех возможностей интерактивной консоли.

Выбрана наиболее компактная и развиваемая реализация клиента на python.

Согласно документации, блок-схема связей работает следующим образом.

1
2
3
4
5
6
7
                                                             
 [xterm.js]           [python client]        [any ssh server]
                                                             
+---------+     http     +--------+    ssh    +-----------+
| browser | <==========> | webssh | <=======> | ssh server|
+---------+   websocket  +--------+    ssh    +-----------+
                                                             

В этом случае клиент wssh может подключиться к любому серверу, включая локальный, используя при подключении любой логин. Но цель ограничить работу по ssh только с локальным сервером, при этом авторизуясь пользователем с ограниченными возможностями доступа к внешней сети.

Блок-схема реализации

1
2
3
4
5
6
7
8
9
10
                           user:webssh
                           iptables:127.0.0.1<>127.0.0.1 only
                           sshd_config:AllowUsers webssh@127.0.0.1
                          +--------------------------------------+
                          |                                      |
+---------+     http      |  +--------+    ssh    +-----------+  |
| browser | <==========>  |  | webssh | <=======> | ssh server|  |
+---------+   websocket   |  +--------+    ssh    +-----------+  |
                          |                                      |
                          +--------------------------------------+

К делу

Устанавливаем пакеты

1
2
3
apt install python-setuptools python-pip
apt install libpython2.7-dev gcc libffi-dev python-wheel make build-essential
pip install webssh

Добавляем пользователя, вводим достаточный пароль.

1
2
groupadd --gid 29132 webssh
adduser --home /home/webssh --shell /bin/bash --ingroup webssh --uid 29132 webssh

Добавляем два правила в скрипт управления iptables или вам видней куда.

1
2
3
4
5
6
# ~~~~~~~~~~~~~~~~~~~~~~~ webssh allow only localhost connections ~~~~~~~~~~~~~~~~~~~~~
# connect to sshd on localhost | connect from nginx to wssh client
$I -A OUTPUT -s 127.0.0.1/32 -d 127.0.0.1/32 -m state --state NEW,RELATED,ESTABLISHED -m owner --uid-owner webssh -j ACCEPT
$I -A OUTPUT -m state --state NEW,RELATED,ESTABLISHED -m owner --uid-owner webssh -j DROP
# $I -A OUTPUT -m state --state NEW,RELATED,ESTABLISHED -m owner --uid-owner webssh -j ACCEPT
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ / webssh  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

В директиву AllowUsers в sshd_config добавляем разрешение для подключения пользователя webssh только с локальной системы.

Если у вас не была описана эта директива, то дополните её соответствующим образом, иначе потеряете связь с сервером!

Например:

1
2
AllowUsers root@192.168.1.* user@192.168.1.102 another@192.168.1.201 webssh@127.0.0.1
  

nginx <> wssh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
        location /webssh/  {
                    rewrite /webssh/(.*) /$1  break;
                    client_body_buffer_size 4k;
                    client_max_body_size 512k;
                    keepalive_timeout 240s;
                    keepalive_requests 100;
                    send_timeout 30s;                       # Время активности неактивной консоли, подключение будет сброшено при отсутствии активности.
                    client_body_timeout 30s;
                    more_set_headers "X-Frame-Options: SAMEORIGIN";
                    more_set_headers "X-Content-Type-Options: nosniff";
                    more_set_headers "X-XSS-Protection: \"1; mode=block\"";
                    more_set_headers "Strict-Transport-Security: \"max-age=31536000; includeSubdomains; preload\" always";
                    auth_basic "ACCESS";
                    auth_basic_user_file /etc/nginx/auth/domain-webssh.pwd;
                    # deny all;
                    proxy_intercept_errors off;
                    proxy_pass http://127.0.0.1:2222;
                    # webssh
                    proxy_http_version 1.1;
                    proxy_read_timeout 120;
                    proxy_set_header Upgrade $http_upgrade;     # http://nginx.org/ru/docs/http/websocket.html
                    proxy_set_header Connection "upgrade";      # ^
                    proxy_set_header Host $http_host;
                    proxy_set_header X-Real-IP $remote_addr;
                    proxy_set_header X-Real-PORT $remote_port;
                    proxy_set_header X-Forwarded-For $remote_addr;
                    proxy_redirect off;
                    proxy_set_header Proxy "";
        }

wssh backend

Кому как удобней запускать демона, здесь я использую собственные скрипты/вочдоги, набросок:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash

# wssh run script
# crontab
# */5 * * * * /usr/local/bin/run-webssh.sh > /dev/null 2>&1
# rc.local
# /usr/local/bin/run-webssh.sh > /dev/null 2>&1

EC="9"
ps aux | grep -Ev "grep|su" | grep -Eo "/usr/local/bin/wssh" ; EC="$?"

[[ "$EC" != "0" ]] && {
    WSPROC="`ps ax | grep "wssh" | grep -v "grep" | awk '{print $1}' | tr '\n' ' '`"
    kill -9 $WSPROC
    su - webssh -c "cd /home/webssh/ ; /usr/bin/python /usr/local/bin/wssh --address=127.0.0.1 --logging=info --log-file-prefix=webssh.log --port=2222 --encoding=utf-8 >/dev/null 2>&1"
    exit 0
}
exit 0

Таким образом, python-клиент wssh (который является backend сервером для nginx) запущен от пользователя webssh, которому запрещены любые соединения, кроме 127.0.0.1.

Этот самый wssh-клиент может подключиться к серверному ssh-демону используя логин webssh и заданный пароль, для доступа к странице может должен использоватся другой логин и другой пароль, заданный в auth_basic_user_file /etc/nginx/auth/domain-webssh.pwd.
Изящно?
Изящно.

Конечно, поверх этого можно соорудить дополнительные средства и механизмы, как для ограничения, так и для расширения полномочий пользователя в системе.

На закуску

Переписываем стили шрифтов

nano /usr/local/lib/python2.7/dist-packages/webssh/templates/index.html

1
2
3
4
5
6
7
8
9
      @font-face {font-family: "LiberationMono";font-style:normal;font-weight:normal;
                  src: url("/webssh/static/css/fonts/LiberationMono-Regular.eot");
                  src: url("/webssh/static/css/fonts/LiberationMono-Regular.eot?#iefix") format("embedded-opentype"), 
                       url("/webssh/static/css/fonts/LiberationMono-Regular.woff2") format("woff2"), 
                       url("/webssh/static/css/fonts/LiberationMono-Regular.woff") format("woff"), 
                       url("/webssh/static/css/fonts/LiberationMono-Regular.ttf") format("truetype"), 
                       url("/webssh/static/css/fonts/LiberationMono-Regular.otf") format("opentype"), 
                       url("/webssh/static/css/fonts/LiberationMono-Regular.svg") format("svg");font-display:swap;}
      body {font-family:'LiberationMono', monospace !important;}

Копируем набор шрифтов в
ls -la /usr/local/lib/python2.7/dist-packages/webssh/static/css/fonts/

1
2
3
4
5
6
7
8
9
10
total 364
drwxr-sr-x 2 root staff  4096 Sep 27 13:10 .
drwxr-sr-x 3 root staff  4096 Sep 26 23:02 ..
-rw-r--r-- 1 root staff     0 Sep 26 22:30 .gitignore
-rw-r--r-- 1 root staff 49031 May  1 22:39 LiberationMono-Regular.eot
-rw-r--r-- 1 root staff 97288 May  1 23:57 LiberationMono-Regular.otf
-rw-r--r-- 1 root staff 81738 May  1 23:57 LiberationMono-Regular.svg
-rw-r--r-- 1 root staff 55212 May  1 22:05 LiberationMono-Regular.ttf
-rw-r--r-- 1 root staff 40428 May  1 22:39 LiberationMono-Regular.woff
-rw-r--r-- 1 root staff 33992 May  1 22:39 LiberationMono-Regular.woff2