Представьте приложение, которое отлично получает пароль от внешнего сервиса из OpenBao. На стенде всё работает: запрос ушёл, ответ пришёл, в репозитории пусто. Все довольны. Потом хранилище перезапускают, скажем, после обновления пакета или перезагрузки виртуальной машины, и выясняется, что приложение превратилось в вежливого просителя у закрытой двери. Оно стучится, получает отказ, перезапускается, снова стучится. А люди, которые могли бы эту дверь открыть, в этот момент выясняют, у кого вообще лежит нужная часть ключа.

Эта статья про то, как провести такой пилот на VPS заранее и не по сценарию «когда-нибудь разберёмся». Мы пройдём путь от разграничения прав до финальной проверки: приложение после остановки сервиса возвращается к работе и видит только свой секрет, а не всё хранилище.

Три права, которые легко спутать

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

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

Право запросить секрет — это уже не данные, а разрешение. Приложение должно как-то доказать хранилищу, кто оно, и получить в ответ ограниченные полномочия: прочитать конкретный путь и ничего больше. Здесь возникает вопрос, который удобно назвать «секретом нулевого уровня»: чтобы получить свои секреты, приложение само должно предъявить какое-то удостоверение. Если это удостоверение лежит рядом с кодом в открытом виде, мы просто переложили проблему на один шаг глубже. Поэтому у такого удостоверения должны быть узкие права и понятный способ замены.

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

Разница проявляется буквально в каждом решении. Если приложению выдали административные права «чтобы работало», то компрометация этого приложения равна компрометации всех секретов всех других систем. Если приложению выдали право читать ровно один путь, то в худшем случае утечёт один пароль, и его можно заменить. Это обычная логика наименьших привилегий, но в хранилище секретов цена ошибки особенно высока: оно по определению собирает в одном месте то, что остальные системы стараются разносить.

Пилот на VPS: что нужно и чего не нужно

Для пилота нужна виртуальная машина с root-доступом, где вы сами решаете, как запускать сервис, где лежат его данные и кто к ним ходит. На VPS MAATRIX доступны NVMe-диски, процессоры AMD EPYC и root-доступ; в публичном предложении перечислены локации UK, US, RU и FR, а виртуальная машина создаётся автоматически через пару минут после оплаты. Для проверки схемы хватит самой небольшой конфигурации из линейки, например Ba (1 vCPU, 2 ГБ RAM, 40 ГБ NVMe), а для уверенной работы с несколькими приложениями можно подняться на Uab или выше. Это ориентиры, а не обещание ёмкости: сколько запросов выдержит ваше хранилище, покажет только нагрузка ваших приложений, и замерять нужно самим.

Куда важнее другое. Хранилище секретов — сервис, чья задача не быстро отвечать, а надёжно отказывать посторонним. Поэтому в пилоте стоит заранее решить несколько вещей, не относящихся к производительности:

  • На этой машине не должно быть случайных соседей: тестовых баз, экспериментальных сайтов, чужих панелей управления. Чем меньше посторонних процессов, тем проще объяснить, кто и зачем туда ходит.
  • Доступ к API хранилища должен идти по защищённому соединению и быть ограничен сетевыми правилами: только адреса ваших приложений и администраторов.
  • Административные действия делаются с рабочего места человека, а не с машины приложения.

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

Выдача доступа одному приложению

Возьмём условный пример: сервис выставления счетов, назовём его invoice-worker. Ему нужен один секрет — пароль к платёжному шлюзу. Больше ничего из хранилища ему видеть не положено. Пусть в хранилище уже лежит несколько других секретов: ключ почтового провайдера, токен CRM, пароли для служебных скриптов. Задача пилота — убедиться, что воркер читает свой пароль, а остальное для него просто не существует.

Схема выдачи состоит из четырёх решений, и каждое имеет смысл проговорить отдельно.

Где лежит секрет

Путь к секрету стоит выбирать так, чтобы по нему было понятно, чей он. Например, отдельная ветка для каждого приложения: всё, что принадлежит воркеру счетов, лежит под его именем. Тогда правило доступа формулируется одной строкой: читать можно только эту ветку. Если же секреты свалены в общую кучу по принципу «как придётся», любое правило превращается в перечисление исключений, а они, как известно, имеют свойство забываться.

