feat(security): Vault — self-hosted стор секретов (standalone + raft) #6

Merged
grachevko merged 3 commits from feat/vault into main 2026-09-17 17:32:16 +00:00
Collaborator

Следующий платформенный слой после хранилища: Vault как источник секретов для External Secrets Operator. ESO будет отдельным PR.

Что в PR

  • ns security + Flux-Kustomization vault (targetNamespace security, wait: false).
  • GitRepository на github.com/hashicorp/vault-helm (tag v0.34.1, Chart 0.34.1 / appVersion 2.0.4), чарт берётся из корня репозитория.
  • HelmRelease vault: server.standalone.enabled + интегрированный raft на PVC 5Gi классом proxmox-local-zfs (ZFS-зеркало rpool), global.authDelegator.enabled: true, server.ui: true, injector и csi выключены.
  • HTTPRoute vault.<домен> на envoy-internal (https-листенер), backend vault:8200.
  • Модуль just с рецептами распечатывания: vault/mod.just + mod? vault 'vault' в корневом justfile.

Ключевые решения

  • Чарт из git, а не из helm-репозитория HashiCorp. https://helm.releases.hashicorp.com/index.yaml отдаётся через CloudFront, который блокирует российские адреса: HelmRelease падает с 403 Forbidden. Источник — GitRepository по тегу, чарт в корне репы.
  • raft, а не file-бэкенд. В дефолтном конфиге чарта для standalone прописан storage "file", мы переопределяем на storage "raft" + service_registration "kubernetes" — это то, что потом позволяет расти до HA и даёт нормальные снапшоты (vault operator raft snapshot).
  • injector/csi выключены, authDelegator включён. Секреты приложениям раздаёт ESO, а не сайдкар-инжектор; для этого Vault должен принимать аутентификацию по service account.
  • wait: false у Kustomization. Готовность пода Vault наступает только после ручного vault operator init + unseal: readinessProbe — это vault status, который до распечатывания отдаёт exit 2. С wait: true объект висел бы красным, хотя чарт поставлен корректно.
  • upgrade.remediation.strategy: rollback (дефолт, но прописан явно) — см. раздел про данные ниже.

Что будет с данными при падении апгрейда и ремедиации

  • Helm при uninstall удаляет только объекты, записанные в манифест релиза: StatefulSet, Deployment, Service, ConfigMap/Secret, RBAC-объекты, StorageClasses. PVC с данными Vault в этот список не попадает: чарт не рендерит PVC, он отдаёт volumeClaimTemplates (helper vault.volumeclaims, server.dataStorage.enabled), а PVC создаёт контроллер StatefulSet — это не ресурс Helm.
  • k8s удаляет PVC от volumeClaimTemplates только по persistentVolumeClaimRetentionPolicy, у чарта она пустая, то есть действует дефолт Retain. Значит PVC data-vault-0 остаётся, а вместе с ним остаётся PV (zvol в rpool/data) и все данные.
  • Несмотря на это, для Vault явно выбран rollback: uninstall на время ремедиации уносит сам сервис, а от Vault зависят ESO и приложения. Приоритет — «остаться на прошлой рабочей ревизии», а не самолечение. uninstall остаётся у proxmox-csi, где переустановка ничего не стоит.
  • Реальная страховка от потери данных лежит вне Helm: ZFS-снапшоты rpool/data по расписанию и vault operator raft snapshot save (отдельным шагом после init), плюс отдельный StorageClass с reclaimPolicy: Retain для критичных томов — предложу отдельным PR.

