Развёртывание OpenBao на VPS: управление секретами, TLS, AppRole и резервное копирование
TL;DR
В этом руководстве мы развернём OpenBao на Ubuntu 24.04 LTS, включим хранилище Raft, защитим веб-интерфейс и API через HTTPS, создадим политику и роль AppRole для приложения, а затем настроим автоматические зашифрованные резервные копии снимков Raft.
- OpenBao будет работать как systemd-сервис с отдельным системным пользователем.
- Данные будут храниться в встроенном Raft Storage без отдельной базы данных.
- Публичный доступ к API будет организован через Caddy и TLS-сертификат Let’s Encrypt.
- Приложения будут получать секреты через AppRole с минимально необходимыми правами.
- Снимки хранилища будут регулярно выгружаться во внешний S3-совместимый бакет через restic.
- Все операции проверяются командами OpenBao CLI, curl и systemctl.
1. TL;DR
В этом руководстве мы развернём OpenBao на Ubuntu 24.04 LTS, включим хранилище Raft, защитим веб-интерфейс и API через HTTPS, создадим политику и роль AppRole для приложения, а затем настроим автоматические зашифрованные резервные копии снимков Raft.
- OpenBao будет работать как systemd-сервис с отдельным системным пользователем.
- Данные будут храниться во встроенном Raft Storage без отдельной базы данных.
- Публичный доступ к API будет организован через Caddy и TLS-сертификат Let’s Encrypt.
- Приложения будут получать секреты через AppRole с минимально необходимыми правами.
- Снимки хранилища будут регулярно выгружаться во внешний S3-совместимый бакет через restic.
- Все операции проверяются командами OpenBao CLI, curl и systemctl.
2. Содержание
Статья рассчитана на администратора, который разворачивает собственное хранилище секретов для небольшого SaaS, CI/CD, внутренних сервисов или домашней инфраструктуры. Команды приведены для Ubuntu 24.04 LTS и архитектуры amd64.
В примере используется один узел OpenBao. Это не кластер высокой доступности: при отказе VPS сервис будет недоступен до восстановления машины, но данные можно восстановить из снимка Raft. Для production-критичной инфраструктуры в конце статьи рассмотрены варианты масштабирования.
3. Что мы настраиваем и зачем
Проблема с секретами в конфигурационных файлах
Пароли баз данных, API-токены, ключи облачных провайдеров и сертификаты часто попадают в Git, Docker Compose, переменные CI или резервные копии серверов. Даже если секрет удалить из последнего коммита, он останется в истории репозитория. Дополнительная проблема — отсутствие аудита: обычно непонятно, какое приложение, когда и какой секрет прочитало.
OpenBao решает эту задачу как централизованное хранилище секретов с контролем доступа. Секреты хранятся внутри storage backend, а клиент сначала проходит аутентификацию, после чего получает только разрешённые пути. OpenBao совместим с привычной моделью API и CLI Vault, но развивается как независимый проект с открытой лицензией.
Архитектура нашего примера
| Компонент | Назначение | Адрес |
|---|---|---|
| OpenBao | Хранилище секретов и API | 127.0.0.1:8200 |
| Raft Storage | Хранение данных и журнала консенсуса | /opt/openbao/data |
| Caddy | HTTPS, сертификат, reverse proxy | https://bao.example.com |
| AppRole | Аутентификация машин и приложений | role_id и secret_id |
| restic | Шифрование и отправка резервных копий | S3-совместимое хранилище |
Что получится в итоге
После выполнения инструкции будет создан один OpenBao-узел с KV v2 secrets engine. В него можно записывать, например, database/password или production/api-key. Приложение будет входить через AppRole, получать короткоживущий токен и читать только нужный путь.
Снаружи будет доступен только TCP-порт 443. Порт OpenBao 8200 останется закрытым для внешней сети, поскольку API будет доступен через Caddy локально. SSH разрешается только с административного IP либо через VPN.
Self-hosted или cloud-managed
Управляемые сервисы секретов удобны: провайдер отвечает за обновления, отказоустойчивость и часть резервного копирования. Их недостатки — стоимость, зависимость от внешней платформы, ограничения по региону и необходимость доверять поставщику критически важную информацию.
Self-hosted OpenBao на VPS подходит, если требуется контролировать расположение данных, использовать собственные процедуры восстановления или сократить постоянные расходы. Взамен администратор сам отвечает за патчи, TLS, мониторинг, резервные копии и процедуру восстановления. Один VPS нельзя считать заменой полноценному отказоустойчивому кластеру.
Что важно знать о seal и unseal
При инициализации OpenBao создаёт root token и ключи unseal. В production нельзя хранить их в файле на сервере. Для лабораторного примера используется классическая схема Shamir с пятью ключами и порогом три: для распечатывания нужны любые три ключа.
Потеря root token не уничтожает данные, но усложняет администрирование. Потеря достаточного количества unseal-ключей делает расшифровку хранилища невозможной. Ключи необходимо распределить между доверенными администраторами и хранить офлайн, например в менеджере паролей и в зашифрованном физическом резерве.
4. Какой VPS-конфиг нужен под эту задачу
OpenBao не требует большого количества CPU, если количество запросов небольшое. Главные требования — стабильный диск, достаточный объём оперативной памяти и регулярные снимки. Для одного узла важнее надёжность хранения и наличие удалённого backup, чем десятки виртуальных ядер.
| Сценарий | CPU | RAM | Диск | Сеть |
|---|---|---|---|---|
| Лаборатория | 1 vCPU | 1 ГБ | 20 ГБ SSD | 100 Мбит/с |
| Небольшая production-инсталляция | 2 vCPU | 2–4 ГБ | 40–80 ГБ SSD/NVMe | 100–1000 Мбит/с |
| Несколько команд и CI/CD | 4 vCPU | 8 ГБ | 100 ГБ NVMe | 1 Гбит/с |
Практичный базовый вариант — 2 vCPU, 4 ГБ RAM, 60 ГБ SSD, публичный IPv4 или доступный IPv6, приватная сеть при наличии нескольких узлов и ежедневные snapshots диска. Для этой конфигурации можно взять подходящий VPS с SSD, статическим IP и возможностью подключить внешний S3-бэкап.
20 ГБ диска достаточно только для небольшого количества секретов и журналов. Сам OpenBao хранит секреты в компактном виде, но рабочий каталог Raft, системные логи и временные файлы всё равно требуют запаса. Не следует использовать переполненный диск: при нехватке места запись в Raft может остановиться.
Когда нужен dedicated
Выделенный сервер оправдан, если OpenBao обслуживает тысячи приложений, хранит большой объём динамических секретов, работает вместе с несколькими тяжёлыми сервисами или требует предсказуемой производительности диска. Dedicated также полезен при строгих требованиях к физической изоляции и локальному контролю оборудования.
Для одного небольшого экземпляра OpenBao dedicated обычно избыточен. Более рационально потратить бюджет на три VPS в разных зонах, внешнее резервное хранилище, мониторинг и тестирование восстановления. Важно не путать выделенный сервер с кластером: один dedicated остаётся одной точкой отказа.
Влияние локации
Локация влияет на задержку между приложениями и API OpenBao, требования к локализации данных и время восстановления. Размещайте OpenBao ближе к приложениям, которые обращаются к нему при старте или регулярно получают короткоживущие токены. Резервный S3-бакет желательно держать в другом отказоустойчивом регионе.
Если используются три узла Raft, задержка между ними должна быть небольшой и стабильной. Для кластера лучше выбирать одну региональную площадку с несколькими зонами доступности, а не растягивать консенсус через континенты.
5. Подготовка сервера
Ниже предполагается чистый Ubuntu Server 24.04 LTS с доступом по SSH под пользователем, созданным провайдером. Замените 203.0.113.10, admin и bao.example.com на свои значения. Все команды выполняются от административного пользователя с правами sudo.
Создание администратора и SSH-ключа
Если у вас уже есть отдельный sudo-пользователь, этот шаг можно пропустить. Не отключайте текущую SSH-сессию, пока не проверите новый вход в отдельном окне.
# Создаём отдельного пользователя для администрирования
sudo adduser ops
# Добавляем пользователя в группу sudo
sudo usermod -aG sudo ops
# Создаём каталог для публичного ключа
sudo install -d -m 700 -o ops -g ops /home/ops/.ssh
# Копируем ключ текущего пользователя в новый аккаунт
sudo cp ~/.ssh/authorized_keys /home/ops/.ssh/authorized_keys
# Исправляем владельца и права файла ключей
sudo chown ops:ops /home/ops/.ssh/authorized_keys
sudo chmod 600 /home/ops/.ssh/authorized_keys
Проверьте вход в новом терминале:
# Подключаемся с локального компьютера и проверяем sudo
ssh [email protected]
sudo -v
whoami
Обновление системы и базовые утилиты
# Обновляем индексы пакетов и устанавливаем исправления
sudo apt update
sudo DEBIAN_FRONTEND=noninteractive apt full-upgrade -y
# Устанавливаем инструменты для установки и диагностики
sudo apt install -y curl unzip jq ca-certificates gnupg lsb-release \
ufw fail2ban chrony unattended-upgrades restic awscli
# Перезагружаем сервер, если обновилось ядро
sudo reboot
После перезагрузки снова подключитесь по SSH. Включите автоматическую установку security-обновлений. Перезапуск OpenBao после обновления должен выполняться в заранее выбранное окно обслуживания.
# Включаем автоматические обновления безопасности Ubuntu
sudo dpkg-reconfigure -plow unattended-upgrades
# Проверяем синхронизацию времени
timedatectl status
chronyc tracking
Настройка SSH
Отключайте парольный вход только после проверки авторизации ключом. Для публичного сервера также запрещается вход root. Порт SSH можно изменить, но это не заменяет ключевую аутентификацию и firewall.
# Создаём отдельный drop-in для sshd
sudo tee /etc/ssh/sshd_config.d/ hardening.conf > /dev/null <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
AllowUsers ops
EOF
# Проверяем синтаксис конфигурации SSH
sudo sshd -t
# Применяем настройки без завершения текущих сессий
sudo systemctl reload ssh
Firewall и fail2ban
Откройте SSH только с вашего административного IP, если он статичен. Если адрес меняется, разрешите SSH из доверенной сети или используйте VPN. Порт 8200 намеренно не открывается: Caddy будет обращаться к OpenBao локально.
# Разрешаем SSH только с административного IPv4
sudo ufw allow from 198.51.100.25 to any port 22 proto tcp
# Открываем публичный HTTPS и временно HTTP для получения сертификата
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Включаем firewall с политикой deny по умолчанию
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable
# Проверяем активные правила
sudo ufw status verbose
Включите jail для SSH. Если SSH доступен только через VPN, укажите подсеть VPN в списке trusted IP.
# Включаем и запускаем защиту SSH
sudo systemctl enable --now fail2ban
# Проверяем состояние jail sshd
sudo fail2ban-client status sshd
6. Установка ПО — пошагово
В примере используется OpenBao 2.2.x, системная служба systemd, Caddy 2.10.x и restic 0.18.x. Перед production-установкой проверьте актуальный стабильный релиз на официальной странице OpenBao и зафиксируйте конкретную версию. Нельзя использовать URL с плавающим словом latest в автоматическом скрипте обновления.
Шаг 1. Создание системного пользователя
# Создаём пользователя без shell для процесса OpenBao
sudo useradd --system --home /etc/openbao --shell /usr/sbin/nologin openbao
# Создаём каталоги конфигурации, данных и TLS
sudo install -d -o openbao -g openbao -m 750 /etc/openbao
sudo install -d -o openbao -g openbao -m 750 /opt/openbao/data
sudo install -d -o openbao -g openbao -m 750 /etc/openbao/tls
Шаг 2. Загрузка OpenBao
Ниже показан пример для amd64. Для ARM64 замените amd64 на arm64. Версия должна быть одинаковой на всех узлах кластера. После загрузки рекомендуется сверить SHA256 с контрольной суммой из официального релиза.
# Фиксируем версию, чтобы обновления были контролируемыми
export OPENBAO_VERSION=2.2.0
# Загружаем официальный архив OpenBao для Linux amd64
curl -fL -o /tmp/openbao.zip \
"https://github.com/openbao/openbao/releases/download/v${OPENBAO_VERSION}/openbao_${OPENBAO_VERSION}_linux_amd64.zip"
# Распаковываем бинарный файл в каталог системных программ
sudo unzip -o /tmp/openbao.zip -d /usr/local/bin
# Назначаем владельца и безопасные права на бинарный файл
sudo chown root:root /usr/local/bin/openbao
sudo chmod 0755 /usr/local/bin/openbao
# Проверяем установленную версию
openbao version
Если в релизе используется другой шаблон имени архива, возьмите точное имя asset со страницы релиза. Не подменяйте официальный бинарный файл сборкой неизвестного происхождения.
Шаг 3. Установка Caddy
Caddy автоматически получает и продлевает сертификаты Let’s Encrypt. Для этого DNS-имя должно указывать на публичный IPv4 или IPv6 сервера, а порты 80 и 443 должны быть доступны из Интернета.
# Добавляем ключ официального репозитория Caddy
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
# Добавляем репозиторий Caddy для Ubuntu
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
# Устанавливаем Caddy 2.x и проверяем его состояние
sudo apt update
sudo apt install -y caddy
sudo systemctl enable --now caddy
caddy version
Шаг 4. Установка systemd unit
# Создаём службу OpenBao с отдельным пользователем и ограничениями systemd
sudo tee /etc/systemd/system/openbao.service > /dev/null <<'EOF'
[Unit]
Description=OpenBao secrets management server
Documentation=https://openbao.org/docs/
After=network-online.target
Wants=network-online.target
[Service]
User=openbao
Group=openbao
ExecStart=/usr/local/bin/openbao server -config=/etc/openbao/openbao.hcl
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
AmbientCapabilities=CAP_IPC_LOCK
CapabilityBoundingSet=CAP_IPC_LOCK
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/opt/openbao/data
[Install]
WantedBy=multi-user.target
EOF
# Перечитываем unit-файлы systemd
sudo systemctl daemon-reload
Шаг 5. Установка CLI-переменных
# Создаём локальный профиль для администратора OpenBao
tee -a ~/.profile > /dev/null <<'EOF'
export BAO_ADDR='https://bao.example.com'
EOF
# Загружаем переменную в текущую оболочку
source ~/.profile
# Проверяем, что CLI установлен и доступен
openbao version
Команда openbao является основным CLI. В старых скриптах может встречаться команда vault; не смешивайте бинарники и переменные окружения без проверки совместимости.
7. Конфигурация
Конфигурация OpenBao и Raft
Сначала создаём конфигурацию. OpenBao слушает только loopback по HTTP, а TLS завершается на Caddy. Это допустимый вариант, потому что трафик между двумя процессами не покидает сервер. Если между reverse proxy и OpenBao есть отдельная сеть или другой хост, внутренний TLS обязателен.
# Создаём конфигурацию OpenBao с Raft Storage и локальным listener
sudo tee /etc/openbao/openbao.hcl > /dev/null <<'EOF'
ui = true
disable_mlock = true
storage "raft" {
path = "/opt/openbao/data"
node_id = "bao-1"
}
listener "tcp" {
address = "127.0.0.1:8200"
cluster_address = "127.0.0.1:8201"
tls_disable = true
}
api_addr = "https://bao.example.com"
cluster_addr = "http://127.0.0.1:8201"
telemetry {
disable_hostname = true
prometheus_retention_time = "24h"
}
EOF
# Назначаем конфигурации владельца и закрытые права
sudo chown openbao:openbao /etc/openbao/openbao.hcl
sudo chmod 640 /etc/openbao/openbao.hcl
# Проверяем конфигурацию без запуска сервера
sudo -u openbao /usr/local/bin/openbao operator validate-config /etc/openbao/openbao.hcl
# Запускаем OpenBao и добавляем его в автозагрузку
sudo systemctl enable --now openbao
# Смотрим последние сообщения службы
sudo systemctl status openbao --no-pager
sudo journalctl -u openbao -n 50 --no-pager
Reverse proxy и HTTPS через Caddy
Замените домен на собственное DNS-имя. Электронный адрес можно указать в глобальном блоке Caddy для уведомлений о сертификатах. Пока DNS не указывает на сервер, Caddy не сможет получить сертификат.
# Настраиваем HTTPS reverse proxy для OpenBao
sudo tee /etc/caddy/Caddyfile > /dev/null <<'EOF'
{
email [email protected]
}
bao.example.com {
encode gzip
reverse_proxy 127.0.0.1:8200 {
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-Host {host}
}
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
Referrer-Policy "no-referrer"
}
}
EOF
# Проверяем синтаксис Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
# Перечитываем конфигурацию без остановки Caddy
sudo systemctl reload caddy
# Проверяем HTTPS-заголовки и ответ API
curl -I https://bao.example.com/v1/sys/health
Код ответа health endpoint зависит от состояния OpenBao. Для неинициализированного или запечатанного сервера часто возвращается HTTP 501 или 503, что не означает неисправность reverse proxy. Важно увидеть корректный TLS-сертификат и JSON-ответ от OpenBao.
Инициализация хранилища
Инициализацию выполняют один раз. Результат команды содержит unseal-ключи и initial root token. Не записывайте вывод в историю shell, чат, тикеты или обычный файл на сервере. Запустите команду с локального администратора, имеющего зашифрованное место для хранения результата.
# Проверяем состояние до инициализации
openbao status
# Инициализируем Raft с пятью ключами и порогом три
openbao operator init -key-shares=5 -key-threshold=3
Сохраните пять ключей у разных доверенных администраторов. Далее распечатайте сервер, передав три ключа в интерактивном режиме. Не передавайте ключи через аргументы командной строки: они могут попасть в историю или список процессов.
# Вводим первый unseal key интерактивно
openbao operator unseal
# Повторяем ещё для двух разных ключей
openbao operator unseal
openbao operator unseal
# Проверяем, что хранилище распечатано
openbao status
Авторизуйтесь initial root token только в административной сессии. После создания постоянных административных политик root token следует убрать в офлайн-хранилище и не использовать для приложений.
KV v2 и политика доступа
KV v2 хранит версии секретов и поддерживает чтение, запись, удаление и восстановление версий. Включим secrets engine под путём secret/, затем создадим приложение с доступом только к secret/data/myapp/.
# Экспортируем root token только в текущую оболочку
read -rsp "OpenBao root token: " BAO_TOKEN
export BAO_TOKEN
echo
# Включаем KV v2 на пути secret/
openbao secrets enable -path=secret kv-v2
# Записываем тестовый секрет
openbao kv put secret/myapp/config \
DB_HOST="127.0.0.1" \
DB_NAME="appdb" \
API_ENDPOINT="https://api.example.com"
# Проверяем чтение секрета
openbao kv get secret/myapp/config
Создайте policy-файл. Для KV v2 путь API содержит data, а CLI скрывает эту деталь. Разрешение list не даёт чтения значений, но его следует выдавать только там, где приложению действительно нужен список ключей.
# Создаём минимальную политику для приложения
sudo tee /tmp/myapp-policy.hcl > /dev/null <<'EOF'
path "secret/data/myapp/" {
capabilities = ["read"]
}
path "secret/metadata/myapp/" {
capabilities = ["list"]
}
EOF
# Загружаем policy в OpenBao
openbao policy write myapp-readonly /tmp/myapp-policy.hcl
# Удаляем временный файл с политикой
shred -u /tmp/myapp-policy.hcl
AppRole для приложения
AppRole предназначен для машинной аутентификации. role_id можно считать идентификатором роли, а secret_id — секретом, который должен храниться в защищённом deployment-секрете. Установим короткое время жизни токена и ограничим число его использований.
# Включаем метод аутентификации AppRole
openbao auth enable approle
# Создаём роль с ограниченным TTL и политикой чтения
openbao write auth/approle/role/myapp \
token_policies="myapp-readonly" \
token_ttl=1h \
token_max_ttl=4h \
secret_id_ttl=24h \
secret_id_num_uses=2
# Получаем role_id, который можно передать в deployment-систему
openbao read -field=role_id auth/approle/role/myapp/role-id
# Генерируем одноразовый secret_id
openbao write -field=secret_id -f \
auth/approle/role/myapp/secret-id
Передайте два значения приложению через защищённый механизм CI/CD или секреты оркестратора. Не добавляйте их в Dockerfile, Git-репозиторий, systemd unit или общедоступный лог. После теста проверьте, что приложение не может читать чужие пути.
# Выполняем login через AppRole и получаем временный client_token
export ROLE_ID="replace-with-role-id"
export SECRET_ID="replace-with-secret-id"
export APP_TOKEN="$(
curl -fsS \
--request POST \
--data "{\"role_id\":\"${ROLE_ID}\",\"secret_id\":\"${SECRET_ID}\"}" \
https://bao.example.com/v1/auth/approle/login \
| jq -r '.auth.client_token'
)"
# Читаем разрешённый секрет
curl -fsS \
-H "X-OpenBao-Token: ${APP_TOKEN}" \
https://bao.example.com/v1/secret/data/myapp/config | jq
# Проверяем отказ в доступе к чужому пути
curl -sS -o /tmp/denied.json -w "%{http_code}\n" \
-H "X-OpenBao-Token: ${APP_TOKEN}" \
https://bao.example.com/v1/secret/data/other-app/config
Управление root token
После проверки AppRole не используйте root token для ежедневных задач. Создайте отдельную административную policy с ограниченными операциями и выдайте персональный токен каждому оператору. Root token можно отозвать, а для аварийных действий генерировать новый recovery-процессом согласно внутреннему регламенту.
# Отзываем root token после завершения первичной настройки
openbao token revoke "$BAO_TOKEN"
# Удаляем токен из текущей оболочки
unset BAO_TOKEN
Проверка работоспособности
# Проверяем health endpoint через публичный HTTPS
curl -fsS https://bao.example.com/v1/sys/health | jq
# Проверяем состояние Raft и sealed status
openbao status
# Проверяем, что порт 8200 не слушает внешний адрес
sudo ss -lntp | grep -E ':8200|:443'
# Проверяем состояние обеих служб
systemctl is-active openbao
systemctl is-active caddy
# Смотрим ошибки за последние 15 минут
sudo journalctl -u openbao --since "15 minutes ago" -p warning
sudo journalctl -u caddy --since "15 minutes ago" -p warning
8. Бэкапы и обслуживание
Что необходимо резервировать
Основной источник данных — снимок Raft. Он содержит состояние OpenBao, включая KV-секреты, политики и настройки auth methods. Дополнительно сохраните конфигурацию /etc/openbao/openbao.hcl, systemd unit, Caddyfile и процедуру восстановления.
Unseal-ключи и root token не следует включать в обычный серверный backup. Они должны храниться отдельно и быть доступны нескольким уполномоченным людям. Если зашифровать backup ключом, который лежит на том же VPS, потеря VPS уничтожит и данные, и ключ расшифровки.
Создание снимка Raft
Снимок выполняется через API и не требует остановки OpenBao. Для backup-пользователя создайте токен с правом read на endpoint snapshot. Этот токен не должен иметь административных прав.
# Создаём каталог для временного snapshot
sudo install -d -o ops -g ops -m 700 /var/backups/openbao
# Создаём снимок Raft через локальный API
curl -fsS \
-H "X-OpenBao-Token: ${BAO_BACKUP_TOKEN}" \
https://bao.example.com/v1/sys/storage/raft/snapshot \
-o /var/backups/openbao/raft-$(date -u +%Y%m%dT%H%M%SZ).snap
# Проверяем, что файл непустой
ls -lh /var/backups/openbao/
Точное право для snapshot endpoint зависит от версии OpenBao и используемого пути. Проверьте policy API вашей версии перед выдачей токена. Никогда не делайте snapshot endpoint доступным без аутентификации.
Шифрование backup через restic и S3
Restic шифрует данные до отправки в S3. Для production используйте отдельный бакет, отдельные access key с минимальными правами и пароль restic из менеджера секретов. В примере переменные вынесены в файл с правами 600; этот файл не должен попадать в Git.
# Создаём каталог с параметрами резервного копирования
sudo install -d -o root -g root -m 700 /etc/openbao
# Сохраняем параметры только для root
sudo tee /etc/openbao/backup.env > /dev/null <<'EOF'
export AWS_ACCESS_KEY_ID='replace-me'
export AWS_SECRET_ACCESS_KEY='replace-me'
export RESTIC_REPOSITORY='s3:https://s3.example.net/openbao-prod'
export RESTIC_PASSWORD='replace-with-long-random-password'
export RESTIC_COMPRESSION='auto'
EOF
# Закрываем файл с секретами
sudo chmod 600 /etc/openbao/backup.env
# Инициализируем restic repository один раз
sudo bash -c 'source /etc/openbao/backup.env && restic init'
Для более строгой модели храните credentials в systemd credentials, отдельном секретном хранилище или используйте IAM-роль, если VPS находится в совместимой облачной среде. Не отправляйте в внешний backup весь каталог данных Raft обычным rsync: согласованный snapshot безопаснее для восстановления.
Скрипт автоматического backup
# Создаём скрипт backup-снимка и выгрузки в restic
sudo tee /usr/local/sbin/openbao-backup.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
source /etc/openbao/backup.env
tmpdir="$(mktemp -d /var/backups/openbao.XXXXXX)"
cleanup() {
rm -rf "$tmpdir"
}
trap cleanup EXIT
snapshot="$tmpdir/raft.snap"
metadata="$tmpdir/metadata.txt"
curl --fail --silent --show-error \
-H "X-OpenBao-Token: ${BAO_BACKUP_TOKEN}" \
"https://bao.example.com/v1/sys/storage/raft/snapshot" \
-o "$snapshot"
{
date -u
hostname
openbao version
sha256sum "$snapshot"
} > "$metadata"
restic backup "$snapshot" "$metadata" --tag openbao-raft
restic forget \
--tag openbao-raft \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 12 \
--prune
restic check
EOF
# Разрешаем запуск только root
sudo chmod 700 /usr/local/sbin/openbao-backup.sh
# Добавляем backup token в защищённый environment-файл
sudo sh -c 'printf "\nexport BAO_BACKUP_TOKEN='\''replace-with-backup-token'\''\n" >> /etc/openbao/backup.env'
sudo chmod 600 /etc/openbao/backup.env
Команда restic check проверяет целостность структуры репозитория, но не заменяет тестовое восстановление. Первый запуск выполните вручную и убедитесь, что в S3 появились зашифрованные объекты.
systemd timer вместо cron
# Создаём service unit для backup
sudo tee /etc/systemd/system/openbao-backup.service > /dev/null <<'EOF'
[Unit]
Description=OpenBao encrypted Raft backup
After=network-online.target openbao.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/openbao-backup.sh
User=root
EOF
# Создаём таймер ежедневного запуска в 03:15 UTC
sudo tee /etc/systemd/system/openbao-backup.timer > /dev/null <<'EOF'
[Unit]
Description=Daily OpenBao backup timer
[Timer]
OnCalendar=-- 03:15:00 UTC
Persistent=true
RandomizedDelaySec=15m
[Install]
WantedBy=timers.target
EOF
# Включаем таймер и проверяем расписание
sudo systemctl daemon-reload
sudo systemctl enable --now openbao-backup.timer
sudo systemctl start openbao-backup.service
sudo systemctl list-timers openbao-backup.timer
sudo journalctl -u openbao-backup.service -n 50 --no-pager
Проверка восстановления
Восстановление следует тестировать на отдельном временном VPS или изолированном контейнере. Нельзя проверять его поверх работающего production-хранилища. Для восстановления нужен чистый OpenBao той же или совместимой версии, конфигурация Raft и snapshot.
# Получаем последний snapshot из restic в отдельный каталог
sudo mkdir -p /tmp/openbao-restore
sudo restic restore latest --tag openbao-raft \
--target /tmp/openbao-restore
# Проверяем найденные файлы
find /tmp/openbao-restore -type f -maxdepth 5 -ls
Дальше остановите тестовый OpenBao, замените его пустой Raft storage и выполните restore через CLI или API согласно документации версии. После восстановления проверьте unseal, чтение тестового секрета, AppRole login и политики. Запишите фактическое время восстановления и исправьте процедуру, если она требует ручных действий, которые никто не может выполнить в аварии.
Обновления
Перед обновлением создайте свежий Raft snapshot и сохраните текущую версию. Для одного узла используйте maintenance window: остановите сервис, замените бинарный файл, запустите OpenBao и проверьте health endpoint. Не удаляйте старый бинарник до успешной проверки.
В кластере из трёх или пяти узлов обновление выполняется по одному узлу с контролем quorum. Это не означает, что можно без проверки смешивать любые версии: ознакомьтесь с compatibility notes конкретного релиза. После обновления одного узла проверьте состояние Raft и только затем переходите к следующему.
# Перед обновлением сохраняем состояние и snapshot
openbao status
sudo systemctl start openbao-backup.service
# После замены бинарника перезапускаем сервис
sudo systemctl restart openbao
# Проверяем API, sealed status и последние ошибки
curl -fsS https://bao.example.com/v1/sys/health | jq
openbao status
sudo journalctl -u openbao -n 100 --no-pager
9. Troubleshooting и FAQ
OpenBao отвечает ошибкой «connection refused» на порту 8200
Сначала проверьте состояние процесса командой systemctl status openbao и журналом journalctl -u openbao -n 100. Затем выполните ss -lntp | grep 8200: listener должен быть на 127.0.0.1:8200. Если service завершился, чаще всего причина в ошибке HCL, неверном владельце каталога Raft или занятом порте. Запустите openbao operator validate-config и исправьте найденную проблему.
HTTPS возвращает 502 Bad Gateway
Код 502 означает, что Caddy не может подключиться к upstream. Убедитесь, что OpenBao запущен и слушает 127.0.0.1:8200, а в Caddyfile указан именно этот адрес. Проверьте curl http://127.0.0.1:8200/v1/sys/health локально и журнал journalctl -u caddy. После изменения конфигурации выполните caddy validate, затем systemctl reload caddy.
Сертификат Caddy не выпускается
Проверьте DNS-записи A и AAAA: обе должны вести на сервер, либо удалите ошибочную AAAA-запись. Порты 80 и 443 должны быть открыты в UFW и во внешнем firewall провайдера. Посмотрите журнал journalctl -u caddy. Let’s Encrypt также ограничивает частоту запросов, поэтому не следует многократно удалять и создавать конфигурацию во время диагностики.
После перезагрузки OpenBao снова sealed
При Shamir seal это нормальное поведение: после запуска требуется ввести установленное количество unseal-ключей. Автоматически хранить эти ключи на том же сервере небезопасно. Введите нужное число ключей вручную или настройте поддерживаемый auto-unseal через внешний KMS/HSM. Убедитесь, что ключи распределены между администраторами и что процедура unseal проверена до аварии.
AppRole login возвращает 400 или 403
Проверьте, что включён путь auth/approle, role_id принадлежит правильной роли, а secret_id не истёк и не использован разрешённое число раз. Выполните openbao read auth/approle/role/myapp административным токеном и проверьте TTL. Если login успешен, но чтение секрета возвращает 403, посмотрите policy: для KV v2 нужен путь secret/data/..., а не только secret/....
Какой VPS-конфиг минимально подойдёт?
Для лаборатории достаточно 1 vCPU, 1 ГБ RAM и 20 ГБ SSD, но это не лучший production-вариант. Для небольшой рабочей системы выбирайте минимум 2 vCPU, 2–4 ГБ RAM, 40 ГБ SSD и стабильный статический IP. Обязательно добавьте внешний backup. Если на том же VPS работают CI runners, базы данных или reverse proxy для многих сервисов, увеличьте память до 8 ГБ либо вынесите OpenBao на отдельную машину.
Что выбрать — VPS или dedicated для этой задачи?
Для одного OpenBao-узла и небольшого числа приложений VPS обычно рациональнее: ресурсов достаточно, а масштабирование можно выполнить добавлением узлов. Dedicated нужен при строгой физической изоляции, высокой нагрузке, большом количестве соседних сервисов или требованиях к предсказуемому диску. Однако dedicated сам по себе не даёт высокой доступности. Для HA важнее три узла Raft и корректная сеть между ними.
Можно ли хранить snapshot на том же VPS?
Локальная копия полезна для быстрого восстановления после ошибочного удаления, но не защищает от отказа диска, компрометации сервера или удаления VPS. Используйте локальный snapshot как промежуточный файл, а затем отправляйте его в отдельное S3-совместимое хранилище с шифрованием restic. Держите несколько поколений backup и периодически выполняйте тестовое восстановление на отдельной машине.
Нужно ли открывать порт 8200 в Интернете?
В описанной схеме нет: OpenBao слушает только loopback, а внешний доступ идёт через Caddy по HTTPS. Это уменьшает поверхность атаки и упрощает firewall. Порт 8200 потребуется открыть только между узлами, если вы создадите Raft-кластер; тогда ограничьте доступ приватной сетью и настройте внутренний TLS. Не публикуйте API напрямую без шифрования и контроля сетевого доступа.
Как понять, что резервное копирование действительно работает?
Проверьте успешный exit code systemd-сервиса, наличие нового snapshot в restic и дату последнего backup. Команда restic snapshots покажет сохранённые поколения, а restic check проверит структуру репозитория. Самая надёжная проверка — восстановить snapshot на временном узле, распечатать OpenBao, прочитать тестовый секрет и проверить вход через AppRole. Backup без проверенного restore нельзя считать рабочим.
10. Выводы и следующие шаги
В результате OpenBao работает на отдельном VPS с Raft Storage, HTTPS через Caddy, машинной аутентификацией AppRole и зашифрованными удалёнными резервными копиями. Секреты отделены от исходного кода, а приложение получает только необходимые права и ограниченный по времени токен.
Следующим шагом создайте мониторинг health endpoint, срока действия TLS-сертификата, свободного места и успешности backup. Затем настройте трёхузловой Raft-кластер, если простой одного VPS неприемлем. Наконец, автоматизируйте ротацию secret_id, регулярное тестовое восстановление и процедуру обновления OpenBao через maintenance window.