Перейти к содержанию

Встроенный удостоверяющий центр

Поставка для случая, когда удостоверяющего центра (УЦ) у вас ещё нет и заводить его отдельно на время пилота не хочется. Стек поднимает УЦ внутри себя — но настоящий, а не демонстрационный.

Всё нужное лежит в этом же репозитории: docker-compose.selfhosted.yml и служебный скрипт vault-bootstrap.sh.

Какой из файлов брать

Файл Vault Когда брать
docker-compose.yml внешний (VAULT_ADDR) УЦ у вас уже есть
docker-compose.demo.yml внутри, режим -dev показать продукт за пять минут
docker-compose.selfhosted.yml внутри, файловое хранилище пилот со своим УЦ, данные которого должны пережить перезапуск

Разница между вторым и третьим — не в степени аккуратности. vault server -dev держит всё в памяти: перезапуск контейнера уничтожает корневой ключ УЦ, и все выданные сертификаты остаются без издателя, которого можно проверить. Для демонстрации это приемлемо, для пилота — нет.

Шаг 1. Задайте обязательные значения

Все команды на этой странице выполняются в корне клона репозитория — там, где лежит сам docker-compose.selfhosted.yml. Файл .env должен лежать рядом с ним.

git clone https://github.com/s3m4rgl/alatyr-docs.git
cd alatyr-docs

cat > .env <<'EOF'
ALATYR_IMAGE_TAG=<версия поставки>     # тега latest у нас нет намеренно
DB_PASSWORD=<сильный пароль>
ALATYR_LOCAL_AUTH_ENABLED=true
ALATYR_LOCAL_JWT_SECRET=<openssl rand -hex 32>
ALATYR_LOCAL_ADMIN_EMAIL=admin@corp.example
ALATYR_LOCAL_ADMIN_PASSWORD=<сильный пароль>
ALATYR_PKI_ALLOWED_DOMAINS=corp.example
EOF

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

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

Результат. В корне клона лежит .env, и docker compose -f docker-compose.selfhosted.yml config печатает состав без единой строки variable is not set.

Шаг 2. Поднимите стек

docker compose -f docker-compose.selfhosted.yml up -d

Сервер и админка поднимаются из готовых образов — собирать у себя нечего. Откуда их взять и что ещё входит в поставку — «Установка».

Результат. Все контейнеры в состоянии running или (для служебного) в exited (0), а сервер отвечает:

docker compose -f docker-compose.selfhosted.yml ps
curl -s http://localhost:8090/api/v1/version

Версия в ответе должна совпадать с ALATYR_IMAGE_TAG из .env. Если нет — дальше разбираться бессмысленно.

Шаг 3. Заберите ключ распечатывания

Служебный контейнер печатает предупреждение один раз и кладёт init.json (ключ распечатывания и корневой токен) в том alatyr_vault_keys:

docker compose -f docker-compose.selfhosted.yml cp \
    vault-bootstrap:/vault/keys/init.json ./init.json

Уберите файл в хранилище секретов организации и удалите локальную копию.

Это осознанный компромисс пилота: стек поднимается без участия человека, но ключ от сейфа лежит рядом с сейфом — кто получил доступ к тому, получил корневой УЦ целиком. Варианты ужесточения, по возрастанию усилий:

  • убрать init.json из тома совсем. Тогда после каждого перезапуска Vault остаётся запечатанным и распечатывается руками: docker compose exec vault vault operator unseal <ключ>;
  • заменить на автоматическое распечатывание через KMS или HSM (seal "awskms", seal "transit" в vault-selfhosted.hcl) — тогда файла не будет вовсе.

Результат. init.json у вас на руках, в хранилище секретов, и его нет ни в рабочем каталоге, ни в истории команд.

Шаг 4. Направьте цели в их издатели

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

Откройте админку, Настройки → Удостоверяющие центры, и задайте каждой цели её точку монтирования и роль:

Цель Точка монтирования Роль
wifi pki_wifi alatyr
user_mtls pki_user_mtls user-mtls
k8s pki_k8s k8s
vpn pki_vpn vpn
ssh движок ssh-client-signer ssh-user

Цепочку из pki_k8s понадобится отдать ещё и самому кластеру Kubernetes — как именно, описано в разделе «Доступ к Kubernetes».

Цели ad_logon в этом списке нет и быть не может

Вход в домен по смарт-карте требует расширения SID (KB5014754), которого Vault PKI не выпускает в принципе. Эта цель работает только через SCEP/ADCS. Как направить её в ваш ADCS — Вход в домен по смарт-карте → Настройка.

