Gestionar Secretos de Aplicaciones de Forma Segura
Cómo almacenar, rotar e inyectar API keys, contraseñas de base de datos y certificados sin hardcodearlos en código fuente o archivos de entorno.
Visión general
Los secretos — API keys, contraseñas de base de datos, certificados TLS, claves de encriptación — son las joyas de la corona de cualquier aplicación. Hardcodearlos en código fuente los commitea a control de versiones para siempre, expuestos a cualquiera con acceso al repositorio. Almacenarlos en archivos .env de texto plano en servidores los deja legibles por cualquier proceso corriendo como el mismo usuario.
La gestión segura de secretos significa almacenarlos en vaults dedicados con encriptación at rest, control de acceso, audit logging y rotación automática. Las aplicaciones obtienen secretos en runtime a través de llamadas API autenticadas, nunca persistiéndolos a disco. Aqui se explica como secret managers nativos de cloud (AWS, GCP, Azure), HashiCorp Vault y Kubernetes Secrets.
Cuándo usarlo
Usa esta receta cuando:
- Migrando de archivos
.envde desarrollo a almacenamiento de secretos de producción. Consulta Environment Variables para patrones de configuración local. - Rotando credenciales comprometidas o cumpliendo con requerimientos de auditoría de seguridad. Consulta JWT Authentication para estrategias de rotación de tokens.
- Compartiendo secretos entre microservicios, pipelines CI/CD y miembros del equipo. Consulta Docker Basics para inyección de secretos en contenedores.
- Gestionando certificados TLS, claves SSH o strings de conexión a base de datos. Consulta Parse Config Files para referencias de secretos por configuración.
- Auditando quién accedió a qué secreto y cuándo. Consulta Structured Logging para auditoría de logs.
Solución
AWS Secrets Manager (Python)
import boto3
import json
client = boto3.client('secretsmanager')
def get_secret(secret_name):
response = client.get_secret_value(SecretId=secret_name)
return json.loads(response['SecretString'])
db_creds = get_secret('prod/db/postgres')
conn = psycopg2.connect(
host=db_creds['host'],
user=db_creds['username'],
password=db_creds['password'],
)
HashiCorp Vault (Go)
import "github.com/hashicorp/vault/api"
client, _ := api.NewClient(api.DefaultConfig())
client.SetToken("s.xxx")
secret, _ := client.KVv2("secret").Get(context.Background(), "database/creds")
username := secret.Data["username"].(string)
password := secret.Data["password"].(string)
Kubernetes Secrets
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
username: admin
password: "{{ .Values.dbPassword }}"
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: app
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
Explicación
- Encriptación at rest: Los secretos se encriptan antes de escribirse a disco.
- Secretos en vivo: Un rol de PostgreSQL podría ser válido por 1 hora y luego revocarse automáticamente, minimizando el blast radius si se filtran.
- Control de acceso: Políticas IAM, políticas de Vault y RBAC de Kubernetes restringen qué servicios o usuarios pueden leer qué secretos. Nunca otorgues acceso de lectura a todos los secretos.
- Audit logging: cada lectura, escritura y rotación de secreto se loguea. Reenvía estos logs a herramientas SIEM para detección de anomalías.
Variantes
| Herramienta | Plataforma | Secretos en vivo | Auto-rotación | Mejor para |
|---|---|---|---|---|
| AWS Secrets Manager | AWS | Sí | Sí | Workloads nativos AWS |
| HashiCorp Vault | Multi | Sí | Sí | Multi-cloud, on-prem |
| Azure Key Vault | Azure | Parcial | Sí | Ecosistemas Azure |
| GCP Secret Manager | GCP | No | No | Workloads nativos GCP |
| Kubernetes Secrets | K8s | No | No | Inyección in-cluster |
Lo que funciona
- Nunca commitees secretos a Git: gitignore
para archivos. envy hooks pre-commit (comogit-secretsotruffleHog`) para escanear commits accidentales. - Rota secretos regularmente: configura políticas de rotación automática (30-90 días) y rota inmediatamente si un secreto es expuesto o un empleado se va.
- Usa acceso least-privilege: otorga a cada servicio exactamente los secretos que necesita. Un servidor web no necesita la clave de encriptación de backups.
- Cachea secretos brevemente, no para siempre: obtén secretos al inicio y refréscalos periódicamente. No llames al secret manager en cada request.
- Separa secretos por entorno:
prod/db/password,staging/db/passwordydev/db/passworddeberían ser valores diferentes en diferentes paths de vault.
Errores comunes
- Almacenar secretos en variables de entorno en hosts compartidos: las variables de entorno son visibles por todos los procesos en la misma máquina.
- Olvidar rotar después de brechas: cambiar la contraseña de aplicación no es suficiente. Rota API keys, certificados y secretos de sesión de forma comprehensiva.
- Loguear secretos: nunca loguees el valor completo de un secreto. Si debes loguear acceso, loguea el nombre del secreto y timestamp, nunca la contraseña misma.
- Usar Kubernetes Secrets sin encriptación de etcd: por default, los Kubernetes Secrets están base64-encoded, no encriptados. Habilita encriptación at rest de etcd.
Tips de Rendimiento
- Cachea secretos en memoria. Obtén una vez al inicio, refresca periódicamente:
import time
class SecretCache:
def __init__(self, ttl=300):
self._cache = {}
self._ttl = ttl
self._timestamps = {}
def get(self, name, fetch_func):
if name not in self._cache or time.time() - self._timestamps[name] > self._ttl:
self._cache[name] = fetch_func(name)
self._timestamps[name] = time.time()
return self._cache[name]
- Usa lecturas bulk de secretos. Obtén todos los secretos de un servicio en una sola llamada API:
# AWS: almacenar todos los secretos del servicio como un único secreto JSON
response = client.get_secret_value(SecretId='prod/api/all-secrets')
all_secrets = json.loads(response['SecretString'])
# all_secrets = {'db_url': '...', 'stripe_key': '...', 'sendgrid_key': '...'}
- Usa connection pooling para Vault. Reutiliza conexiones HTTP a Vault:
client, _ := api.NewClient(api.DefaultConfig())
// Client reutiliza conexiones internamente
// Ajusta settings de transport para alto throughput:
transport := &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
}
- Pre-carga secretos en init del contenedor. Obtén secretos durante el startup del contenedor, no en el primer request:
# Init container de Kubernetes
initContainers:
- name: secret-loader
image: secret-loader:latest
command: ["/bin/sh", "-c"]
args:
- |
vault kv get -field=password secret/db > /secrets/db_password
vault kv get -field=apikey secret/api > /secrets/api_key
volumeMounts:
- name: secrets
mountPath: /secrets
- Usa sidecar para rotación de secretos. Un sidecar puede observar cambios de secretos y enviar señales al contenedor principal:
# Vault Agent sidecar envía SIGHUP cuando los secretos cambian
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-inject-command-db-creds: "kill -HUP 1" Preguntas frecuentes
¿Debería usar un archivo .env en producción?
Solo como último recurso. Los archivos .env son legibles por cualquiera con acceso al servidor. Prefiere un secret manager que provea encriptación, control de acceso y rotación.
¿Cómo comparto secretos entre miembros del equipo de forma segura?
Usa un password manager de equipo (1Password, Bitwarden) para credenciales humanas y un secret manager (Vault, AWS SM) para credenciales de aplicación. Nunca compartas via Slack o email.
¿Qué es secret sprawl?
La duplicación no controlada de secretos a través de sistemas, repos y archivos. Combátelo con un vault centralizado y políticas estrictas de rotación.
¿Puedo usar Kubernetes Secrets para todo?
Los K8s Secrets están bien para inyección in-cluster pero carecen de capacidades avanzadas como generación en vivo y sharing cross-cluster. Usa un vault dedicado para requerimientos complejos.
Recursos Relacionados
Variables de Entorno
Cómo leer, establecer y gestionar variables de entorno de forma segura en Python, JavaScript y Java.
RecipeFundamentos de Docker
Cómo containerizar una aplicación, escribir un Dockerfile y ejecutar contenedores con Docker Compose.
RecipeProteger APIs con encabezados de seguridad HTTP
Cómo configurar headers de seguridad esenciales como HSTS, CSP y X-Frame-Options para proteger APIs y aplicaciones web de ataques comunes.
RecipePlaybook de Ansible para Configuración de Servidores
Cómo escribir y ejecutar playbooks de Ansible para aprovisionar, configurar y administrar servidores con tareas idempotentes, roles e inventarios.
RecipeConfigurar Certificados SSL con Let's Encrypt
Guía paso a paso para obtener certificados TLS gratuitos de Let's Encrypt con Certbot — en Nginx, Apache, standalone, Docker y Kubernetes.