С приходом контейнеров и оркестрации появилась проблема: где безопасно хранить секреты? На рынке достаточно решений - Sealed Secrets, Kubernetes Secrets, Infisical - но сейчас стандартом остаётся HashiCorp Vault. Vault - это не просто хранилище паролей. Это централизованное управление секретами с динамической выдачей секретов, гибкими политиками доступа и полным аудитом.

Но и у Vault есть свои проблемы: высокий порог входа, настройка политик доступа и самая большая боль - unseal. Дело в том, что Vault при запуске находится в sealed-состоянии и не отдаёт секреты, пока его не разблокируют. Перезагрузился под, упала VM - и все сервисы, зависящие от Vault, перестанут запускаться (уже запущенные, кстати, продолжат работать). Вопрос в том, как это решать правильно.

Самый простой способ хранить unseal-ключи - записать на листочек и спрятать в сейф. Но как передать ключи коллеге? Что если единственный носитель ключа уволился, ушёл в отпуск или просто недоступен в 3 часа ночи? Это классическая единая точка отказа - и строить на ней безопасность продакшена как минимум рискованно, а как максимум приведёт к потере секретов навечно.

Решение этой проблемы - auto-unseal через облачные KMS: AWS KMS, GCP Cloud KMS, Azure Key Vault. Сервисы берут на себя расшифровку мастер-ключа при старте Vault, и ручное вмешательство больше не нужно. Но что если вы работаете только в on-premise и отправлять даже ключи шифрования во внешние сервисы - не вариант?

Можно поднять второй Vault, который будет unseалить первый через Transit. Рабочая схема, но это дополнительная инфраструктура, которую тоже нужно обслуживать и мониторить. А если хочется полной отказоустойчивости, цепочка Vault'ов начинает расти.

Я столкнулся с этой проблемой на практике: Kubernetes-кластер, тысяча секретов, десять проектов - и только on-premise.

Наше решение - автоматический unseal внутри кластера. В pod template Vault добавлен lifecycle hook PostStart, который при старте выполняет vault operator unseal тремя ключами Shamir.

Вот как это выглядит:

extraSecretEnvironmentVars:
    - envName: VAULT_UNSEAL_KEY_1
      secretName: vault-unseal-keys
      secretKey: VAULT_UNSEAL_KEY_1
    - envName: VAULT_UNSEAL_KEY_2
      secretName: vault-unseal-keys
      secretKey: VAULT_UNSEAL_KEY_2
    - envName: VAULT_UNSEAL_KEY_3
      secretName: vault-unseal-keys
      secretKey: VAULT_UNSEAL_KEY_3

  postStart:
    - /bin/sh
    - -c
    - |
      sleep 5;
      for i in $(seq 1 30); do
        vault status >/dev/null 2>&1;
        [ $? -ne 1 ] && break;
        sleep 2;
      done;
      vault operator unseal "$VAULT_UNSEAL_KEY_1" >/dev/null 2>&1;
      vault operator unseal "$VAULT_UNSEAL_KEY_2" >/dev/null 2>&1;
      vault operator unseal "$VAULT_UNSEAL_KEY_3" >/dev/null 2>&1;

Параллельно работает sidecar-контейнер, который мониторит состояние Vault - и если тот уходит в sealed, повторяет unseal без перезагрузки пода. Мы реализовали это так:

extraContainers:
    - name: auto-unseal
      image: hashicorp/vault:1.18
      command:
        - /bin/sh
        - -c
        - |
          export VAULT_ADDR=http://127.0.0.1:8200
          echo "Auto-unseal watcher started"
          while true; do
            vault status >/dev/null 2>&1
            if [ $? -eq 2 ]; then
              echo "$(date) Vault is sealed, unsealing..."
              vault operator unseal "$VAULT_UNSEAL_KEY_1" >/dev/null 2>&1
              vault operator unseal "$VAULT_UNSEAL_KEY_2" >/dev/null 2>&1
              vault operator unseal "$VAULT_UNSEAL_KEY_3" >/dev/null 2>&1
              echo "$(date) Unseal complete"
            fi
            sleep 10
          done
      env:
        - name: VAULT_UNSEAL_KEY_1
          valueFrom:
            secretKeyRef:
              name: vault-unseal-keys
              key: VAULT_UNSEAL_KEY_1
        - name: VAULT_UNSEAL_KEY_2
          valueFrom:
            secretKeyRef:
              name: vault-unseal-keys
              key: VAULT_UNSEAL_KEY_2
        - name: VAULT_UNSEAL_KEY_3
          valueFrom:
            secretKeyRef:
              name: vault-unseal-keys
              key: VAULT_UNSEAL_KEY_3
      resources:
        requests:
          memory: 16Mi
          cpu: 10m
        limits:
          memory: 64Mi
          cpu: 50m

Контейнер потребляет минимум ресурсов, но всегда отработает - ничего кроме проверки он не делает.

Сами unseal-ключи хранятся в кластере как Sealed Secrets. Доступ к namespace Vault закрыт строгим RBAC - credentials выдаются вручную ограниченному кругу инженеров и только на ограниченное время. Без явного разрешения ни увидеть, ни расшифровать ключи невозможно.

Я уверен, что можно придумать способ лучше - но для on-premise без внешнего KMS это решение, которое работает в проде уже не первый месяц. Vault перезагружается, unseалится сам, сервисы не замечают даунтайма.

Good Luck & Have DevOps fun.