единый контроль секретов в нескольких облаках без разброса копий

Управление секретами в масштабе в мультиоблачных и гибридных средах

14 минут

Когда учётные данные лежат в AWS, GCP, Azure и локальном Vault одновременно, разброс копий и устаревшая ротация раздувают последствия утечки. Единый слой с External Secrets Operator и короткоживущими динамическими секретами держит доступ под контролем и аудитом.

Почему мультиоблачная среда умножает риски с секретами

Один кластер EKS может одновременно читать AWS Secrets Manager, корпоративный Vault в ЦОД и GCP Secret Manager. Без продуманной схемы команды возвращаются к переменным окружения, долгоживущим ключам в CI и разовым скриптам без ротации. Один пароль от базы оказывается в десятке микросервисов, трёх пайплайнах и двух системах мониторинга — у каждой копии свой доступ и свой след в логах. Сменили пароль в одном хранилище — остальные ссылки устарели. Аудитор спрашивает, кто читал продакшен-учётные данные за месяц; собрать ответ по разным бэкендам долго и больно. Утечка сервисного аккаунта в мультитенантном кластере может пройти через пространства имён, облачные аккаунты и интеграции с партнёрами. Цель — единая плоскость управления: короткоживущие учётные данные, централизованные политики, встроенная интеграция с Kubernetes и CI и доказательства для compliance.

Опорная архитектура: центральный Vault, мосты в облака и синхронизация в Kubernetes

Возьмите HashiCorp Vault — или сопоставимое корпоративное хранилище — как точку политик и аудита. Включите движки под задачи: KV v2 для конфигурации, database для временных пользователей PostgreSQL, AWS и GCP — для облачных IAM-учётных данных, PKI — для mTLS-сертификатов. Там, где нужны нативные облачные API, External Secrets Operator синхронизирует Vault в Secret Kubernetes или читает AWS Secrets Manager и GCP Secret Manager напрямую через отдельные ClusterSecretStore. Предпочитайте динамические секреты с TTL статическим паролям в манифестах. Vault держите в HA с integrated storage и auto-unseal через облачный KMS. Для runtime используйте External Secrets Operator, если нужен Secret в кластере, или Secrets Store CSI driver, если под читает файлы без объекта Secret. CI входит по JWT или AppRole, читает только свои пути и не выводит значения в логи.

Bash · установка External Secrets Operator
helm repo add external-secrets https://charts.external-secrets.io
helm upgrade --install external-secrets external-secrets/external-secrets \
  --namespace external-secrets --create-namespace \
  --set installCRDs=true

Развёртывание Vault HA и движки секретов

Запускайте Vault с хранилищем Raft и распечатыванием через KMS, чтобы перезапуск не зависел от ручного ввода ключей Shamir. Включите TLS на listener, зафиксируйте api_addr на DNS сервиса и подключите audit до продакшен-трафика. Монтируйте движки по назначению: KV для конфигов, database для динамических учётных записей, облачные — где приложению нужен федеративный доступ. Политики ограничивайте префиксом пути, чтобы workload читал только свой каталог.

HCL · сервер Vault с Raft и печатью AWS KMS
storage "raft" {
  path    = "/vault/data"
  node_id = "vault-node-1"
}

seal "awskms" {
  region     = "us-east-1"
  kms_key_id = "alias/vault-unseal"
}

listener "tcp" {
  address       = "0.0.0.0:8200"
  tls_cert_file = "/vault/tls/tls.crt"
  tls_key_file  = "/vault/tls/tls.key"
}

api_addr     = "https://vault.internal:8200"
cluster_addr = "https://vault-node-1.internal:8200"
Bash · включение движков Vault
vault secrets enable -path=secret kv-v2
vault secrets enable database
vault secrets enable -path=aws/production aws
vault secrets enable -path=gcp/production gcp
vault secrets enable pki

vault audit enable file file_path=/var/log/vault/audit.log

External Secrets Operator: Vault и ClusterSecretStore для нескольких облаков

ClusterSecretStore описывает, как оператор входит в Vault или облачный API. ExternalSecret по интервалу refresh подтягивает удалённые поля в Secret Kubernetes — чем короче интервал, тем быстрее подхватывается ротация; для динамических учётных записей БД ставьте TTL меньше часа и refresh короче TTL. Для Vault используйте Kubernetes auth от service account оператора. Если workload обязан оставаться в одном облаке, добавьте ClusterSecretStore на AWS Secrets Manager или GCP Secret Manager, а Vault пусть выдаёт общие динамические пользователи БД между регионами.

