Безопасное управление секретами в Kubernetes и GitLab CI с помощью HashiCorp Vault
Хранение учетных данных в конфигурационных файлах проекта, репозиториях или в виде переменных CI/CD несет серьезные риски утечки секретов через логи сборщиков или при компрометации раннеров. Использование статических паролей усугубляет проблему: они редко ротируются из-за опасения нарушить работу систем. Интеграция с HashiCorp Vault (системой централизованного хранения секретов и управления доступом) позволяет решить эти проблемы за счет динамической генерации и инжекции учетных данных.
Архитектура интеграции с Kubernetes через Vault Agent Injector
Vault Agent Injector работает как Mutating Admission Webhook в Kubernetes. Он перехватывает запросы на создание подов, проверяет аннотации в манифесте и добавляет в под Init-контейнер и Sidecar-контейнер с Vault Agent. Агент авторизуется в Vault с помощью JWT-токена сервисного аккаунта Kubernetes, считывает секреты и монтирует их в виде файлов по пути /vault/secrets/ в память пода.
Ниже приведен пример Deployment с настроенной инжекцией секретов базы данных. Аннотации должны располагаться строго в блоке spec.template.metadata.annotations (шаблон пода):
apiVersion: apps/v1
kind: Deployment
metadata:
name: billing-service
namespace: production
spec:
replicas: 1
selector:
matchLabels:
app: billing-service
template:
metadata:
labels:
app: billing-service
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "db-app"
vault.hashicorp.com/agent-inject-secret-db-creds: "database/creds/db-app"
vault.hashicorp.com/agent-inject-template-db-creds: |
{{ with secret "database/creds/db-app" }}
export DB_USER="{{ .Data.username }}"
export DB_PASSWORD="{{ .Data.password }}"
{{ end }}
spec:
serviceAccountName: app-example
containers:
- name: application
image: registry.example.com/billing:v1.2.0
command: ["/bin/sh", "-c", ". /vault/secrets/db-creds && exec billing-app"]
Интеграция с GitLab CI: OIDC и JWT
Для аутентификации пайплайнов GitLab CI в Vault используется механизм OIDC. GitLab генерирует JWT-токен сборки, а Vault проверяет его подпись через JWKS-эндпоинт GitLab и выдает временный токен авторизации.
1. Нативная интеграция (GitLab Premium / Ultimate)
В коммерческих версиях доступен синтаксис secrets, автоматически запрашивающий секреты из Vault без необходимости устанавливать клиент Vault в образ:
deploy_prod:
stage: deploy
id_tokens:
VAULT_ID_TOKEN:
aud: https://vault.example.com
secrets:
DATABASE_PASSWORD:
vault: production/db/password@ops
file: false
script:
- ansible-playbook deploy.yml --extra-vars "db_pass=$DATABASE_PASSWORD"
2. Интеграция через CLI (GitLab Community Edition - CE)
В бесплатной версии GitLab аналогичный функционал реализуется через вызовы CLI-клиента Vault:
deploy_staging:
stage: deploy
image: hashicorp/vault:1.15.0
id_tokens:
VAULT_ID_TOKEN:
aud: https://vault.example.com
script:
- export VAULT_TOKEN=$(vault write -field=token auth/jwt/login role=billing-deploy jwt=$VAULT_ID_TOKEN)
- export STAGING_DB_PASS=$(vault kv get -field=password secret/billing/staging/db)
- ansible-playbook deploy.yml --extra-vars "db_pass=$STAGING_DB_PASS"
Критическое предупреждение по безопасности (security caveat): При настройке JWT-авторизации необходимо ограничивать доступ через проверку полей токена. В настройках роли Vault задавайте bound_claims (например, разрешая доступ только для конкретных project_id, namespace_id или веток ref: refs/heads/main), чтобы другие проекты вашего инстанса GitLab не могли получить доступ к секретам этой роли.
Переход к динамическим секретам PostgreSQL
Вместо постоянных паролей для базы данных движок Dynamic Secrets в Vault генерирует уникальных пользователей в СУБД со случайными паролями и коротким временем жизни (TTL).
Настройка PostgreSQL-движка в Vault выполняется администратором:
# Включение движка баз данных
vault secrets enable database
# Настройка подключения к базе данных
vault write database/config/my-postgres-database plugin_name=postgresql-database-plugin allowed_roles=my-db-role connection_url="postgresql://{{username}}:{{password}}@postgres-server:5432/billing?sslmode=disable" username="vault_admin_user" password="super_secure_admin_password"
# Создание роли с TTL в 15 минут
vault write database/roles/my-db-role db_name="my-postgres-database" creation_statements="CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO "{{name}}";" default_ttl="15m" max_ttl="30m"
При обращении к роли vault read database/creds/my-db-role Vault выполняет SQL-запрос из creation_statements, генерируя пользователя вида v-token-my-db-ro-xxxx, и автоматически удаляет его по истечении TTL.
Пошаговый план миграции инфраструктуры
- Включение методов аутентификации: Активируйте поддержку токенов Kubernetes и JWT-токенов в Vault.
- Установка Vault Agent Injector: Установите Helm-чарт Vault в Kubernetes:
helm repo add hashicorp https://helm.releases.hashicorp.com helm repo update helm install vault hashicorp/vault --namespace vault --create-namespace --set "injector.enabled=true" - Настройка связки GitLab CI и Vault: Создайте роли с привязкой к OIDC GitLab и проверьте прохождение аутентификации.
- Перенос статических секретов: Перенесите секреты из переменных репозиториев в KV-хранилище Vault.
- Внедрение динамических секретов: Настройте интеграцию с PostgreSQL и переведите доступ на временных пользователей.
Риски и эксплуатационные ограничения
- Утечка через переменные: При
file: falseсекреты хранятся в памяти процесса. По возможности используйте временные файлы секретов для изоляции. - Ограничения короткого TTL: Время жизни пароля в 15 минут может прервать длительные миграции БД. Для таких задач создавайте роли с увеличенным
max_ttlили используйте механизм продления аренды секрета (lease renewal). - Стратегия отката (Rollback): На время миграции сохраняйте старые статические учетные записи в режиме «только чтение» для быстрого переключения в случае сбоя.
