Зачем подписывать удалённые команды в браузере: авторизация и границы доверия Fleetd

Как Fleetd v0.3.0 проверяет Ed25519, цели, сроки, повторы и локальные права; отзыв ключей и границы доверия к интерфейсу и терминалу.

Когда машина подключена к панели, хочется запускать команды прямо из браузера: проверить диск, перезапустить сервис, открыть терминал. Это удобно, но теперь панель может изменять состояние других машин. Сбой мониторинга и компрометация интерфейса выполнения команд имеют совсем разные последствия.

В предыдущей статье речь шла об исходящем подключении, одобрении устройств и их идентификации. Следующий вопрос: если агент уже доверяет серверу, зачем перед выполнением команды проверять ещё и подпись браузера? Ниже описана реализация Fleetd v0.3.0, включая её предпосылки: доверие к коду страницы и границы защиты открытого терминала.

Вход в панель не даёт неограниченных полномочий

В обычной схеме администратор входит в панель, сервер разрешает действие, а агент исполняет полученное сообщение. Полномочия сосредоточены на сервере.

Fleetd добавляет локальную проверку: разрешено ли это действие ключом, которому доверяет сама машина? Браузер формирует и подписывает запрос, сервер пересылает его, агент проверяет собственные настройки. Вход в систему и аутентификация соединения всё равно нужны: они решают другие задачи.

Подтверждение целей и команды → Подпись в браузере
→ Пересылка сервером → Проверки агента → Выполнение

Сообщение, пришедшее по аутентифицированному соединению, не получает право на выполнение автоматически.

Подписывается всё действие

Если подписать только слово uptime, останутся вопросы: на какой машине его можно выполнить, когда и можно ли повторно отправить тот же запрос? Подпись Fleetd охватывает следующие поля:

Поле Назначение
v Версия протокола
id Идентификатор разрешения и обнаружение повторов
kind Выполнение команды или открытие терминала
targets Отпечатки разрешённых целевых машин
argv или action Программа с аргументами либо локально заданное действие
issued_at, expires_at Временное окно приёма
timeout Тайм-аут выполнения с учётом ограничения агента

Текущая форма преобразует введённый текст в ['sh', '-c', текст]. Подписывается запрос с этими аргументами. Конвейеры и && работают потому, что оболочка запускается явно.

Браузер сериализует запрос в байты JSON и подписывает их Ed25519. Агент сначала проверяет именно полученные байты, затем разбирает JSON. Замена uptime на reboot или добавление цели делает прежнюю подпись недействительной. Если несколько машин указаны изначально, выполнение на каждой допустимо: привязка к целям ограничивает область разрешения, а не число устройств одним.

Агент сам оценивает разрешение

У каждого агента есть список trusted_signers. Приложенный к запросу открытый ключ должен уже находиться в этом списке. Иначе любой отправитель мог бы создать ключ и разрешить собственные команды.

Помимо подписи проверяются тип запроса, наличие собственного отпечатка среди целей, время и локальные права. Неизвестные поля JSON отклоняются, чтобы стороны не трактовали запрос по-разному.

Браузер создаёт запросы со сроком действия пять минут. Агент допускает две минуты расхождения часов и отклоняет заявленный интервал действия свыше десяти минут. Это окно приёма разрешения, а не максимальная длительность процесса: тайм-аут выполнения задаётся отдельно. Допуск часов также означает, что граница не проходит строго на пятой минуте. Обоим участникам нужны достаточно точные часы.

Вместе эти проверки определяют, применимо ли доверенное разрешение к данной машине сейчас.

Защита от повторов — не гарантия однократного эффекта

Правильно подписанный неизменённый запрос можно переслать снова. Браузер Fleetd создаёт ID из 16 случайных байтов; агент после успешной авторизации сохраняет его и отклоняет повторное использование.

Запись хранится в seen.json в каталоге состояния, по умолчанию /var/lib/fleetd. Если состояние сохранено, обычный перезапуск процесса не делает ещё действующие запросы пригодными для повторного исполнения. При ошибке сохранения выполнение не продолжается.

Но это не обеспечивает семантику «ровно один раз» для результата операции. ID записывается до проверки числа параллельных команд и запуска процесса. Все слоты могут оказаться заняты: ID уже израсходован, хотя процесс не стартовал. Возможна и обратная ситуация: команда успела изменить машину, а соединение оборвалось до получения результата панелью.

Повторное нажатие создаёт новый ID и новое разрешение. Агент не знает, восстанавливаете ли вы неудачную попытку или повторяете уже завершённое действие. Перед миграцией БД, изменением настроек или обновлением проверяйте реальное состояние и по возможности делайте операции идемпотентными. Потеря файла состояния и откат диска тоже отличаются от обычного перезапуска процесса.

Локальные права сохраняют силу