Какое правило доступа получает приложение

Правило должно разрешать минимум: чтение одного пути. Не список всех путей, не запись, не создание новых правил. Хороший тест: прочитайте правило вслух и спросите себя, что случится, если им воспользуется злоумышленник. Если ответ звучит как «получит один пароль к платёжному шлюзу», правило написано правильно. Если ответ начинается со слов «ну, в принципе, он сможет…», его надо сужать.

Как приложение доказывает, кто оно

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

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

Если удостоверение приложения живёт месяцами и лежит в файле, доступном всем пользователям машины, то ограничение правилами сильно обесценивается. Права на файл, владелец процесса, отдельная системная учётная запись для воркера — всё это скучная, но необходимая часть общей картины.

Как приложение ведёт себя, когда хранилище недоступно

Здесь история из начала статьи и начинается по-настоящему. Приложение, которое запрашивает секрет только при старте, при перезапуске хранилища либо упадёт, либо останется без секрета. Приложение, которое запрашивает секрет перед каждой операцией, будет падать при каждой операции. Нужен осознанный выбор: что делать воркеру, если хранилище временно не отвечает.

Варианты поведения приложения при недоступном хранилище
ПодходЧто происходит при остановке хранилищаЧто нужно учесть
Читать секрет только при стартеРаботающий процесс продолжает жить со старым значением; новый запуск невозможенЗамена секрета потребует перезапуска или отдельного сигнала на перечитывание
Читать перед каждой операциейОперации останавливаются сразуХранилище становится критичной зависимостью каждого действия
Держать значение в памяти с ограниченным сроком и обновлять по таймеруПроцесс работает до истечения срока, затем ждёт возвращения хранилищаСрок должен быть короче, чем вы готовы терпеть старое значение, но длиннее ожидаемого простоя

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

Что на самом деле значит «sealed»

Теперь о том, что происходит с самим хранилищем после перезапуска. Согласно документации OpenBao о seal и unseal, при старте сервер находится в состоянии sealed и не может выполнять обычные операции с секретами, пока его не разблокируют. Unseal открывает доступ к корневому ключу, с помощью которого расшифровывается keyring.

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

Практический вывод для пилота: проверка живости должна различать sealed и unsealed. Иначе приложение будет получать ошибки, а дежурный видеть зелёный индикатор, что гарантирует долгий и малопродуктивный разговор.

Эта же особенность объясняет, почему перезапуск хранилища — не то же самое, что перезапуск обычного сервиса. Обычный сервис, поднявшись, продолжает работу. Хранилище после подъёма ждёт, пока кто-то или что-то предъявит ему материал для разблокировки. И вот тут возникает главный вопрос всей статьи: кто и как это делает.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.

АРЕНДОВАТЬ СЕРВЕР

Выбор процедуры разблокировки

По умолчанию используется Shamir seal: ключ для разблокировки разделяется на доли, и для unseal нужно предъявить определённое их количество. Идея понятна: ни один человек в одиночку не может открыть хранилище, а потеря одной доли не катастрофична. Число долей и порог задаются при инициализации; выбирать их стоит исходя из размера команды, а не из желания выглядеть серьёзно.

Разберём несколько условных конфигураций.

Условные схемы долей ключа для небольшой команды
СхемаКак работаетСлабое место
Много долей при низком порогеРазблокировать может любой из нескольких участников почти в одиночкуКомпрометация двух человек открывает всё; защита чисто декоративная
Порог равен общему числу долейНужны все держатели без исключенияОтпуск или болезнь одного блокируют восстановление
Порог меньше числа долей с запасомДостаточно большинства; потеря одной доли не критичнаТребует дисциплины: нужно знать, кто держит доли и как с ними связаться

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

