Налаштування 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