auth: вход по логину/паролю от веб-админки роутера + сессионные токены

- auth.py: NDM-аутентификация (md5+sha256 challenge) к http://ROUTER_HOST/auth
- Сессионные токены (secrets, TTL 8ч, скользящее окно), заголовок X-Session-Token
- WEB_PASSWORD остаётся как запасной мастер-пароль
- /api/login и /api/logout, форма входа в UI с полем «Логин»
- Шапка автора в UI: GitHub, Telegram, Boosty, Поддержать (Ozon СБП)
- README: NDM-флоу, переменные ROUTER_HOST/ROUTER_LOGIN/ROUTER_TIMEOUT
- install.sh копирует auth.py
This commit is contained in:
Андрей Бобырев
2026-04-26 00:19:09 +03:00
parent 7c7a58b3a9
commit 75cb432e13
6 changed files with 324 additions and 90 deletions

115
README.md
View File

@@ -1,42 +1,46 @@
# keenetic_ssh-web
Локальный веб-интерфейс на **Keenetic + Entware**: список **shell-команд** на роутере (как «RouterSync», но вместо URL — **команда**), **расписание**, ручной запуск **всех** или **одной**, просмотр **stdout/stderr**. Сервис слушает порт **2001** (по умолчанию).
> Локальная веб-панель для **Keenetic + Entware**: набор **shell-команд** на роутере (как «RouterSync», но вместо URL — **команда**), **расписание**, ручной запуск **всех** или **одной** команды, просмотр **stdout/stderr**. Работает на самом роутере, слушает порт **2001**. Вход — по **логину и паролю от веб-админки роутера**.
Репозиторий: [github.com/andrey271192/keenetic_ssh-web](https://github.com/andrey271192/keenetic_ssh-web)
---
<a id="white-ip-wan"></a>
## Что внутри
## Важно: доступ только с «белых» / доверенных IP
| Что | Что делает |
|---|---|
| **Список команд** | `ВКЛ` / `Распис.` / `Название` / `Команда (shell)` / `Примечание` / `Последний запуск` / `Действия`. |
| **Расписание** | Один общий интервал в минутах. Запускаются строки с `ВКЛ` **и** `Распис.` (`0` — только ручной режим). |
| **«Выполнить всё»** | Запускает все строки с `ВКЛ` (независимо от `Распис.`). |
| **«Вывод»** | Раскрывает полный **stdout + stderr** последнего запуска. |
| **Авторизация** | По логину/паролю **веб-админки роутера** (NDM-протокол). Запасной мастер-пароль `WEB_PASSWORD` — опционально. |
| **Сессии** | Сессионный токен на 8 часов, лежит в `sessionStorage` браузера. |
| **Шапка автора и донат** | GitHub, Telegram, Boosty, Ozon (СБП) — наверху страницы. |
| **Лог** | `/opt/var/log/keenetic-ssh-web.log`. |
Панель выполняет **произвольные команды в shell на самом роутере**. Размещайте её **только в доверенной сети** и **ограничьте WAN-доступ**.
- В `.env` задайте **`ALLOWED_IPS`** — список IP, с которых разрешены запросы (через запятую). Пустое значение = фильтр выключен (**не рекомендуется** на WAN).
- Дополнительно закройте порт **2001** на межсетевом экране Keenetic для всего интернета, кроме нужных адресов (как в справке Keenetic для облачного доступа: без прямого SSH с облака; здесь — **не публикуйте панель в открытый интернет** без allowlist).
**Пароль** `WEB_PASSWORD` обязателен: без него приложение не авторизует клиентов.
Команды выполняются **на том же хосте**, где работает Python (ваш Keenetic), с дополнением `PATH` для **Entware** (`/opt/bin`, `/opt/sbin` и т. д.).
---
## Возможности
<a id="white-ip-wan"></a>
| | |
|---|---|
| **ВКЛ** | Команда участвует в ручном «Выполнить всё» и в одиночном запуске. |
| **Распис.** | Плюс участие в фоне по **интервалу** (минуты; **0** = только ручной режим). |
| **Вывод** | Кнопка «Вывод» раскрывает полный текст последнего запуска (stdout + stderr). |
| **Полоска автора** | GitHub, Boosty, Ozon (СБП), Telegram — внизу страницы (username из `.env`). |
## Важно: доступ только из доверенной сети
Команды выполняются **на том же хосте**, где запущен Python (ваш Keenetic), с дополнением `PATH` для **Entware** (`/opt/bin` и т.д.).
Панель выполняет **произвольные команды в shell на роутере**. Размещайте её **только в доверенной сети**, не публикуйте в открытый интернет.
- В `.env` укажите **`ALLOWED_IPS`** — список IP клиентов через запятую. Пустое значение = фильтр выключен (**не рекомендуется** на WAN).
- Закройте порт **`2001`** на межсетевом экране Keenetic для всего интернета, кроме нужных адресов.
- Вход в любом случае требует пароль (от админки роутера или `WEB_PASSWORD`).
---
## Требования
- Keenetic с **Entware**
- `python3`, желательно пакет **`python3-venv`** (`opkg install python3-venv`)
- `python3`, желательно `python3-venv` (`opkg install python3-venv`)
- Свободный порт **2001** (или другой в `.env`)
- Доступная HTTP-админка Keenetic (по умолчанию `http://127.0.0.1` с самого роутера)
---
@@ -44,7 +48,7 @@
### Одной командой с GitHub (на роутере, Entware)
Нужны **`curl`** или **`wget`** и **`tar`** (обычно уже есть). Скрипт скачает архив ветки **`main`**, распакует во временный каталог и выполнит **`install.sh`** (копирование в `/opt/share/keenetic_ssh-web`, venv, init).
Нужны **`curl`** или **`wget`** и **`tar`**. Скрипт скачает архив ветки `main`, распакует во временный каталог и выполнит `install.sh` (копирование в `/opt/share/keenetic_ssh-web`, venv, init).
```sh
curl -fsSL https://raw.githubusercontent.com/andrey271192/keenetic_ssh-web/main/bootstrap.sh | sh
@@ -58,20 +62,11 @@ export KSSH_REPO=andrey271192/keenetic_ssh-web
curl -fsSL "https://raw.githubusercontent.com/${KSSH_REPO}/${KSSH_BRANCH}/bootstrap.sh" | sh
```
После установки задайте пароль и (по желанию) список IP:
```sh
nano /opt/share/keenetic_ssh-web/.env
/opt/etc/init.d/S99keenetic-ssh-web start
```
### Установка из уже скачанного каталога
Скопируйте репозиторий на роутер или клонируйте через `git`, затем:
```sh
cd /path/to/keenetic_ssh-web
chmod +x install.sh uninstall.sh run.sh
chmod +x install.sh uninstall.sh run.sh bootstrap.sh
./install.sh
```
@@ -80,21 +75,23 @@ chmod +x install.sh uninstall.sh run.sh
- копирует файлы в **`/opt/share/keenetic_ssh-web`**
- создаёт **`venv`**, ставит **Flask** и **Waitress**
- создаёт **`data/store.json`** и **`.env`** из примеров
- ставит **`/opt/etc/init.d/S99keenetic-ssh-web`**
- устанавливает **`/opt/etc/init.d/S99keenetic-ssh-web`**
Дальше:
### Первый запуск
```sh
nano /opt/share/keenetic_ssh-web/.env
# WEB_PASSWORD=...
# ROUTER_HOST=http://127.0.0.1 (если на роутере)
# ROUTER_LOGIN=admin
# WEB_PASSWORD= (запасной мастер-пароль; можно оставить пустым)
# PORT=2001
# ALLOWED_IPS=192.168.1.100
# ALLOWED_IPS=192.168.1.100 (по желанию)
# AUTHOR_TELEGRAM_USERNAME=Iot_andrey
/opt/etc/init.d/S99keenetic-ssh-web start
```
Откройте в браузере: `http://IP_РОУТЕРА:2001`
Откройте в браузере: `http://IP_РОУТЕРА:2001` — введите **логин и пароль от веб-админки роутера**.
**Автозапуск** (если у вашей сборки Entware есть `rc.d`):
@@ -114,10 +111,12 @@ chmod +x run.sh
./run.sh
```
Для разработки на ПК:
Для разработки на ПК (без роутера):
```sh
python3 -m venv venv && ./venv/bin/pip install -r requirements.txt
# на ПК `ROUTER_HOST` оставьте пустым и используйте мастер-пароль:
export ROUTER_HOST=
export WEB_PASSWORD=test
./venv/bin/python app.py
# или: ./venv/bin/python -m waitress --listen=127.0.0.1:2001 app:app
@@ -145,11 +144,7 @@ curl -fsSL https://raw.githubusercontent.com/andrey271192/keenetic_ssh-web/main/
cd /path/to/keenetic_ssh-web
chmod +x uninstall.sh
./uninstall.sh
```
Сохранить данные (`data/`, `.env`), но убрать сервис:
```sh
# Сохранить данные:
KEEP_DATA=1 ./uninstall.sh
```
@@ -157,21 +152,36 @@ KEEP_DATA=1 ./uninstall.sh
## Переменные `.env`
| Переменная | Описание |
|------------|----------|
| `WEB_PASSWORD` | Пароль входа (**обязательно** сменить). |
| `PORT` | Порт HTTP (по умолчанию **2001**). |
| `CMD_TIMEOUT` | Таймаут одной команды, сек (по умолчанию **300**). |
| `AUTHOR_TELEGRAM_USERNAME` | Username для ссылки t.me внизу страницы. |
| `ALLOWED_IPS` | Список разрешённых IP клиентов через запятую; пусто = без фильтра (осторожно). |
| Переменная | Описание | По умолчанию |
|---|---|---|
| `ROUTER_HOST` | HTTP-адрес админки Keenetic для проверки логина/пароля. На самом роутере — `http://127.0.0.1`. Пусто = выключить роутерную авторизацию. | `http://127.0.0.1` |
| `ROUTER_LOGIN` | Логин по умолчанию в форме входа. | `admin` |
| `ROUTER_TIMEOUT` | Таймаут запроса к `/auth` админки, сек. | `5` |
| `WEB_PASSWORD` | Запасной мастер-пароль. Если роутерная авторизация не сработала и пароль совпал — пустит. Можно оставить пустым. | пусто |
| `PORT` | Порт HTTP. | `2001` |
| `CMD_TIMEOUT` | Таймаут одной команды, сек. | `300` |
| `AUTHOR_TELEGRAM_USERNAME` | Username для ссылки `t.me` в шапке и снизу. | `Iot_andrey` |
| `ALLOWED_IPS` | Список разрешённых IP клиентов через запятую. Пусто = без фильтра. | пусто |
---
## Как устроена авторизация
1. Браузер → `POST /api/login` с `{login, password}`.
2. Сервер обращается по `ROUTER_HOST` к `/auth` админки Keenetic, читает `X-NDM-Realm` и `X-NDM-Challenge`, считает хеш `sha256(challenge + md5(login:realm:password))` и шлёт `POST /auth` на роутер.
3. Если роутер вернул `200 OK` — выдаём сессионный токен (`secrets.token_urlsafe(32)`), TTL **8 часов**, скользящее окно. Клиент кладёт его в `sessionStorage` и шлёт в заголовке `X-Session-Token`.
4. Если роутерная авторизация не сработала, но `WEB_PASSWORD` задан и совпал — тоже выдаём токен (мастер-пароль).
Никаких паролей в файлах не хранится. Токены живут в памяти процесса — рестарт сервиса = разлогин всех.
---
## Безопасность
- Это **не песочница**: любая команда — с правами пользователя, от которого запущен процесс (часто **root** на Entware). Не вставляйте непроверенный текст.
- Не выставляйте порт в интернет без **пароля + allowlist** или VPN.
- Резервная копия: файл **`/opt/share/keenetic_ssh-web/data/store.json`**.
- Это **не песочница**: команды выполняются с правами процесса (на Entware часто **root**). Не вставляйте непроверенный текст из чужих источников.
- Не выставляйте порт в интернет без `ALLOWED_IPS` или VPN.
- Резервная копия команд: `/opt/share/keenetic_ssh-web/data/store.json`.
- HTTP админки Keenetic на loopback (`http://127.0.0.1`) — это нормально для запросов с самого роутера. Между ПК и роутером используйте проводную/WiFi LAN или VPN.
---
@@ -180,6 +190,7 @@ KEEP_DATA=1 ./uninstall.sh
- **Boosty:** [boosty.to/andrey27/donate](https://boosty.to/andrey27/donate)
- **Ozon Bank (СБП):** [ссылка на оплату](https://finance.ozon.ru/apps/sbp/ozonbankpay/019dc200-2a5d-7931-a619-782d285f6798)
- **Telegram:** [@Iot_andrey](https://t.me/Iot_andrey)
- **GitHub:** [andrey271192](https://github.com/andrey271192)
Кнопка **Sponsor** на GitHub ведёт на варианты из `.github/FUNDING.yml`.
@@ -188,10 +199,10 @@ KEEP_DATA=1 ./uninstall.sh
## Связанные проекты
- [keenetic-unified](https://github.com/andrey271192/keenetic-unified) — мониторинг и управление с **VPS** по SSH (нужен «белый» IP на WAN для SSH).
- В **keenetic_ssh-web** всё выполняется **локально на роутере**; сценарий доступа другой, но ограничение **ALLOWED_IPS** + файрвол по-прежнему рекомендуется.
- В **keenetic_ssh-web** всё работает **локально на роутере** — белый IP не нужен; ограничение `ALLOWED_IPS` + файрвол по-прежнему рекомендуется.
---
## Лицензия
MIT
MIT — см. [LICENSE](LICENSE).