Теперь про автоматизацию. Руки, вводящие доли после каждой перезагрузки, — это надёжно, но неудобно: ночной перезапуск превращается в побудку для нескольких людей. Поэтому возникает соблазн настроить автоматическую разблокировку. Здесь нужна трезвость. Автоматический unseal — не флажок, который включается и всё само начинает работать. Он зависит от поддерживаемого механизма, который нужно выбрать, настроить и, главное, проверить на реальном перезапуске. Точный список поддерживаемых вариантов и порядок их настройки определяется документацией вашей версии OpenBao, и до тех пор, пока такой механизм не настроен и не испытан, правильно считать, что после каждой остановки хранилище ждёт ручной разблокировки.

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

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

Репетиция остановки

Репетиция занимает полчаса и экономит много нервов. Порядок такой:

  1. Убедитесь, что приложение читает свой секрет и работает.
  2. Остановите и снова запустите сервис хранилища.
  3. Проверьте, что оно находится в sealed и что приложение получает отказ. Это нужно увидеть собственными глазами: тогда вы знаете, как выглядит симптом.
  4. Посмотрите, как приложение переживает отказ: пишет понятную ошибку, ждёт и повторяет с паузами, или в панике перезапускается каждую секунду.
  5. Проведите разблокировку по той процедуре, которую считаете рабочей, и засеките время от «хранилище упало» до «приложение снова получает секрет».
  6. Убедитесь, что приложение вернулось без ручного вмешательства: перезапускать его вручную не пришлось.

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

Копия данных и материалы для восстановления: две разные корзины

Теперь коснёмся распространённой путаницы: резервная копия хранилища и материалы для разблокировки — не одно и то же, и хранить их вместе нельзя.

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

Отсюда два правила, которые на практике нарушаются удивительно часто:

  • Доли ключа не должны лежать на том же сервере, что и данные хранилища. Иначе тот, кто получил доступ к машине, получил и замок, и ключи. Разделение на доли теряет смысл.
  • Копия данных не должна лежать только на том же сервере. Если диск или виртуальная машина исчезнут, исчезнет и копия. Хранить её нужно отдельно, на другом носителе или в другом месте, и периодически проверять, что из неё можно что-то восстановить.

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

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

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

Замена секрета и прекращение использования старого значения

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

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

  1. Выпустить новое значение на стороне внешней системы. Например, создать новый пароль у платёжного шлюза. Старый пока продолжает работать: если отозвать его сразу, воркер упадёт до того, как узнает о новом.
  2. Записать новое значение в хранилище по пути приложения. Хранилище теперь содержит актуальное значение, но воркер об этом может ещё не знать.
  3. Дождаться, пока приложение подхватит новое значение. Тут и срабатывает выбранная ранее схема чтения: при чтении только на старте нужен перезапуск, при чтении с обновлением по таймеру нужно подождать срок обновления.
  4. Убедиться, что приложение работает с новым значением. Лучше смотреть на реальный признак: успешные обращения к шлюзу уже под новым паролем, а не только факт записи в хранилище.
  5. Отозвать старое значение на стороне внешней системы. Только теперь старый пароль действительно перестаёт существовать. Запись в хранилище его не отменяет, это нужно сделать там, где он принимается.

Последний пункт — самый легко забываемый. Хранилище хранит значение, но само по себе не может запретить стороннему сервису принимать старый пароль. Пока вы не отозвали его у владельца, старое значение остаётся живым, где бы оно ни осело: в журнале, в чьей-то копии, в ответе, полученном до замены. Именно поэтому порядок «сначала новое, потом отзыв старого» — не вопрос педантизма, а единственный способ не уронить приложение и не оставить дыру.

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

Родственная тема — секреты, которые вообще не живут долго: временные пароли к базам данных, выдаваемые на ограниченный срок. Если вам интересно, как это устроено для PostgreSQL, продолжение логики описано в материале OpenBao на VPS: временные пароли PostgreSQL для приложения. Там замена перестаёт быть событием и становится свойством самой схемы выдачи.

Финал репетиции: воркер возвращается, а ключей от всего у него нет

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

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

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

Если вы хотите провести такой пилот на отдельной машине, подойдёт VPS с root-доступом, где вы сами управляете сервисом, сетевыми правилами и расположением данных. Начните с минимального тарифа, а когда станет ясно, сколько приложений и запросов реально нужно обслуживать, масштабируйте по результатам собственных замеров, а не по таблице ресурсов.