Результат. В списке профилей у каждой цели указаны своя точка монтирования и своя роль. Кнопка проверки связи на вкладке отвечает успехом.

Шаг 5. Смените пароль первого администратора

Сервер напоминает об этом в журнале при первом запуске. После смены уберите ALATYR_LOCAL_ADMIN_PASSWORD из .env.

Результат. Вход под прежним паролем не проходит, под новым — проходит.

Почему у каждой цели свой промежуточный УЦ

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

Цели wifi и user_mtls несут одинаковое имя в сертификате — непрерывность личности между целями сделана намеренно, — и приёмник mTLS различает их только по издателю. Пока обе выписываются одним издателем, машинный сертификат, который подписывается молча, без PIN и биометрии, является годной заменой пользовательскому.

Замеры на живых nginx, haproxy и Keycloak, а также рецепты настройки приёмника — «Приёмники».

Что проверено прогоном

Снято на живом стеке (Docker 29.6.1, Vault 1.17):

Проверка Результат
Корневой УЦ переживает down и up отпечаток совпадает побайтово
Vault распечатывается сам после перезапуска да
Повторный запуск служебного контейнера «уже настроен — ключ не трогаю», ничего не перевыпускает
Сервер берёт креды AppRole из файлов здоровье 200
Подпись имени в домене организации подписано
Подпись имени вне домена организации отказ 400 — роль действительно сужена
Тем же токеном pki/root/sign-intermediate отказ 403
Тем же токеном чтение sys/auth отказ 403
Подпись SSH-сертификата тем же токеном выдан ssh-ed25519-cert-v01

Два отказа в середине таблицы — главное, ради чего стоит смотреть на эту поставку: сервер ходит в Vault не корневым токеном, а ролью, которой хватает ровно на подпись и отзыв.

Резервная копия

Копировать нужно оба тома с данными — по отдельности они бесполезны:

Том Что в нём Потеря означает
alatyr_vault_data корневой ключ УЦ и промежуточные ни один выданный сертификат больше не проверяется по цепочке; перевыпуск УЦ равен переустановке на всём парке
alatyr_postgres_data устройства, заявки, выданные сертификаты, аудит УЦ жив, но продукт не знает, кому что выдавал
alatyr_vault_keys ключ распечатывания без него копия vault_data бесполезна — распечатать нечем
# Копия, снятая на остановленном стеке, надёжнее.
docker compose -f docker-compose.selfhosted.yml stop
docker run --rm -v alatyr_vault_data:/d -v "$PWD:/b" alpine \
    tar czf /b/vault-data.tgz -C /d .
docker run --rm -v alatyr_postgres_data:/d -v "$PWD:/b" alpine \
    tar czf /b/postgres-data.tgz -C /d .
docker compose -f docker-compose.selfhosted.yml start

Результат. Оба архива существуют и не пусты (ls -l *.tgz), а стек снова отвечает на /api/v1/version.

Восстановление — обратной командой в пустые тома и до первого up. Если стек уже поднялся, служебный контейнер увидит неинициализированный Vault и выпустит НОВЫЙ корень.

Задание по расписанию для этих команд заводите у себя: сама поставка резервных копий не делает.

Переезд на ваш Vault

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

  1. В вашем Vault поднимите ту же структуру: корень, промежуточные и роли.
  2. В админке перепишите профили издателей на новый адрес.
  3. Убедитесь, что новые заявки уходят туда: сертификат, выпущенный после переезда, должен строить цепочку к новому корню.
  4. Старый корень не выключайте, пока живы выданные им сертификаты. Их проверяют по цепочке, а цепочка ведёт к нему; выключенный УЦ ещё и перестаёт публиковать список отзыва, то есть приёмник, настроенный на CRL, начнёт отвергать всех (замеры — «Приёмники»).

Чего эта поставка не делает

  • Не реплицирует ничего. Одна реплика сервера, один PostgreSQL, один Vault. Что именно небезопасно на нескольких репликах — «Отказоустойчивость (HA)».
  • Не делает резервных копий сама. Команды выше — ваши, задание по расписанию заводите у себя.
  • Не заменяет автоматическое распечатывание через KMS. По умолчанию ключ распечатывания лежит в томе; см. шаг 3.
  • Не публикует Vault наружу. У сервиса vault нет опубликованных портов, и слушатель работает без TLS ровно поэтому. Как только Vault выносится на отдельный хост — TLS обязателен, иначе токен AppRole и подписанные сертификаты идут по сети открытым текстом.