Есть два типа Kubernetes-кластеров.

В первом люди хранят секреты в values.yaml, коммитят .env в Git и называют это “временным решением”.

Во втором - поднимают HashiCorp Vault, настраивают auto unseal, раздают доступ через Kubernetes Auth, а потом спокойно пьют кофе, пока остальные экстренно ротируют токены после очередного “ой”.

Сегодня - как раз про второй вариант.

Что вообще такое Auto Unseal и зачем он нужен

Обычный Vault после старта требует unseal.

То есть:

  • Vault поднялся,
  • Pod красивый,
  • readiness green,
  • а внутри - кирпич.

Пока кто-то не введёт несколько unseal keys.

В проде это выглядит примерно как:

“Кто помнит где лежит третий ключ?”
“А кто вообще был on-call?”
“Почему Vault лежит уже 40 минут?”

Auto Unseal решает эту проблему.

Vault сам расшифровывает master key через внешний KMS:

  • AWS KMS
  • Google Cloud KMS
  • Azure Key Vault
  • Transit backend другого Vault
  • HSM

Для Kubernetes чаще всего используют:

  • Cloud KMS
  • или Transit Auto Unseal

Transit - особенно хорош, когда:

  • мультикластер,
  • hybrid,
  • bare metal,
  • или “у нас private cloud, потому что compliance”.

Архитектура нормального Vault в Kubernetes

Минимально адекватная схема выглядит так:

                    +-------------------+
                    |  Transit Vault    |
                    |  (Unseal Master)  |
                    +---------+---------+
                              |
                       Transit Encrypt
                              |
        +---------------------+----------------------+
        |                                            |
+-------+--------+                         +---------+--------+
| Vault Cluster  |                         | Vault Cluster    |
| Kubernetes #1  |                         | Kubernetes #2    |
+----------------+                         +------------------+

Почему хранить секреты в Kubernetes Secret - зашквар?

Потому что:

  • base64 - это не encryption,
  • etcd иногда живёт слишком долго,
  • backup’ы тоже живут слишком долго,
  • а потом кто-то находит дамп etcd за 2023 год и начинается киберпанк.

Vault решает:

  • динамические креды,
  • lease,
  • ротацию,
  • PKI,
  • short-lived secrets,
  • audit,
  • revocation.

И внезапно безопасность перестаёт быть “таблицей в Confluence”.

Установка Vault через Helm

Официальный Helm Chart - HashiCorp Vault Helm Chart

Добавляем репозиторий:

helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update

Ну и по накатанной...

values.yaml для HA + Raft + Auto Unseal

server:
  replicas: 3

  ha:
    enabled: true

    raft:
      enabled: true

      config: |
        ui = true

        listener "tcp" {
          tls_disable = 0
          address       = "[::]:8200"
          cluster_address = "[::]:8201"

          tls_cert_file = "/vault/userconfig/tls/tls.crt"
          tls_key_file  = "/vault/userconfig/tls/tls.key"
          tls_client_ca_file = "/vault/userconfig/tls/ca.crt"
        }

        storage "raft" {
          path = "/vault/data"
        }

        seal "transit" {
          address = "https://vault-transit.internal:8200"
          token   = "hvs.xxxxxxxxx"
          key_name = "autounseal"
          mount_path = "transit/"
          tls_skip_verify = "false"
        }

service:
  enabled: true

ui:
  enabled: true

Создаём transit backend

На master Vault:

vault secrets enable transit

Создаём ключ:

vault write -f transit/keys/autounseal

Политика:

path "transit/encrypt/autounseal" {
  capabilities = ["update"]
}

path "transit/decrypt/autounseal" {
  capabilities = ["update"]
}

Kubernetes Auth

Это место, где многие делают:

token_reviewer_jwt: ""

А потом удивляются почему auth не работает.

Настраиваем:

vault auth enable kubernetes

vault write auth/kubernetes/config \
  kubernetes_host=https://kubernetes.default.svc

Создаём role:

vault write auth/kubernetes/role/app \
    bound_service_account_names=app \
    bound_service_account_namespaces=prod \
    policies=app \
    ttl=1h

Доставка секретов в Pod

Теперь начинается самое интересное.

Есть несколько способов.

И вот тут уже видно разницу между:

“мы развернули Vault”

и

“мы умеем им пользоваться”.

Вариант 1. Vault Agent Injector

Самый популярный вариант.

Pod получает sidecar:

  • авторизуется,
  • тянет секреты,
  • рендерит template,
  • обновляет их автоматически.

