SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-22

Выбор self-hosted менеджера секретов для одного VPS

Сравнение OpenBao, Infisical, SOPS с age и systemd credentials для одного сервера. Узнайте, какой инструмент подходит для автоматизации и сколько ресурсов он потребляет.

Чем self-hosted менеджер секретов отличается от менеджера паролей

Self-hosted менеджер секретов передает учетные данные процессам. Менеджер паролей передает их людям. Все остальные различия вытекают из этого факта. Менеджер паролей разблокируется человеком, который присутствует и выполняет действие. Менеджер секретов должен предоставить приложению пароль от базы данных в 03:00, когда никого нет на месте.

Режимы сбоев различаются критическим образом. Заблокированный менеджер паролей — это неудобство: вы просто вводите мастер-пароль еще раз. Заблокированный (sealed) менеджер секретов — это простой: каждый сервис, который перезапускается в этот момент, не получает учетные данные и остается нерабочим. Запуск Vaultwarden в качестве собственного менеджера паролей хорошо решает задачу для человека. Он не решает задачу для машины, и он изначально не был для этого предназначен. Если вы используете его параллельно, стоит уделить внимание защите административного токена и файла резервной копии, а не содержимого хранилища, которое клиент и так шифрует. Укрепление безопасности Vaultwarden охватывает оба этих аспекта.

Реалистичные варианты для одного сервера делятся на две группы. OpenBao и Infisical — это полноценные сервисы: API, база данных, TLS (transport layer security), этап авторизации и процесс, который теперь нужно поддерживать в рабочем состоянии. SOPS с использованием age, systemd credentials и Docker secrets — это файлы: они зашифрованы в состоянии покоя, расшифровываются уже запущенным процессом и не требуют дополнительного мониторинга.

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

Достаточно ли прав доступа 600 для файла с переменными окружения?

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

sudo install -d -m 750 -o root -g myapp /etc/myapp
sudo install -m 640 -o root -g myapp /dev/null /etc/myapp/env
sudoedit /etc/myapp/env

Проверьте это с обеих сторон:

sudo -u myapp cat /etc/myapp/env
sudo -u nobody cat /etc/myapp/env

Первая команда выводит содержимое файла. Вторая выводит cat: /etc/myapp/env: Permission denied, так как nobody не входит в группу myapp, а у файла отсутствуют права для остальных пользователей. Это и есть вся модель безопасности, и она вполне реальна.

Утечка происходит на следующем этапе. Юнит systemd с параметром EnvironmentFile= копирует эти значения в переменные окружения процесса, а переменные окружения процесса доступны для чтения.

[Service]
User=myapp
EnvironmentFile=/etc/myapp/env
ExecStart=/usr/local/bin/myapp
sudo cat /proc/$(pgrep -n myapp)/environ | tr '\0' '\n'

Эта команда выводит ваши секреты в открытом виде, так как файл /proc/<pid>/environ доступен для чтения пользователю root и пользователю, от имени которого запущен процесс. Система сбора отчетов об ошибках, которая прикрепляет окружение к отчету, увидит то же самое. Как и любая утилита, запущенная под той же учетной записью. Именно поэтому исключение секретов из AI-агентов начинается с их удаления из переменных окружения. Используйте этот файл в связке с выделенным сервисным пользователем с низкими привилегиями, чтобы «пользователь, от имени которого запущен процесс» не был root.

Использование SOPS с age: зашифрованные секреты в репозитории git

SOPS (secrets operations) шифрует значения в файлах YAML или JSON, оставляя ключи в открытом виде. age — это компактный инструмент шифрования, который использует одну пару ключей и не требует сервера ключей. Их совместное использование позволяет хранить secrets.enc.yaml рядом с кодом, а git diff по-прежнему показывает, какая настройка изменилась, не раскрывая её нового значения.

age входит в состав Ubuntu 24.04. SOPS в репозиториях отсутствует, поэтому загрузите .deb со страницы релизов. Версия 3.13.3 была актуальна на август 2026 года.