YAML · ClusterSecretStore для Vault KV v2
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: https://vault.internal:8200
      path: secret
      version: v2
      auth:
        kubernetes:
          mountPath: kubernetes
          role: external-secrets
          serviceAccountRef:
            name: external-secrets
            namespace: external-secrets
YAML · ExternalSecret для полей подключения к БД
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-database-credentials
  namespace: production
spec:
  refreshInterval: 15m
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: db-credentials
    creationPolicy: Owner
    template:
      engineVersion: v2
      data:
        DB_HOST: "{{ .host }}"
        DB_USER: "{{ .username }}"
        DB_PASS: "{{ .password }}"
        DB_PORT: "{{ .port }}"
  dataFrom:
    - extract:
        key: secret/data/production/database

Динамические учётные данные, политики Terraform и JWT в CI/CD

Настройте роли database в Vault с SQL на создание и отзыв — каждый под или job получает уникального пользователя с автоматическим сроком жизни. Terraform описывает монтирования, подключения к БД, роли Kubernetes auth и политики least privilege для External Secrets и CI. В GitLab CI обменивайте JWT job на токен Vault; в GitHub Actions — hashicorp/vault-action с OIDC: секреты только в переменных окружения, без вывода в лог. Статические KV ротируйте по расписанию с версиями; после смены ESO по refresh разнесёт новые значения по кластерам.

Terraform · динамическая роль БД и Kubernetes auth
resource "vault_database_secret_backend_connection" "postgresql" {
  backend = vault_mount.database.path
  name    = "production-postgresql"

  postgresql {
    connection_url = "postgresql://{{username}}:{{password}}@db.internal:5432/app?sslmode=require"
  }
}

resource "vault_database_secret_backend_role" "readonly" {
  backend       = vault_mount.database.path
  name          = "readonly"
  db_name       = vault_database_secret_backend_connection.postgresql.name
  default_ttl   = 3600
  max_ttl       = 86400
  creation_statements = [
    "CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';",
    "GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";",
  ]
}

resource "vault_kubernetes_auth_backend_role" "external_secrets" {
  role_name                        = "external-secrets"
  bound_service_account_names      = ["external-secrets"]
  bound_service_account_namespaces = ["external-secrets"]
  token_policies                   = [vault_policy.external_secrets.name]
  token_ttl                        = 3600
}
YAML · GitHub Actions и Vault JWT без утечки в лог
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: hashicorp/vault-action@v3
        with:
          url: https://vault.internal:8200
          method: jwt
          role: github-actions
          secrets: |
            secret/data/production/api-key api_key | API_KEY
      - name: Deploy
        env:
          API_KEY: ${{ env.API_KEY }}
        run: ./deploy.sh

Практика: сканирование репозитория, минимальные права, аудит и учения на отказ

Не допускайте секретов в Git: detect-secrets или git-secrets в pre-commit и CI. Каждому сервису и пайплайну — узкая политика Vault; перечисление родительского пути без чтения, если нужен только обзор имён. Отправляйте audit Vault в SIEM или Loki и фильтруйте по request.path для ответов аудиторам. Задайте максимальный TTL для каждого класса учётных данных и алерт при сбое ротации. В staging отрабатывайте отказ: остановите Vault и проверьте, стартуют ли поды с уже синхронизированными Secret; укоротите refresh ESO и убедитесь, что новые учётные записи БД доходят под нагрузкой. Не кладите секреты в образы и не печатайте их в выводе пайплайна — окупаемость архитектуры видна при первой утечке, когда пострадал часовой токен, а не вечный ключ без ротации.

HCL · политика Vault с минимальными правами для сервиса
path "secret/data/production/service-a/*" {
  capabilities = ["read", "list"]
}

path "secret/data/production/*" {
  capabilities = ["list"]
}
Bash · поиск обращений к секрету в audit-логе Vault
jq 'select(.type=="response" and (.request.path|startswith("secret/data/production/db")))' \
  /var/log/vault/audit.log \
  | jq '{time, path: .request.path, identity: .auth.display_name}'
YAML · проверка секретов в CI через detect-secrets
- name: Scan for secrets
  run: |
    pip install detect-secrets
    detect-secrets scan --all-files --exclude-files '\.lock$' > report.json
    python -c "import json,sys; r=json.load(open('report.json'))['results']; sys.exit(1 if r else 0)"

Паттерны Vault для конвейеров и монтирование через CSI разобраны в гайде по управлению секретами в DevOps и CI/CD.

Подстановка секретов при запуске дополняет hardening кластера из практического гайда по безопасности Kubernetes в продакшене.