Безопасность сервера
Сервер Signal Lab отправляет настоящий трафик с той машины, на которой работает: OSC, UDP, HTTP, MQTT, штормы, сканирования, широковещательные рассылки. Тот, кто может им пользоваться, может делать всё это с этой машины, поэтому по умолчанию сервер закрыт и открывается только токеном.
WARNING
Относитесь к токену доступа как к паролю от сети этой машины. Тот, у кого он есть, может отправлять трафик с сервера на всё, до чего сервер дотягивается.
Коротко
- Без токена — только эта машина. Без токена сервер слушает loopback и отвечает только на имена хоста из loopback. На любом другом адресе он отказывается запускаться.
- Для всех остальных — токен. Браузеры входят один раз и получают cookie сессии; скрипты отправляют токен с каждым запросом.
- Только свои страницы. Запросы, которые что-то меняют, и WebSocket событий должны приходить с собственного источника (origin) сервера; команды принимают только JSON.
- Только свои имена. Имя хоста, на которое сервер не отвечает, отклоняется, что останавливает DNS rebinding.
- Секреты остаются внутри. Только для чтения, из окружения или файлов, никогда не возвращаются, маскируются везде, где могли бы показаться.
- Ничего сверх своей задачи. Он никогда не меняет брандмауэр хоста, не отдаёт файлов вне своей папки данных и не требует привилегий.
Без токена: только эта машина
Запущенный без токена, сервер слушает 127.0.0.1:1430 и не требует входа: это инструмент для человека за этой машиной. Чтобы веб-страница в любом браузере на этой машине не могла достучаться до него через имя, которое разрешается в 127.0.0.1 (DNS rebinding), он отвечает только на запросы, у которых Host — имя из loopback: localhost, имя с окончанием .localhost, 127.x.x.x или [::1], — либо имя, которое вы разрешили через --allowed-host.
Если ему велят слушать любой другой адрес без токена, он не запускается: сообщает причину и завершается с кодом 2.
Токен
Токен содержит не меньше 24 символов, без пробелов и переводов строки. signal-lab-server token печатает случайный токен из 64 шестнадцатеричных символов, а --generate-token (в образе включён) создаёт его при первом запуске, хранит в папке данных так, что читать его может только собственный пользователь сервера, и печатает один раз. Все способы задать токен — в разделе Токен доступа.
Сравнение токена занимает одно и то же время, где бы он ни отличался, а неверный токен стоит секунды ожидания и предупреждения в журнале: подбор идёт медленно и оставляет след.
Браузеры: сессии
Браузер, который не вошёл, перенаправляется на страницу входа. Его токен один раз обменивается на сессию, которая хранится в cookie со свойствами:
HttpOnly— ни один скрипт на странице не может её прочитать;SameSite=Strict— страница другого сайта не может заставить браузер её отправить;- действует 7 дней;
Secureс--secure-cookie, так что она передаётся только по HTTPS (включайте за прокси с HTTPS).
Сессии живут в памяти сервера: перезапуск выводит всех из системы, а кнопка Выйти завершает одну сразу. Хранится не больше 1024 сессий; самая старая вытесняется первой.
Скрипты: bearer-токен
Скрипт, signallab --server и CI отправляют токен с каждым запросом:
Authorization: Bearer <token>Каждой конечной точке нужен токен или сессия, кроме GET /api/health (отвечает ли сервер, его версия, требует ли он токен) и страницы входа. Запрос к API без них получает 401 с ошибкой auth.required; страница получает страницу входа.
Имена хоста
| Запуск сервера | На какие имена хоста он отвечает |
|---|---|
| без токена | имена loopback и имена из --allowed-host |
с токеном, без --allowed-host | на любые |
с токеном и --allowed-host | имена loopback и имена из --allowed-host |
--allowed-host (SIGNALLAB_ALLOWED_HOSTS) принимает имена через запятую, которые сравниваются без порта и без учёта регистра:
signal-lab-server --listen 0.0.0.0:1430 --token-file token.txt --allowed-host lab-pc.example.com,192.0.2.10Любой другой Host получает 403 с ошибкой auth.host. Задайте его на сервере, который доступен под известными именами, чтобы страница другого сайта не могла достучаться до него через собственное имя.
Источник и тип содержимого
- Каждый запрос, который что-то меняет (всё, кроме
GETиHEAD), и WebSocket событий должны либо не нестиOrigin, либо нести собственный источник сервера — тот же хост и порт, что и вHost. Страница другого сайта или страница, которая отправляетOrigin: null, получает403сauth.origin. Скрипты иcurlOriginне отправляют и этого не касаются. - Команды принимают только
Content-Type: application/json(иначе415сcommand.json_required), поэтому форма на другом сайте не может их отправить. - Сервер не отвечает на кросс-доменные (CORS) запросы.
Заголовки ответа
Каждый ответ несёт:
| Заголовок | Значение |
|---|---|
Content-Security-Policy | default-src 'self'; connect-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self' |
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
Referrer-Policy | same-origin |
Cache-Control | no-store для API и страницы входа |
Интерфейс загружает только то, что отдаёт сервер, ни с чем, кроме сервера, не общается и не может быть встроен во фрейм другой страницы.
Файлы и размеры
- Скачивание (
GET /api/files?path=…) обслуживается только изнутри папки данных, не больше 256 МиБ; всё остальное —404. - Тело запроса — не больше 24 МиБ.
Секреты
Значение секрета никогда не покидает движок:
- На сервере значения только для чтения: переменная окружения
SIGNALLAB_SECRET_<NAME>или файл<NAME>в папке секретов (по умолчанию/run/secrets/signallab). Задать или удалить секрет из браузера нельзя (secret.read_only), так что значение, введённое на странице, не окажется сохранённым где-то менее защищённо. См. Секреты. - Ни одна команда не возвращает значение; интерфейс узнаёт только, задано ли имя.
- Эксперименты называют секреты как
{{secret.NAME}}. Пока запуск или отправка ими пользуется, каждый текст, о котором он сообщает, — шаги, ошибки, отчёт о запуске, — показывает на их месте••••, а кадры в Инспектор маскируются байт за байтом. - Учётные данные HTTP-узла становятся заголовком
Authorizationтолько в момент отправки запроса; шаги, кадры и отчёты несут ответ, но никогда этот заголовок.
Что записывается в журнал
Журнал записывает с адресом клиента: каждую запущенную задачу — штормы, сканирования, широковещательные рассылки, мониторы, генераторы, запуски, эмуляторы, — каждый вход и каждый вход с неверным токеном. См. Журналы.
Чего сервер не делает никогда
- Не меняет брандмауэр хоста. Настольное приложение может добавить правило брандмауэра, когда вы его об этом просите; на сервере эта команда отклоняется (
firewall.server). Брандмауэр хоста принадлежит тому, кто управляет хостом. (Скрипт установки одной командой предлагает открыть порт сервера в ufw или firewalld и спрашивает об этом заранее — см. Брандмауэр хоста.) - Не запускается доступным без токена ни на каком адресе, кроме loopback.
- Не отдаёт файл из-за пределов своей папки данных.
- Не хранит секрет, введённый в браузере.
- Не говорит по TLS сам: поставьте перед ним прокси с HTTPS (см. За прокси с HTTPS).
Все ограничения движка — не больше 1024 хостов при обходе, не больше 50 000 пакетов в секунду от маяка — действуют на сервере так же, как в приложении. Это ограничители, а не разрешение: отправляйте трафик только на системы, которые принадлежат вам или которые вам разрешено тестировать.
Контейнер
Образ работает от имени непривилегированного пользователя (uid и gid 10001) и пишет только в /data. Он работает без изменений с корневой файловой системой только для чтения, без capabilities и с no-new-privileges — так его запускают скрипт установки и deploy/compose.yaml. Каждый образ публикуется с SBOM, происхождением сборки и подписанной аттестацией GitHub:
gh attestation verify oci://ghcr.io/proanima/signallab:1.0.0 -R ProAnima/SignalLabЧто Signal Lab отправляет во внешний мир
Помимо трафика, который отправляете вы, Signal Lab общается с двумя адресами, оба — студии.
Проверка обновлений
Обновления ищет только настольное приложение; сервер и браузер — никогда. Когда установлен флажок Проверять раз в день (в окне О программе), раз в день и всякий раз, когда вы нажимаете Проверить обновления, приложение обращается к узлу студии (hub.proanima.net) — а к последнему выпуску на GitHub лишь тогда, когда узел недоступен. В вопросе содержатся:
- версия приложения;
- операционная система и архитектура процессора;
- случайное число этой установки (
X-Install-Id), созданное один раз и хранящееся в настройках приложения, чтобы новый выпуск мог сначала дойти до части установок. Оно ничего не говорит ни о вас, ни о компьютере.
Предлагаются только опубликованные выпуски. Загрузка, подпись которой не совпадает с ключом, встроенным в приложение, не устанавливается, и ничто не устанавливается, пока вы не нажмёте Установить и перезапустить.
Обратная связь
Кнопка Написать разработчикам (значок ✉ в шапке, а также в окне О программе) отправляет сообщение разработчикам через узел студии, который пересылает его по почте; пароля для этого в приложении нет. Отправляется только то, что показывает форма: ваше сообщение, ваш e-mail, если вы его указали, добавленные вами скриншоты и — в разделе Прикладывается автоматически — журнал консоли и Версия и система; каждое из них можно открыть перед отправкой и отключить. Имя этого компьютера, его адрес и ваши папки из них убраны. Из браузера форму отправляет сервер.