Пример Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  template:
    metadata:
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "app"
        vault.hashicorp.com/agent-inject-secret-config: "secret/data/app"
        vault.hashicorp.com/agent-inject-template-config: |
          {{- with secret "secret/data/app" -}}
          DATABASE_URL={{ .Data.data.database_url }}
          REDIS_PASSWORD={{ .Data.data.redis_password }}
          {{- end }}
    spec:
      serviceAccountName: app
      containers:
        - name: api
          image: ghcr.io/company/api:latest

Внутри контейнера появится файл:

/vault/secrets/config

Почему Vault Agent хорош

Потому что:

  • секреты не лежат в Git,
  • не лежат в etcd,
  • не светятся в CI/CD,
  • и не путешествуют по Slack как “скинь токен срочно”.

Вариант 2. CSI Driver

Когда хочется именно mounted files.

Устанавливаем: Secrets Store CSI Driver

И Vault Provider: Vault CSI Provider

SecretProviderClass

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: vault-db
spec:
  provider: vault
  parameters:
    vaultAddress: "https://vault.example.com"
    roleName: "app"
    objects: |
      - objectName: "db-password"
        secretPath: "secret/data/db"
        secretKey: "password"

Pod

volumes:
  - name: secrets
    csi:
      driver: secrets-store.csi.k8s.io
      readOnly: true
      volumeAttributes:
        secretProviderClass: vault-db

Когда CSI лучше Agent Injector

CSI удобнее когда:

  • нужны именно файлы,
  • legacy приложения,
  • Java любит .jks,
  • nginx хочет PEM,
  • PostgreSQL требует cert/key.

То есть всё то прекрасное enterprise-наследие, которое живёт дольше некоторых датацентров.

Вариант 3. External Secrets Operator

Очень хороший вариант.

Особенно если:

  • команды привыкли к Kubernetes Secret,
  • но вы не хотите хранить секреты в Git.

Проект: External Secrets Operator

ClusterSecretStore

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "https://vault.example.com"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "external-secrets"

ExternalSecret

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secret
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: app-secret
  data:
    - secretKey: password
      remoteRef:
        key: app
        property: password

PKI через Vault

Вот тут Vault становится уже не просто “хранилищем секретов”.

А полноценным internal CA.

Можно:

  • выпускать mTLS сертификаты,
  • делать short-lived certs,
  • автоматизировать ротацию,
  • выдавать cert per pod.

Включаем PKI

vault secrets enable pki

Генерируем Root CA

vault write pki/root/generate/internal \
  common_name="corp.local"

Role

vault write pki/roles/internal \
  allowed_domains="corp.local" \
  allow_subdomains=true \
  max_ttl="72h"

Получение сертификата

vault write pki/issue/internal \
  common_name=api.prod.corp.local

И вот здесь начинается настоящая магия

Когда:

  • PostgreSQL получает mTLS,
  • ingress получает cert автоматически,
  • сервисы живут на short-lived certs,
  • а revoke делается за секунды.

В этот момент инфраструктура начинает выглядеть так, будто её собирали люди, которые хотя бы иногда читают документацию не только после инцидента.

Что я обычно рекомендую в production

Маленький кластер

  • Vault HA
  • Raft
  • Transit Auto Unseal
  • Agent Injector

Большой Kubernetes

  • Vault HA
  • Raft
  • Transit или Cloud KMS
  • CSI Driver
  • PKI
  • dynamic secrets
  • audit backend
  • namespaces
  • Sentinel policies

Что НЕ надо делать

Не храните root token

Особенно:

  • в Jenkins,
  • в GitLab variables,
  • в Notion,
  • в “временном txt”.

Потому что:

“временное” в инфраструктуре живёт примерно до смерти вселенной.

Не отключайте TLS

Даже “внутри кластера”.

Эта фраза:

“ну это же internal traffic”

обычно произносится за пару месяцев до большого security review.

Не используйте один policy на всех

Политика:

path "*" {
  capabilities = ["create", "read", "update", "delete", "list"]
}

Это крик о помощи.

Итог

Vault в Kubernetes - это уже давно не “модно”.

Это просто нормальная инженерная гигиена.

Особенно когда:

  • Kubernetes растёт,
  • сервисов становится сотни,
  • CI/CD живёт своей жизнью,
  • а секреты начинают размножаться быстрее rabbitmq-инстансов после пятничного релиза.

И да - auto unseal это именно тот момент, когда инфраструктура перестаёт зависеть от человека с флешкой и становится похожей на production, а не на квест “найди второго администратора”.

PS: Если время будет, расскажу, как развернуть свой доверенный центр сертификации на Волте.