docs(ops): vault unseal runbook + auto-unseal migration plan

Triage card для on-call: где взять unseal-keys (1Password placeholder,
уточнить с DevOps lead), как запустить script либо ручную процедуру,
что проверить после, troubleshooting типовых проблем.

Также: §7 — план миграции на auto-unseal (Transit / Yandex KMS) когда
определимся с master Vault или KMS-backend.
This commit is contained in:
Zimin A.N.
2026-05-05 16:56:45 +03:00
parent cef78f9971
commit 0fb20dc8d4
+138
View File
@@ -0,0 +1,138 @@
# Runbook: Vault unseal (staging / prod)
**Когда использовать:** Vault на кластере залип в `Sealed=true` после reboot
ноды или manual seal. Признаки:
- Spring Boot pod'ы (`ordinis-app`, `ordinis-read-api`, `ordinis-projection-writer`)
стоят в `Init:0/1`.
- В логах `vault-agent-init` контейнера: `Vault is sealed`, error 503 на
`auth/kubernetes/login`.
- `kubectl exec -n config vault-0 -- vault status``Sealed: true`.
**Auto-unseal сейчас НЕ настроен** (v1 closure backlog: Transit secret engine
с master Vault либо Yandex Cloud KMS). До миграции — ручная процедура ниже.
---
## 1. Где взять unseal-ключи
Ключи Shamir, threshold **3 из 5**. Хранение:
- **Corporate 1Password** — vault `Ordinis НСИ ЦУОД`:
- `vault-staging-unseal-keys` — для cluster.265 (192.168.100.161)
- `vault-prod-unseal-keys` — для cluster.142 (192.168.100.142)
- **Резерв** (cold storage) — у DevOps lead в KeePass.
Доступ к 1Password vault — у списка инженеров с on-call. При расширении —
добавлять через 1Password admin. Никогда не пересылать ключи в Slack /
Telegram / email.
> ⚠️ **При утере 5 из 5 ключей:** Vault инициализируется заново → ВСЕ
> secrets потеряны (postgres passwords, Kafka SASL, Keycloak signing keys
> в K8s secrets — отдельно). Plan: получить root token + replay `setup.sh` +
> заново положить secrets из 1Password (Postgres backup + Vault: писать
> только в зеркало). Делать только с письменным разрешением tech lead.
## 2. Быстрый unseal (script)
```bash
cd ~/code/ordinis-infra/k8s/vault
# staging
./unseal.sh staging "<KEY_1>" "<KEY_2>" "<KEY_3>"
# prod
./unseal.sh prod "<KEY_1>" "<KEY_2>" "<KEY_3>"
```
Скрипт делает:
1. Pull kubeconfig с ноды (если ещё не скачан).
2. Проверяет `vault status` — если уже unsealed, выходит.
3. Применяет 3 ключа последовательно через `vault operator unseal`.
4. Проверяет `Sealed: false`.
5. Force-deletes Spring Boot pod'ы — vault-agent-init иначе ждёт backoff
до 3+ минут на следующую попытку auth.
После — `kubectl get pods -n ordinis-{env}` показывает `2/2 Running`
через ~2 минуты.
## 3. Ручной unseal (если script недоступен)
```bash
# 1. Получить kubeconfig
ssh root@192.168.100.161 "cat /etc/kubernetes/admin.conf" > /tmp/kc-161
sed -i '' 's|server: https://[^:]*:|server: https://192.168.100.161:|' /tmp/kc-161
export KUBECONFIG=/tmp/kc-161
# 2. Проверить статус
kubectl exec -n config vault-0 -- vault status
# 3. Применить 3 ключа (по одному)
kubectl exec -n config vault-0 -- vault operator unseal '<KEY_1>'
kubectl exec -n config vault-0 -- vault operator unseal '<KEY_2>'
kubectl exec -n config vault-0 -- vault operator unseal '<KEY_3>'
# 4. Убедиться Sealed: false
kubectl exec -n config vault-0 -- vault status
# 5. Перезапустить Spring Boot pod'ы
kubectl delete pod -n ordinis-staging \
-l 'app.kubernetes.io/name in (ordinis-app,ordinis-read-api,ordinis-projection-writer)' \
--grace-period=0 --force
# 6. Проверить
kubectl get pods -n ordinis-staging
```
## 4. Подтверждение что всё работает
```bash
# read-api
curl -sk --resolve ordinis.k8s.265.nstart.cloud:443:192.168.100.161 \
https://ordinis.k8s.265.nstart.cloud/api/v1/spacecraft/records?lang=ru-RU \
| jq 'length'
# → должно вернуть число > 0
# admin
curl -sk -o /dev/null -w "%{http_code}\n" \
--resolve ordinis.k8s.265.nstart.cloud:443:192.168.100.161 \
"https://ordinis.k8s.265.nstart.cloud/api/v1/admin/audit?page=0&size=5"
# → 200
```
## 5. Эскалация / проблемы
| Симптом | Что делать |
|------------------------------------------------------|--|
| `vault status``connection refused` | Pod `vault-0` не Running. `kubectl get pods -n config` + `kubectl describe`. |
| `unseal` принимает ключ но Sealed остаётся true | Проверить count `Unseal Progress` — может нужен 4-й ключ если threshold увеличили. |
| Все 3 ключа отвергает (`invalid key`) | Whitespace / обрезанные символы. Скопировать заново из 1Password (полное поле). |
| Pod'ы Spring Boot не поднимаются после unseal | `kubectl logs <pod> -c vault-agent-init` — могут быть policy/role миграции после изменений. |
| Consul backend (Vault использует Consul) недоступен | `kubectl get pods -n config consul-server-0` + `kubectl logs`. |
При 30+ минут downtime → эскалация tech lead → решение о переносе
prod-нагрузки на запасной кластер либо публичная коммуникация в SLA-канал.
## 6. Связанные документы
- Установка Vault с нуля: `ordinis-infra/k8s/vault/setup.sh`
- Архитектура auth: `ordinis-auth/.../{ScopeContext,SecurityConfig}.java`
- Список секретов в Vault: `secret/ordinis/cuod/` (postgres, kafka, redis)
## 7. Переход к auto-unseal (TODO для v1 closure)
Когда будет master Vault либо Yandex Cloud KMS:
1. Добавить `seal "transit"` либо `seal "yandexcloudkms"` stanza в vault config
ConfigMap (`config/vault-config`).
2. Запустить migration:
```
kubectl exec -n config vault-0 -- vault operator unseal -migrate KEY_1
kubectl exec -n config vault-0 -- vault operator unseal -migrate KEY_2
kubectl exec -n config vault-0 -- vault operator unseal -migrate KEY_3
```
3. После миграции существующие Shamir-keys больше не работают — выдаются
Recovery Keys (5 шт, тот же threshold 3) для emergency rekey/operator-init
операций.
4. Обновить этот runbook: основной случай — auto-unseal, ручной unseal
только при downtime master Vault / KMS.