
Если у вас B2B/B2C‑сервис, мобильное приложение, платёжные интеграции или партнерские API — у вас есть секреты: пароли БД, ключи подписи, токены платёжек, вебхуки. Их безопасное хранение и своевременная ротация — это:
Против этого работают: централизованное хранение, минимально необходимые привилегии, шифрование, короткие TTL, автоматическая ротация, аудит доступа.
.env удобен для локальной разработки, но в проде это:
Kubernetes Secret хранится в etcd в base64. Минимально — включить шифрование Secret в etcd через поставщика KMS кластера и ограничить доступы RBAC. Но это только база, без аудита и ротации.
KMS — это управляемый сервис шифрования (AWS KMS, GCP KMS, Azure Key Vault Keys). Он даёт:
Пример ручного шифрования файла ключом KMS:
aws kms encrypt --key-id alias/app-prod --plaintext fileb://secret.txt \
--output text --query CiphertextBlob > secret.enc.base64
# Расшифровать на машине с правами к KMS
aws kms decrypt --ciphertext-blob fileb://secret.enc.base64 \
--output text --query Plaintext | base64 -d > secret.txt
Такой подход лучше совмещать с автоматизацией (например, sops) или с менеджером секретов, чтобы приложение не знало о KMS напрямую.
Плюсы: контроль доступа по ролям, аудит, автоматическая ротация, интеграции с Kubernetes и CI, удобные API. Это базовый выбор для прод‑сред.
В PostgreSQL нет «двойного пароля» на одну учётку. Рабочий паттерн:
SQL для PostgreSQL (роль прав отдельно, два логина на одну роль):
-- Роль с правами приложения
CREATE ROLE app_role;
GRANT CONNECT ON DATABASE appdb TO app_role;
GRANT USAGE ON SCHEMA public TO app_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_role;
-- Текущая учётка
CREATE ROLE app_v1 LOGIN PASSWORD 'S3cure#2025';
GRANT app_role TO app_v1;
-- Новая учётка для ротации
CREATE ROLE app_v2 LOGIN PASSWORD 'S3cure#2026';
GRANT app_role TO app_v2;
-- После переката на app_v2
REVOKE app_role FROM app_v1;
DROP ROLE app_v1;
Практические моменты:
MySQL поддерживает «двойной пароль» для одной учётки (c 8.0.14), можно включить новый пароль как «второй», перекатить, затем удалить старый.
Для подписи токенов используйте пару ключей и публикуйте открытые ключи через JWKS‑эндпойнт. При ротации добавляйте новый ключ (с новым kid), дайте потребителям подтянуть его, потом уберите старый.
Проверка токена по JWKS на стороне потребителя (Node.js):
const jwksClient = require('jwks-rsa');
const jwt = require('jsonwebtoken');
const client = jwksClient({
jwksUri: 'https://auth.example.com/.well-known/jwks.json',
cache: true,
cacheMaxEntries: 5,
cacheMaxAge: 10 * 60 * 1000
});
function getKey(header, cb) {
client.getSigningKey(header.kid, function(err, key) {
if (err) return cb(err);
const signingKey = key.getPublicKey();
cb(null, signingKey);
});
}
function verify(token) {
return new Promise((resolve, reject) => {
jwt.verify(token, getKey, { algorithms: ['RS256'] }, (err, decoded) => {
if (err) return reject(err);
resolve(decoded);
});
});
}
Используйте cert-manager в Kubernetes и короткие сроки жизни сертификатов (дни/недели). Ротация пройдёт автоматически без простоя.
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: app-ca
namespace: app
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: app-mtls
namespace: app
spec:
secretName: app-mtls
commonName: app
duration: 168h # 7 дней
renewBefore: 24h
issuerRef:
name: app-ca
kind: Issuer
ESO синхронизирует секреты из облака в Kubernetes Secret и умеет обновлять их по расписанию. Поды перезапускаются при изменении Secret.
ServiceAccount для доступа к секретам (в EKS — через роль IAM, упрощённо):
apiVersion: v1
kind: ServiceAccount
metadata:
name: eso-sa
namespace: app
SecretStore с AWS Secrets Manager:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws
namespace: app
spec:
provider:
aws:
service: SecretsManager
region: eu-central-1
auth:
jwt:
serviceAccountRef:
name: eso-sa
ExternalSecret для базы:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-db
namespace: app
spec:
refreshInterval: 1h
secretStoreRef:
name: aws
kind: SecretStore
target:
name: app-db
creationPolicy: Owner
data:
- secretKey: DATABASE_URL
remoteRef:
key: app/prod/db
property: url
Использование в Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: app
spec:
replicas: 3
selector:
matchLabels: { app: app }
template:
metadata:
labels: { app: app }
spec:
serviceAccountName: eso-sa
containers:
- name: app
image: registry.example.com/app:1.2.3
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-db
key: DATABASE_URL
Vault умеет подсаживать агента‑сайдкар, который кладёт секреты в файловую систему или переменные окружения и обновляет их.
Политика Vault (HCL):
path "kv/data/app/prod/*" {
capabilities = ["read"]
}
Аннотации Deployment для инжектора:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: app
annotations:
vault.hashicorp.com/agent-inject: 'true'
vault.hashicorp.com/role: 'app-role'
vault.hashicorp.com/agent-inject-secret-db: 'kv/data/app/prod/db'
vault.hashicorp.com/agent-inject-template-db: |
{{- with secret "kv/data/app/prod/db" -}}
DATABASE_URL={{ .Data.data.url }}
{{- end -}}
spec:
replicas: 2
selector:
matchLabels: { app: app }
template:
metadata:
labels: { app: app }
spec:
containers:
- name: app
image: registry.example.com/app:1.2.3
envFrom:
- secretRef:
name: vault-secrets
Чтобы не хранить открытые Secret в git, используйте Sealed Secrets: в репозитории — только зашифрованные объекты, а в кластере контроллер их расшифровывает.
kubectl create secret generic app-db \
--from-literal=DATABASE_URL='postgres://app_v2:pass@db:5432/app' \
-n app --dry-run=client -o json \
| kubeseal --controller-namespace kube-system --format yaml > app-db-sealed.yaml
kubectl apply -f app-db-sealed.yaml
День 1–2: аудит секретов
День 3–4: выбор инструмента
День 5–7: пилот на одном сервисе
День 8–10: распространение
День 11–14: операционные мелочи
Примеры правил Prometheus (псевдонимы метрик — адаптируйте под свой стек):
- alert: VaultLeaseRenewalErrors
expr: rate(vault_agent_renewal_errors_total[5m]) > 0
for: 5m
labels:
severity: critical
annotations:
summary: Сбой продления секрета через Vault Agent
- alert: ExternalSecretSyncFailed
expr: increase(externalsecret_sync_calls_failed_total[10m]) > 0
for: 10m
labels:
severity: warning
annotations:
summary: Ошибка синхронизации ExternalSecret
Затраты:
Выигрыш:
В: Нужно ли шифровать значения в Kubernetes Secret, если у нас уже есть Vault? О: Да, включите шифрование Secret в etcd — это базовая гигиена. Vault решает доставку и ротацию, а шифрование в etcd защищает от утечки снапшотов/бэкапов кластера.
В: Как часто ротировать пароли БД? О: Если нет динамических секретов — раз в 90 дней. Если есть подозрение на компрометацию — немедленно. Для особо критичных сервисов рассмотрите динамические секреты с TTL 1–24 часа.
В: Можно ли хранить секреты только в переменных окружения контейнера? О: Можно, но следите, чтобы они не попали в логи/дампы/диагностику. Для особо чувствительных значений лучше монтировать как файл с ограниченными правами и коротким TTL, а приложение перечитывает файл.
В: Что выбрать: Vault или облачный Secret Manager? О: Если вы в одном облаке и хотите быстрее — начните с облачного. Если нужна мультиоблачность, динамические секреты для БД, гибкие политики — Vault.
В: Как убедиться, что все поды обновились на новый секрет? О: В ESO при изменении Secret меняется его ресурс‑версия, и контроллер Deployment делает rolling update. В Vault Agent можно включить отправку сигнала приложению при обновлении файла. Добавьте проверку версий секретов в readiness‑пробу или экспозер метрики версии.