Hub stage 0: own users with roles, login limit, audit log, permission table

Users in HUB_DATA/users.json with scrypt hashes and roles (viewer, operator,
apps-admin, admin), managed by hub/users-cli.ts; HUB_USER/HUB_PASSWORD remain a
bootstrap admin while the file does not exist. Five failed logins in 15 minutes lock
the address or the name for 15 minutes. Logins, logouts and every refused request go
to a JSON Lines audit log that admins read through hub/audit/list. Endpoints are
listed in routes.ts with their kind and capability; writes stay refused for now.
Optional <ID>_ADMIN_TOKEN is read for the coming write stages. The management plan
is in docs/hub-management-plan.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
DenozordecandClaude Opus 5.5 committed 2026-10-31 15:25:38 +07:00
1 parent 042cb116ea
commit 0ef2d53a65
17 files changed
+1497 -79

No files matched your search

+50 -7
View File
@@ -4,7 +4,7 @@
- запрос «All servers» хаб отправляет на оба сервера и складывает ответы;
- запрос к одному серверу хаб пересылает только ему;
- запись хаб не выполняет никогда.
- запись хаб пока не выполняет: любой запрос на изменение получает отказ и попадает в журнал.
Серверы Technitium не меняются, кластер не нужен. В инструкции хаб ставится на хост, где уже работают Traefik и `technitium-smart` (`docs/compose.shnt.example.yaml`). Адрес хаба: `https://dnss.shnt.top`.
@@ -91,12 +91,12 @@ git tag hub-v1.0.0 && git push origin hub-v1.0.0
MDNS_TOKEN=вставьте_токен_mdns
NDNS_TOKEN=вставьте_токен_ndns
# Вход в сам хаб (это НЕ логин Technitium)
# Первый вход в хаб (это НЕ логин Technitium). Нужен, пока не создан файл пользователей (шаг 4a)
HUB_USER=admin
HUB_PASSWORD=длинный_случайный_пароль
```
Пароль хаба нужен длинный: у хаба нет ограничения числа попыток входа. Сгенерировать можно так:
Пароль лучше длинный. После 5 неудачных попыток за 15 минут вход блокируется на 15 минут, но это не замена хорошему паролю. Сгенерировать можно так:
```bash
openssl rand -base64 24
```
@@ -126,8 +126,12 @@ chmod 600 .env
- NDNS_TOKEN=${NDNS_TOKEN}
- HUB_USER=${HUB_USER}
- HUB_PASSWORD=${HUB_PASSWORD}
- HUB_DATA=/data
- HUB_TRUST_PROXY=1
- HUB_DOMAIN=dnss.shnt.top
- TZ=Asia/Novosibirsk
volumes:
- technitium-hub-data:/data
labels:
- "traefik.enable=true"
- "traefik.docker.network=dns-net"
@@ -138,6 +142,13 @@ chmod 600 .env
- "traefik.http.services.hub-svc.loadbalancer.server.port=8080"
```
Том `technitium-hub-data` нужно объявить в разделе `volumes:` того же файла:
```yaml
volumes:
technitium-hub-data:
```
В нём лежат файл пользователей `users.json` и журнал `audit.log`. `HUB_TRUST_PROXY=1` нужен за Traefik: хаб берёт адрес клиента из заголовка `X-Forwarded-For` для ограничения попыток входа и журнала. Если до хаба можно достучаться в обход Traefik, включать эту переменную нельзя: заголовок можно подделать.
Если хаб стоит в том же файле, что Traefik, отдельный блок `networks:` с `external: true` из примера не нужен: сеть `dns-net` в этом файле уже объявлена.
Про имена в `HUB_SERVERS`:
@@ -149,6 +160,32 @@ chmod 600 .env
---
## Шаг 4a. Пользователи хаба
У хаба свои пользователи с ролями. Они лежат в `/data/users.json`, пароли в виде хешей.
| Роль | Что может |
|---|---|
| `viewer` | смотреть |
| `operator` | смотреть, блокировка и кеш (после появления записи) |
| `apps-admin` | смотреть, управлять приложениями (после появления записи) |
| `admin` | всё, включая журнал действий |
Пока файла нет, в хаб входит один `admin` из `HUB_USER` / `HUB_PASSWORD`. Когда файл появился, эти две переменные игнорируются (в логе будет строка об этом). Если нет ни файла, ни переменных, хаб не запустится.
После запуска (шаг 5) создайте пользователей. Пароль команда спросит дважды, при вводе он не показывается:
```bash
docker exec -it technitium-hub node --experimental-strip-types /app/hub/users-cli.ts add denis admin
docker exec -it technitium-hub node --experimental-strip-types /app/hub/users-cli.ts add alice viewer
```
Остальные команды: `passwd <имя>`, `role <имя> <роль>`, `disable <имя>`, `enable <имя>`, `list`. Хаб замечает изменение файла на следующем запросе и завершает сессии затронутых пользователей. Перезапуск не нужен.
После создания своего `admin` уберите `HUB_USER` и `HUB_PASSWORD` из `.env` и compose, затем `docker compose up -d technitium-hub`.
Журнал: войдите админом и откройте `/api/hub/audit/list?limit=200` (JSON, новые записи первыми) или смотрите файл `docker exec technitium-hub tail -n 50 /data/audit.log`. Пароли и токены в журнал не пишутся.
---
## Шаг 5. Запуск
Если реестр закрытый, один раз войдите в него. Нужен токен Gitea с правом `read:package`:
@@ -218,7 +255,10 @@ docker compose rm -f technitium-hub # удалить контейнер
|---|---|---|
| `HUB_SERVERS` | обязательна | Серверы по порядку: `id=https://адрес,...` |
| `<ID>_TOKEN` | обязательна для каждого | API-токен сервера (`MDNS_TOKEN`, `NDNS_TOKEN`, ...) |
| `HUB_USER`, `HUB_PASSWORD` | обязательны | Вход в хаб |
| `HUB_USER`, `HUB_PASSWORD` | нужны, пока нет `users.json` | Первый `admin`; при наличии файла игнорируются |
| `HUB_DATA` | `/data` | Папка `users.json` и `audit.log` (на ней том) |
| `HUB_TRUST_PROXY` | выключено | `1`: адрес клиента из `X-Forwarded-For` (только за Traefik) |
| `<ID>_ADMIN_TOKEN` | нет | Токен записи сервера (`MDNS_ADMIN_TOKEN`). Пока не используется, в логи не пишется |
| `HUB_DOMAIN` | `hub` | Имя, которое консоль показывает как имя сервера |
| `HUB_CACHE_SECONDS` | `15` | Сколько секунд хранить ответы серверов (0 выключает кеш) |
| `HUB_TIMEOUT_MS` | `8000` | Сколько ждать ответа сервера, мс |
@@ -239,7 +279,10 @@ docker compose rm -f technitium-hub # удалить контейнер
| Контейнер сразу завершается | В `docker compose logs technitium-hub` будет названа недостающая переменная. Проверьте `.env` и что compose его видит (`docker compose config \| grep TOKEN`). |
| `https://dnss.shnt.top` не открывается, ошибка сертификата | Нет DNS-записи `dnss` (шаг 2) или Traefik ещё получает сертификат: смотрите `docker logs traefik`. |
| 404 от Traefik | Контейнер не в сети `dns-net` или опечатка в `Host(\`dnss.shnt.top\`)`. |
| Вход в хаб: «Invalid username or password» | Неверные `HUB_USER`/`HUB_PASSWORD` в `.env`. После правки `.env` нужен `docker compose up -d technitium-hub`. |
| Вход в хаб: «Invalid username or password» | Неверный логин или пароль. Если есть `users.json`, работают только его пользователи (`users-cli.ts list`), а `HUB_USER` игнорируется. После правки `.env` нужен `docker compose up -d technitium-hub`. |
| «Too many failed logins. Try again in N minutes.» | 5 неудач за 15 минут с одного адреса или на одно имя. Подождите; блокировка хранится в памяти, её снимает перезапуск контейнера. |
| Все пользователи блокируются разом | Не включён `HUB_TRUST_PROXY=1`: хаб видит только адрес Traefik. |
| Контейнер пишет `audit log is not writable` | Нет тома на `/data` или папка не принадлежит `node`. Журнал живёт в памяти (последние 1000 записей) до исправления. |
| В шапке «1 server not answering», в подсказке `ndns: its API token was refused` | Токен `NDNS_TOKEN` отозван или скопирован с ошибкой. Создайте новый (шаг 1.4) и перезапустите хаб. |
| В подсказке `... did not answer: ...` или `ECONNREFUSED` / `timeout` | Хаб не достучался до сервера. Проверьте с хоста хаба: `docker exec technitium-hub node -e "fetch('https://ndns.shnt.top/api/status').then(r=>console.log(r.status))"`. |
| В подсказке `ndns: ...` с сообщением об отказе в доступе | У группы `hub-readers` на этом сервере не хватает View в каком-то разделе (шаг 1.2). |
@@ -249,6 +292,6 @@ docker compose rm -f technitium-hub # удалить контейнер
## Безопасность
- Хаб только читает: любая запись получает отказ ещё в хабе, а токены выданы пользователю с одними правами View.
- Хаб только читает: любая запись получает отказ ещё в хабе (и запись в журнале), а токены выданы пользователю с одними правами View.
- Токены лежат только в `.env` на хосте и в памяти контейнера. В браузер они не попадают, в ответы и логи хаба не пишутся.
- У хаба один общий логин и нет ограничения попыток входа. Нужен длинный пароль, а если хаб не должен быть виден из интернета, добавьте к роутеру `hub` в Traefik `ipallowlist` или `basicAuth`.
- У каждого человека свой логин и роль. Вход защищён блокировкой после 5 неудач. Нужны длинные пароли, а если хаб не должен быть виден из интернета, добавьте к роутеру `hub` в Traefik `ipallowlist` или `basicAuth`.