Уровень Что принимает агент
monitor Мониторинг, без команд и терминалов
allowlist Локально определённые действия с доверенной подписью
full Произвольные команды и терминалы с доверенной подписью

Машину только для наблюдения можно оставить в monitor. Для небольшого набора операций подходит локальный список разрешений. Например, на машине с systemd и установленным nginx:

permission: allowlist
allowlist:
  nginx-status: [systemctl, is-active, nginx]

Это фрагмент конфигурации прав: настройки подключения и доверенные открытые ключи также нужны. Подписанный запрос передаёт action: nginx-status, а программу и аргументы агент берёт из своей конфигурации. Отправитель не может дописать произвольные аргументы; сочетание action и argv тоже отклоняется.

В форме команд v0.3.0 нет выбора предустановленных действий: она отправляет команды оболочки. Ввод nginx-status в это поле не вызовет действие на машине с allowlist. Поддержка на уровне протокола пока шире возможностей интерфейса.

В текущей схеме развёртывания агент работает от root, и команды full получают соответствующие права. Подпись не распознаёт опечатки или разрушительные действия. Уже разрешённая root-команда может изменить конфигурацию самого агента. Локальная политика ограничивает неавторизованные запросы, а не власть уже авторизованного root-процесса.

У ключей браузера есть жизненный цикл

WebCrypto создаёт пару Ed25519 с неизвлекаемым закрытым ключом и сохраняет её в IndexedDB. Обычная регистрация отправляет серверу открытый ключ, но не закрытый.

После смены компьютера или очистки данных сайта вход может работать, а подписывать запросы знакомым агентам уже не получится. Новому браузеру нужен новый ключ, а агентам — доверие к его открытой части. Регистрация на сервере не обновляет существующие установки автоматически.

Отзыв также должен дойти до агентов. Удаление ключа из панели не отменяет доверие на уже настроенных машинах. Уберите его из их списков trusted_signers и перезапустите агенты подходящим для системы способом.

Ключ подписи релизов — отдельный инструмент. Подпись команды разрешает операцию, подпись релиза удостоверяет пакет обновления агента. Об обновлениях будет отдельная статья.

Две важные границы доверия

Сервер распространяет и код интерфейса

Неизвлекаемость означает, что обычный API WebCrypto не позволяет экспортировать материал закрытого ключа. Она не запрещает вредоносному коду того же origin вызвать операцию подписи.

Панель Fleetd загружается с сервера. После его компрометации браузер при следующем посещении может получить изменённый JavaScript. Такая страница способна показать одну команду, а подписать другую. Диалог подтверждения из той же страницы не является независимой проверкой её достоверности.

Агент отклоняет изменённые запросы без действительной подписи, но доверие к коду интерфейса остаётся предпосылкой. Отдельно распространяемый инструмент подписи мог бы уменьшить эту зависимость. CLI-подписант упомянут в проектном документе, но в v0.3.0 ещё не реализован.

Подписывается только открытие терминала

Для открытия проверяется запрос kind: shell с доверенной подписью и локальное право full. Последующие нажатия клавиш передаются в существующей сессии без отдельных подписей браузера. Скомпрометированный сервер может внедрить ввод в уже открытый терминал.

Открывайте интерактивные сессии по необходимости и закрывайте после работы. Явную разовую команду обычно проще проверить, чем длительный сеанс. Начальная подпись не обеспечивает такую же защиту каждого последующего ввода.

Подпись также не скрывает команды и вывод. Шифрование между агентом и сервером защищает сетевой путь, но сервер является конечной стороной соединения. Это не сквозная конфиденциальность между браузером и агентом, исключающая сервер.

Проверять нужно и отказы

Вывод успешно выполненной команды — наглядная демонстрация. Для авторизации не менее важны запросы, которые должны отклоняться. Существующие тесты проверяют изменённую нагрузку, недоверенный ключ, неверную цель, просроченное или слишком будущее время, недостаточные права и повтор ID после загрузки сохранённого состояния.

Они закрепляют отдельные правила и помогают замечать регрессии. Это не полный аудит безопасности и не автоматическая проверка вредоносного интерфейса, терминального ввода или всех вариантов развёртывания.

Для самостоятельного размещения важна объяснимая цепочка: каким ключам доверяет машина, какие цели разрешены, когда запрос действителен, что допускает локальная политика и как обрабатываются повторы. Интерфейс предустановленных действий, независимая подпись и защита терминалов остаются конкретными направлениями развития.

В следующей статье — о развёртывании одного Go-агента на обычном Linux-сервере и маршрутизаторе OpenWrt.

Обсуждение

Задайте вопрос или поделитесь опытом. Комментарии появляются после проверки. Адрес почты не публикуется. Конфиденциальность комментариев

Загрузка комментариев…