sudo apt update && sudo apt install -y age
curl -LO https://github.com/getsops/sops/releases/download/v3.13.3/sops_3.13.3_amd64.deb
sudo apt install -y ./sops_3.13.3_amd64.deb
sops --version

Создайте пару ключей. age-keygen сохраняет закрытый ключ в файл и выводит открытый ключ, поэтому вы увидите строку, начинающуюся с Public key: age1....

mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
chmod 600 ~/.config/sops/age/keys.txt
age-keygen -y ~/.config/sops/age/keys.txt

Поместите открытый ключ в .sops.yaml в корне репозитория, чтобы не указывать получателя в командной строке каждый раз.

creation_rules:
  - age: age1s3cqcks5genc6ru8chl0hkkd04zmxvczsvdxq99ekffe4gmvjpzsedk23c
sops encrypt secrets.yaml > secrets.enc.yaml
sops decrypt secrets.enc.yaml

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

Во время выполнения передавайте значения только одному процессу:

sops exec-env secrets.enc.yaml './myapp'

sops exec-env выполняет расшифровку в оперативной памяти и устанавливает значения в окружении дочернего процесса, поэтому открытый текст не записывается на диск. Ограничение окружения из предыдущего раздела по-прежнему применимо к этому дочернему процессу.

Две проблемы часто вызывают затруднения. Ошибка Failed to get the data key required to decrypt the SOPS file при работе под управлением systemd почти всегда означает, что SOPS искал файл в неверном домашнем каталоге, так как юнит не наследует вашу переменную HOME. Явно укажите путь с помощью Environment=SOPS_AGE_KEY_FILE=/etc/sops/age.txt в юните. Кроме того, редактирование .sops.yaml не перешифровывает существующие данные: добавление открытого ключа коллеги влияет только на новые файлы, поэтому для каждого существующего файла необходимо запустить sops updatekeys secrets.enc.yaml. Если ваша конфигурация уже управляется через Ansible, шифрование тех же значений с помощью Ansible Vault позволяет достичь того же результата без использования дополнительного инструмента.

Учетные данные systemd: секреты, которые не попадают в переменные окружения

В Ubuntu 24.04 поставляется systemd 255, поэтому установка не требуется. systemd-creds шифрует секрет на хосте, а systemd расшифровывает его в приватную директорию, доступную только конкретному сервису.

sudo systemd-creds setup
sudo install -d -m 700 /etc/myapp
echo -n 'hunter2' | sudo systemd-creds encrypt --name=db_password - /etc/myapp/db_password.cred
[Service]
User=myapp
LoadCredentialEncrypted=db_password:/etc/myapp/db_password.cred
ExecStart=/usr/local/bin/myapp

Сервис считывает значение из файла с именем db_password внутри директории, указанной в $CREDENTIALS_DIRECTORY. Значение не попадает в переменные окружения, поэтому /proc/<pid>/environ не показывает ничего полезного, а открытый текст никогда не записывается на корневую файловую систему.

Перед тем как указывать путь к файлу в юните, убедитесь, что он расшифровывается:

sudo systemd-creds decrypt /etc/myapp/db_password.cred -

Знайте, какой ключ использовался для шифрования, так как от этого зависит пригодность вашей резервной копии. По умолчанию --with-key=auto использует чип TPM2 (trusted platform module version 2), если он присутствует и доступен, в противном случае используется ключ хоста. На большинстве VPS-инстансов TPM2 отсутствует.

systemd-analyze has-tpm2

no означает, что был использован ключ хоста, который находится в /var/lib/systemd/credential.secret и доступен для чтения только пользователю root. Если восстановить db_password.cred на новый VPS без этого файла, расшифровка станет невозможной. Скопируйте credential.secret в ту же резервную копию или сохраните открытый текст в доступном вам месте.

Docker secrets: файлы в /run/secrets

Compose считывает файл с хоста и монтирует его в контейнер по пути /run/secrets/<name>.

