Nivel 3 · 30 min
Gestión de Secretos: vaults, rotación, leakage
Los secretos — passwords de DB, API keys, claves de firma, claves privadas TLS — son las credenciales que tus servicios usan para autenticarse contra otros sistemas. Son el target principal de los atacantes porque una sola API key robada puede leer una base de datos entera. La disciplina es mantenerlos fuera del source, distribuirlos de forma segura en runtime, rotarlos automáticamente, y detectar leakage rápido.
Donde NO Deben Vivir los Secretos
Anti-patrones que siguen shippeando a producción semanalmente: secretos en repos de source (sí, en archivos .env commiteados por accidente); secretos en logs de CI/CD (un curl -H "Authorization: $TOKEN" con set -x habilitado imprime el token); secretos en layers de imagen de container (ENV DB_PASSWORD=... en un Dockerfile queda horneado en cada layer de la imagen, visible para cualquiera con acceso de pull); secretos en mensajes de error (un stack trace que incluye el connection string); secretos en código del lado cliente (cualquier API key shippeada al browser es pública, sin excepciones). El pitfall del git history: borrar un secreto en un commit posterior NO lo saca del history — git filter-repo / BFG es necesario, y cualquier clone hecho antes del rewrite todavía lo tiene. Una vez que un secreto fue pusheado a un repo público por cualquier duración, tratalo como comprometido: rotalo inmediatamente, no solo borres el commit.
Vaults: Arquitectura y Mecanismos
Un vault (HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault) es un sistema de registro para secretos con las siguientes primitivas: (1) Cifrado en reposo usando una clave raíz de KMS (HSM-backed en variantes de cloud); el vault mismo solo ve secretos descifrados en memoria mientras sirve una request. (2) Acceso basado en identidad — los servicios se autentican al vault usando su workload identity de cloud (rol IAM, service account de GCP, token de service account de Kubernetes vía OIDC) y reciben credenciales de vida corta, no se requiere ''master secret'' de larga vida. (3) Audit log de cada lectura, cada escritura, cada cambio de policy — write-only a un destino separado (un bucket S3 al que el vault puede appendear). (4) Secretos dinámicos: Vault genera credenciales de DB por request con un lease de 1 hora — en vez de que todos los servicios compartan una password de DB para siempre, cada request recibe sus propias credenciales que expiran. El compromiso de los secretos en memoria de un servicio queda acotado en el tiempo. (5) Transit (encryption-as-a-service): el vault hace encrypt/decrypt sin exponer la clave — útil para cifrado a nivel de campo de aplicación (PII) sin darle la clave a la app.
Rotación, Detección de Leaks, Least Privilege
Los secretos estáticos se vuelven más débiles cuanto más viven: cada backup, cada línea de log, cada memory dump es un vector de leak potencial. Rotación automática: credenciales de DB rotadas por Vault en cada lease (1 hora); claves de firma JWT rotadas trimestralmente (con soporte de key-id para que tokens viejos sigan verificándose hasta expirar); API keys de terceros rotadas trimestralmente vía APIs del vendor donde se soporta. El pre-commit secret scanning ya es table stakes: gitleaks o trufflehog como pre-commit hook, GitHub secret scanning (auto-detecta 100+ tipos de secreto, puede revocar automáticamente con integraciones partner como AWS), scanning de paquetes npm/PyPI. Respuesta a un leak: rotá inmediatamente, auditá el uso del secreto en el audit log para determinar el radio de impacto, no solo reescribas git history (el secreto ya fue visto). Least privilege: cada secreto tiene un solo tenant (un servicio por secreto, no ''password de servicio-X compartida''); entornos totalmente aislados (dev / staging / prod tienen secretos separados — filtrar dev no filtra prod); los servicios read-only no reciben secretos que permitan escribir; el acceso admin de ''break-glass'' es logueado y time-limited (24h), con review trimestral de quién lo usó.
Code example
# .gitignore — al inicio de cada repo
.env
.env.*
!.env.example # mantené el template, nunca los valores reales
*.pem
*.key
secrets/
# pre-commit hook (.pre-commit-config.yaml)
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
# Ejemplo Vault Agent (Kubernetes) — sidecar baja el secreto, la app lee desde disco
apiVersion: v1
kind: Pod
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "orders-service"
vault.hashicorp.com/agent-inject-secret-db: "database/creds/orders-readwrite"
vault.hashicorp.com/agent-inject-template-db: |
{'{- with secret "database/creds/orders-readwrite" -}'}
DB_USER="{'{.Data.username}'}"
DB_PASS="{'{.Data.password}'}"
{'{- end -}'}
# Vault genera una credencial de DB por Pod, con lease de 1h
# Cuando el lease expira (o el Pod muere), la credencial se revoca
# Aplicación — lee desde el archivo inyectado, sin secreto en env vars
Properties props = new Properties();
props.load(new FileInputStream("/vault/secrets/db"));
String user = props.getProperty("DB_USER");
String pass = props.getProperty("DB_PASS");