Распечатывание: just vault unseal

  • Доли берутся из файла vault-unseal-keys в корне репы (переопределяется VAULT_UNSEAL_KEYS): по одному ключу на строку, строки с # игнорируются. Файл вне git — добавлен в .gitignore вместе с /vault-root-token.
  • В sops ключи не кладём сознательно: age-ключ sops лежит в самом кластере (секрет sops-age), поэтому любой sops-файл из этого репозитория расшифрует тот, у кого есть доступ к кластеру, и смысл ручного unseal (PVC без ключей бесполезен) исчезает. Копии — в менеджере паролей и на бумаге.
  • Рецепт идемпотентный: если Vault уже распечатан — ничего не делает; отправляет ровно столько долей, сколько нужно до порога; при нехватке верных долей выходит с кодом 1 и внятным сообщением, печатает итоговый статус. just vault status — просто состояние.
  • Проверено локально на фейковом kubectl: 3 доли из 5 → распечатан и цикл остановлен; повторный запуск → no-op; неверные доли → exit 1; отсутствующий под → exit 1; нет файла ключей → подсказка с абсолютным путём.

После мержа (руками, по шагам)

  1. kubectl -n security get pvc,pod — PVC должен забиндиться на proxmox-local-zfs.
  2. kubectl -n security exec -it vault-0 -- vault operator init (5 долей, порог 3) → ключи скопировать в vault-unseal-keys (chmod 600), root token и копии ключей — в менеджер паролей и офлайн.
  3. Дальше just vault unseal (в том числе удалённо, через WireGuard) и just vault status.
  4. DNS: статическая A-запись на RB5009 vault192.168.40.11 (LB-адрес envoy-internal). Сертификат — от internal-ca, браузер будет ругаться, пока CA не добавлен в доверенные на клиентах.
  5. UI: https://vault.<домен>.