services:
  app:
    image: myapp:latest
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
secrets:
  db_password:
    file: ./db_password.txt

cat /run/secrets/db_password

docker compose exec app cat /run/secrets/db_password
docker compose exec app env | grep -i password

env | grep DB_PASSWORD_FILE=/run/secrets/db_password

Первая команда выводит секрет. Вторая выводит только DB_PASSWORD_FILE=/run/secrets/db_password, в этом и заключается смысл: значение переменной никогда не попадает в окружение контейнера, поэтому оно не отображается в выводе docker inspect. Многие официальные образы уже рассчитаны на такой подход, и образ Postgres считывает POSTGRES_PASSWORD_FILE именно таким образом.

Важно понимать, что это такое. Вне режима Swarm шифрование на каком-либо уровне отсутствует: ./db_password.txt — это обычный текстовый файл на хосте, и единственная его защита — это права доступа и владелец. Устанавливайте их самостоятельно, так как Compose без предупреждений смонтирует файл, доступный для чтения всем пользователям. Подробное сравнение преимуществ и недостатков этого метода по сравнению с простым использованием env_file приведено в руководстве по файлам окружения и секретам в Compose.

Реальная стоимость эксплуатации OpenBao и Vault

OpenBao — это форк HashiCorp Vault от Linux Foundation, созданный после того, как HashiCorp изменила лицензию Vault на Business Source License в 2023 году. OpenBao остается под лицензией MPL 2.0 (Mozilla Public License). Релиз 2.6.2 был актуален по состоянию на август 2026 года. Почти всё нижесказанное применимо и к Vault, так как форк сохранил тот же набор команд.

docker pull docker.io/openbao/openbao

Пакеты для Debian и Ubuntu доступны на странице загрузки OpenBao, если вы предпочитаете, чтобы apt управлял обновлениями. Серверу требуется конфигурационный файл, содержащий настройки listener и storage backend:

listener "tcp" {
  address       = "127.0.0.1:8200"
  tls_cert_file = "/path/to/full-chain.pem"
  tls_key_file  = "/path/to/private-key.pem"
}

storage "raft" {
  path = "/path/to/raft/data"
  node_id = "raft_node_1"
}

Затем запустите его один раз:

bao operator init

По умолчанию система разделяет root key на 5 частей и требует 3 из них для снятия блокировки (unseal), что определяется флагами -key-shares и -key-threshold. Система выводит эти части и начальный root token только один раз, и больше они не отображаются.

Теперь о том, что пропускает большинство сравнений. Перезагруженный сервер — это заблокированный сервер. OpenBao хранит root key только в оперативной памяти, поэтому после перезапуска он не может расшифровать собственное хранилище, пока кто-то не предоставит пороговое количество частей ключа. Таким образом, обновление ядра или завершение процесса из-за нехватки памяти (OOM kill) приводит к блокировке сервера и невозможности входа для приложений.

На VPS, обслуживаемом одним человеком, разделение по методу Шамира ничего не защищает, так как все пять частей оказываются в одном менеджере паролей, принадлежащем одному и тому же лицу. Функция auto unseal переносит ключ на доверенное устройство или сервис. В крупном облаке это означает использование управляемого сервиса ключей, а на вашем VPS — обычно файл ключа, лежащий на том же диске, что и защищаемые им данные. Это реальное снижение уровня безопасности в обмен на то, что сервер самостоятельно восстанавливает работу после перезагрузки. Идите на этот компромисс осознанно и зафиксируйте, какой вариант вы выбрали.

Infisical: веб-интерфейс, база данных и мастер-ключ под вашим контролем

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

curl -o docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
docker compose -f docker-compose.prod.yml up -d

Перед выполнением последней команды отредактируйте .env. Два значения должны быть уникальными, а одно из них нельзя изменять после запуска:

openssl rand -hex 16
openssl rand -base64 32

