Перейти к содержимому

Безопасность сервера ​

Сервер 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 отправляют токен с каждым запросом:

http
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) принимает имена через запятую, которые сравниваются без порта и без учёта регистра:

bash
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. Скрипты и curl Origin не отправляют и этого не касаются.
  • Команды принимают только Content-Type: application/json (иначе 415 с command.json_required), поэтому форма на другом сайте не может их отправить.
  • Сервер не отвечает на кросс-доменные (CORS) запросы.

Заголовки ответа ​

Каждый ответ несёт:

ЗаголовокЗначение
Content-Security-Policydefault-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-Optionsnosniff
X-Frame-OptionsDENY
Referrer-Policysame-origin
Cache-Controlno-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:

bash
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, если вы его указали, добавленные вами скриншоты и — в разделе Прикладывается автоматически — журнал консоли и Версия и система; каждое из них можно открыть перед отправкой и отключить. Имя этого компьютера, его адрес и ваши папки из них убраны. Из браузера форму отправляет сервер.