Следующий платформенный слой после хранилища: **Vault** как источник секретов для External Secrets Operator. ESO будет отдельным PR. ## Что в PR - ns `security` + Flux-Kustomization `vault` (targetNamespace `security`, `wait: false`). - `GitRepository` на `github.com/hashicorp/vault-helm` (tag `v0.34.1`, Chart 0.34.1 / appVersion 2.0.4), чарт берётся из корня репозитория. - `HelmRelease` vault: `server.standalone.enabled` + интегрированный **raft** на PVC 5Gi классом `proxmox-local-zfs` (ZFS-зеркало rpool), `global.authDelegator.enabled: true`, `server.ui: true`, injector и csi выключены. - `HTTPRoute` `vault.<домен>` на `envoy-internal` (https-листенер), backend `vault:8200`. - Модуль `just` с рецептами распечатывания: `vault/mod.just` + `mod? vault 'vault'` в корневом justfile. ## Ключевые решения - **Чарт из git, а не из helm-репозитория HashiCorp.** `https://helm.releases.hashicorp.com/index.yaml` отдаётся через CloudFront, который блокирует российские адреса: HelmRelease падает с `403 Forbidden`. Источник — GitRepository по тегу, чарт в корне репы. - **raft, а не file-бэкенд.** В дефолтном конфиге чарта для standalone прописан `storage "file"`, мы переопределяем на `storage "raft"` + `service_registration "kubernetes"` — это то, что потом позволяет расти до HA и даёт нормальные снапшоты (`vault operator raft snapshot`). - **injector/csi выключены, authDelegator включён.** Секреты приложениям раздаёт ESO, а не сайдкар-инжектор; для этого Vault должен принимать аутентификацию по service account. - **`wait: false` у Kustomization.** Готовность пода Vault наступает только после ручного `vault operator init` + unseal: readinessProbe — это `vault status`, который до распечатывания отдаёт exit 2. С `wait: true` объект висел бы красным, хотя чарт поставлен корректно. - **`upgrade.remediation.strategy: rollback`** (дефолт, но прописан явно) — см. раздел про данные ниже. ## Что будет с данными при падении апгрейда и ремедиации - Helm при uninstall удаляет только объекты, записанные в манифест релиза: StatefulSet, Deployment, Service, ConfigMap/Secret, RBAC-объекты, StorageClasses. PVC с данными Vault в этот список не попадает: чарт не рендерит PVC, он отдаёт `volumeClaimTemplates` (helper `vault.volumeclaims`, `server.dataStorage.enabled`), а PVC создаёт контроллер StatefulSet — это не ресурс Helm. - k8s удаляет PVC от `volumeClaimTemplates` только по `persistentVolumeClaimRetentionPolicy`, у чарта она пустая, то есть действует дефолт `Retain`. Значит PVC `data-vault-0` остаётся, а вместе с ним остаётся PV (zvol в `rpool/data`) и все данные. - Несмотря на это, для Vault явно выбран `rollback`: uninstall на время ремедиации уносит сам сервис, а от Vault зависят ESO и приложения. Приоритет — «остаться на прошлой рабочей ревизии», а не самолечение. `uninstall` остаётся у proxmox-csi, где переустановка ничего не стоит. - Реальная страховка от потери данных лежит вне Helm: ZFS-снапшоты `rpool/data` по расписанию и `vault operator raft snapshot save` (отдельным шагом после init), плюс отдельный StorageClass с `reclaimPolicy: Retain` для критичных томов — предложу отдельным PR. ## Распечатывание: `just vault unseal` - Доли берутся из файла `vault-unseal-keys` в корне репы (переопределяется `VAULT_UNSEAL_KEYS`): по одному ключу на строку, строки с `#` игнорируются. Файл вне git — добавлен в `.gitignore` вместе с `/vault-root-token`. - **В sops ключи не кладём сознательно:** age-ключ sops лежит в самом кластере (секрет `sops-age`), поэтому любой sops-файл из этого репозитория расшифрует тот, у кого есть доступ к кластеру, и смысл ручного unseal (PVC без ключей бесполезен) исчезает. Копии — в менеджере паролей и на бумаге. - Рецепт идемпотентный: если Vault уже распечатан — ничего не делает; отправляет ровно столько долей, сколько нужно до порога; при нехватке верных долей выходит с кодом 1 и внятным сообщением, печатает итоговый статус. `just vault status` — просто состояние. - Проверено локально на фейковом `kubectl`: 3 доли из 5 → распечатан и цикл остановлен; повторный запуск → no-op; неверные доли → exit 1; отсутствующий под → exit 1; нет файла ключей → подсказка с абсолютным путём. ## После мержа (руками, по шагам) 1. `kubectl -n security get pvc,pod` — PVC должен забиндиться на `proxmox-local-zfs`. 2. `kubectl -n security exec -it vault-0 -- vault operator init` (5 долей, порог 3) → ключи скопировать в `vault-unseal-keys` (chmod 600), root token и копии ключей — в менеджер паролей и офлайн. 3. Дальше `just vault unseal` (в том числе удалённо, через WireGuard) и `just vault status`. 4. DNS: статическая A-запись на RB5009 `vault` → `192.168.40.11` (LB-адрес envoy-internal). Сертификат — от `internal-ca`, браузер будет ругаться, пока CA не добавлен в доверенные на клиентах. 5. UI: `https://vault.<домен>`.
hermes added 1 commit 2026-09-17 16:53:32 +00:00
feat(security): Vault standalone+raft на proxmox-csi, чарт из git
Flate / Flate - Filter (pull_request) Successful in 5s
Flate / Flate (pull_request) Successful in 16s
Flate / Flate - Success (pull_request) Canceled after 0s
f7471fb105
hermes added 1 commit 2026-09-17 17:17:31 +00:00
feat(vault): just vault unseal/status — доли из gitignored файла
Flate / Flate - Filter (pull_request) Successful in 5s
Flate / Flate (pull_request) Successful in 18s
Flate / Flate - Success (pull_request) Canceled after 0s
372e26ece8
hermes added 1 commit 2026-09-17 17:27:16 +00:00
fix(vault): remediation rollback вместо uninstall — Vault критичен для ESO
Flate / Flate - Success (pull_request) Blocked by required conditions
Flate / Flate - Filter (pull_request) Successful in 5s
Flate / Flate (pull_request) Successful in 18s
869817c4db
grachevko merged commit 869817c4db into main 2026-09-17 17:32:16 +00:00
grachevko deleted branch feat/vault 2026-09-17 17:32:16 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: grachevko/home-ops#6