Первое — это ENCRYPTION_KEY, 16-байтовая шестнадцатеричная строка. Это ключ, которым шифруются секреты в PostgreSQL. Его потеря превратит резервную копию базы данных в бесполезный набор зашифрованных данных, а изменение на работающем экземпляре сделает невозможной расшифровку существующих секретов. Второе — AUTH_SECRET, 32-байтовая строка в формате base64, используемая для сессий. SITE_URL должен содержать абсолютный URL, по которому сервис будет доступен, включая протокол, иначе перенаправление при входе в систему работать не будет.

Infisical подходит лучше, чем OpenBao, если ваша основная задача — работа с пользователями: веб-интерфейс для небольшой команды и разделение сред, а не динамические учетные данные для баз данных с ограниченным сроком действия. Для работы потребуются PostgreSQL, Redis и TLS-сертификат, которые теперь вам придется самостоятельно обновлять и резервировать.

Что происходит, когда сервис секретов недоступен, а ваше приложение перезапускается

Этот вопрос определяет, стоит ли размещать сервис секретов на одном узле с приложением. Файлы доступны для чтения до запуска сети. Сервис — нет.

При перезагрузке сервера ваше приложение и OpenBao запускаются одновременно. Приложение запрашивает пароль от базы данных, OpenBao всё ещё запечатан (sealed), запрос завершается ошибкой, и systemd перезапускает приложение в цикле, пока администратор не введет ключи для распечатки (unseal shares). Ничего не сломано. Но и ничего не работает.

Есть два честных способа решения этой проблемы. Настройте порядок запуска юнитов и добавьте в приложение механизм повторных попыток: After= сервис секретов, а также Restart=on-failure и RestartSec= с интервалом, достаточным для того, чтобы не перегружать API запросами. Либо получайте секреты во время деплоя, а не при загрузке: сохраняйте секрет в файл с правами 600 или используйте systemd credentials, чтобы работающая система зависела от файла, а не от API.

Истечение срока действия токена — та же проблема, но с более медленным таймером. Токены и лизы (leases) OpenBao имеют время жизни (TTL), поэтому долго работающий процесс, который никогда не обновляет токен, теряет доступ в момент, не связанный с деплоем. Этот сбой сбивает с толку именно потому, что в этот день ничего не менялось.

Резервное копирование самого хранилища

У каждого варианта есть ключ, и резервная копия без этого ключа бесполезна. Запишите, где хранится ваш ключ.

Если вы используете файл переменных окружения, то сам файл является секретом, поэтому резервная копия должна быть зашифрована. В случае с SOPS зашифрованный файл можно разместить в любом публичном месте, но закрытый ключ age, расположенный в ~/.config/sops/age/keys.txt, — это то, что нельзя терять. Для учетных данных systemd создавайте резервную копию /var/lib/systemd/credential.secret вместе с файлами .cred. Для Infisical сделайте дамп PostgreSQL и храните ENCRYPTION_KEY отдельно от него.

OpenBao с хранилищем raft создает собственные снимки состояния:

bao operator raft snapshot save backup.snap
bao operator raft snapshot restore backup.snap

Снимок содержит ваше зашифрованное хранилище, поэтому для восстановления на новом сервере всё равно потребуются ключи для снятия блокировки (unseal shares) из bao operator init. Ежедневное задание, которое копирует снимки в объектное хранилище, пока ключи нигде не сохранены, — это резервная копия, которая ничего не восстановит. Протестируйте восстановление на временном VPS, прежде чем полагаться на него в работе.

Аудит доступа: кто читал секреты

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

OpenBao регистрирует каждый запрос в устройстве аудита, которое вы активируете явным образом:

bao audit enable file file_path=/var/log/openbao_audit.log

Два факта об этих логах влияют на эксплуатацию сервера. Большинство строк в запросах и ответах хешируются с помощью HMAC-SHA256 с использованием соли. Это позволяет сопоставить известное вам значение с записью в логе, не сохраняя при этом открытый текст в самом журнале. Целые числа и логические значения записываются в открытом виде, поэтому числовые секреты не защищены таким хешированием.

