diff --git a/docs/ops/vault-unseal.md b/docs/ops/vault-unseal.md new file mode 100644 index 0000000..e893b3d --- /dev/null +++ b/docs/ops/vault-unseal.md @@ -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 "" "" "" + +# prod +./unseal.sh prod "" "" "" +``` + +Скрипт делает: + +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 '' +kubectl exec -n config vault-0 -- vault operator unseal '' +kubectl exec -n config vault-0 -- vault operator unseal '' + +# 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 -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.