Мы развёртываем на серверы клиентов и не храним ни одного SSH-ключа
Ключи в папке — самая дорогая вещь в студии и самая незаметная. Разбор pull-модели: агент сам приходит за заданием, подписывает запросы Ed25519, приватная часть не покидает машину.
Если ваша студия ведёт больше трёх проектов с собственными серверами клиентов, у вас есть папка. В ней ключи, пароли от панелей хостеров и доступы к базам — чужая инфраструктура, за которую вы отвечаете, но которая вам не принадлежит.
Эта папка — самая дорогая вещь в компании и одновременно самая незаметная. Она не числится ни в одном реестре активов, её никто не оценивал, и план на случай её утечки обычно состоит из одного слова: «менять».
Арифметика, которую неприятно считать
Двадцать клиентских серверов. Уволился инженер — значит, сменить ключи на двадцати машинах, а заодно вспомнить, где ещё лежали его доступы: панель хостера, регистратор доменов, реестр образов, объектное хранилище.
Реалистичная оценка — три-четыре рабочих дня и почти гарантированно пропущенная машина. Не потому что кто-то небрежен, а потому что полного списка не существует ни у кого.
Разворот: пусть сервер приходит сам
Мы сделали так, что хранить нечего.
Панель не подключается к серверу. Агент на сервере сам обращается наружу за заданием и присылает результат. Входящих портов открывать не нужно, обратного канала нет вовсе.
Практическое следствие важнее удобства: компрометация панели не даёт выполнения команд на серверах клиентов. Это свойство схемы, а не обещание в договоре — команду просто некуда доставить.
Как агент доказывает, что он — это он
Раз агент приходит сам, вопрос доверия переворачивается: теперь платформа должна убедиться, что пришёл именно тот сервер.
При установке агент генерирует пару ключей Ed25519 и регистрируется одноразовым токеном, отдавая публичный ключ и его отпечаток. Приватный ключ не покидает машину клиента — его нет ни в базе платформы, ни у нас.
Дальше каждый запрос подписывается, и подписывается не тело, а связка из пяти частей: метод, путь, отпечаток ключа, метка времени и хэш тела.
Что закрывает каждая часть:
| Часть | От чего |
|---|---|
| метка времени | повтор перехваченного запроса |
| путь и метод | подстановка запроса в другую ручку |
| хэш тела | подмена любой части содержимого |
| отпечаток ключа | подмена отправителя |
Тело присутствует хэшем, а не целиком, — этого достаточно: изменение любого байта ломает подпись.
Грабля: текст ошибки — это тоже канал утечки
Когда выкат падает, агент отправляет причину: хвост вывода docker и git.
Это удобно — человек видит, что сломалось, не заходя на сервер.
В этом хвосте попадаются значения переменных окружения приложения. А история релизов видна любому участнику проекта, включая тех, у кого прав на управление развёртыванием нет.
Поэтому агент затирает известные ему значения секретов перед отправкой. Заплатка простая, вывод общий:
Любой диагностический вывод, который пересекает границу доверия, чистится на стороне отправителя. Не на стороне отображения — отображений со временем становится несколько.
Что унести, даже если вы нас никогда не увидите
Проверьте, где у вас лежат ключи от чужих машин, и посчитайте, сколько займёт их ротация при уходе одного человека. Число обычно неприятно удивляет.
Разверните направление, где можете. Всё, что умеет ходить наружу само — агент, раннер, воркер, — не требует хранить доступ к себе. Хранение доступа это всегда обязательство, а не удобство.
Чистите диагностику на отправителе. Хвост лога, трассировка, дамп конфигурации — всё это пересекает границу и всё это содержит больше, чем кажется.
У нас подключение сервера — одна команда, выполненная вами по SSH, и после неё у платформы нет ни ключа, ни пароля от машины. Пошагово — в руководстве Как подключить сервер, полное описание раздела — Деплой из Git.
Читайте также
- Редирект вёл на 0.0.0.0:3000, а локально всё работалоАбсолютный адрес строился от того, что процесс думает о себе. За nginx это дало адрес, которого не существует, — и ни одна проверка до выката этого не видела.
- Ссылка на сайт уходила в чат без картинки — из-за одной строки в robots.txtРазметка Open Graph на месте, страница открывается, у других превью есть. Разборщик Telegram не отдаёт приоритет более длинному Allow — и за картинкой не идёт.
Автор
Александр ЦапковОснователь Скоупворк
Веду платформу и её боевой контур сам: разработка, выкат, дежурство. Пишу о том, на чём мы обожглись, — с датами, замерами и ссылками на решения в репозитории.