Важный эксплуатационный нюанс: OpenBao перестает отвечать на запросы, если ни одно из включенных устройств аудита не может их записать. Если устройство выходит из строя в блокирующем режиме, запросы будут зависать до тех пор, пока проблема не будет устранена. Переполнение диска по пути /var/log приведет к остановке API секретов по замыслу разработчиков. Выделите для логов аудита отдельное пространство и настройте правило logrotate в первый же день, не дожидаясь первого сбоя.

Какой self-hosted менеджер секретов выбрать?

Оцените количество машин и пользователей, затем сделайте выбор.

  1. Одна машина, один пользователь: используйте файл с правами 600, принадлежащий root и доступный для чтения пользователю сервиса. Добавьте systemd credentials, если хотите исключить значение из переменных окружения процесса.
  2. Одна машина, от двух до пяти пользователей, конфигурация уже в git: используйте SOPS с age. У каждого пользователя должна быть пара ключей, а в .sops.yaml перечисляются все публичные ключи, которым разрешена расшифровка.
  3. Несколько машин, один репозиторий конфигурации, нет необходимости в учетных данных с ограниченным сроком действия: по-прежнему SOPS с age, с одним ключом получателя на каждый хост. В этом случае скомпрометированный ключ хоста позволит расшифровать только файлы этого конкретного хоста.
  4. Несколько машин и несколько команд, которым действительно нужны учетные данные базы данных с ограниченным временем жизни, а также журнал аудита: используйте OpenBao и заложите в бюджет один час в месяц на работу оператора по разблокировке (unsealing) и тренировки по восстановлению.

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

FAQ

Стоит ли использовать self-hosted менеджер секретов для одного VPS?

Обычно нет, если речь идет о таких сервисах, как OpenBao или Infisical. На одном сервере с одним или двумя пользователями файл с правами 600 или зашифрованные учетные данные systemd обеспечивают ту же защиту от других локальных пользователей, не требуя процедуры unseal и обслуживания дополнительного сервиса. Менеджер секретов становится оправданным, когда у вас появляется несколько машин, несколько пользователей или реальная потребность в учетных данных, которые должны истекать автоматически без ручной ротации.

В чем разница между менеджером паролей и менеджером секретов?

Менеджер паролей хранит учетные данные, которые вводит человек, и разблокируется пользователем в его присутствии. Менеджер секретов предоставляет учетные данные процессам, поэтому он должен работать в 03:00 без участия человека. Из этого вытекает главное отличие: заблокированный менеджер паролей требует ввода мастер-пароля, а заблокированный (sealed) менеджер секретов останавливает работу всех сервисов, которые пытаются перезапуститься в этот момент.

Что произойдет с моими приложениями, если OpenBao заблокируется после перезагрузки?

Они не смогут получить свои секреты и не запустятся, а systemd будет перезапускать их в цикле, пока кто-то не предоставит порог для разблокировки (по умолчанию это 3 из 5 ключей). OpenBao хранит мастер-ключ только в оперативной памяти, поэтому каждая перезагрузка снова блокирует сервис. Либо включите auto unseal, приняв тот факт, что на одном VPS ключ разблокировки будет лежать на том же диске, что и данные, либо записывайте секреты в файл во время развертывания, чтобы загрузка системы не зависела от API.

Можно ли коммитить файлы, зашифрованные через SOPS, в публичный репозиторий?

Значения зашифрованы, поэтому они в безопасности от любого, у кого нет приватного ключа age. Сами ключи не зашифрованы: читатель может увидеть, что у вас есть STRIPE_SECRET_KEY и SMTP_PASSWORD, а также как часто каждый из них меняется. Эти метаданные приемлемы для большинства проектов, но недопустимы для некоторых. Храните приватный ключ age вне репозитория и запускайте sops updatekeys для каждого существующего файла при добавлении или удалении получателя.