Aprende › Cloud, APIs, forense y más

Gestión de secretos: qué es, cómo falla y cómo se corrige con Vault, SOPS y Sealed Secrets

· 11 min de lectura

Riesgos de secretos hardcodeados y archivos .env, arquitectura de HashiCorp Vault (seal/unseal, dynamic secrets, transit engine), SOPS y Sealed Secrets para Kubernetes, y rotación automática de credenciales.

El secreto peor guardado de la industria del software es que los secretos están mal guardados. Contraseñas de bases de datos en archivos .env que acaban en Git, API keys de producción en variables de entorno visibles para cualquier proceso, tokens de servicio que no se rotan desde que se crearon hace tres años. Según un análisis de GitGuardian de 2024, se detectaron más de 12,8 millones de secretos expuestos en repositorios públicos de GitHub. Cada uno es una puerta abierta a la infraestructura de alguien.

La gestión de secretos no es instalar un vault y olvidarse. Es un sistema completo que abarca cómo se generan, almacenan, distribuyen, rotan y revocan las credenciales. Esta lección cubre los riesgos de las prácticas comunes, la arquitectura de las soluciones modernas (HashiCorp Vault, SOPS, Sealed Secrets) y las técnicas para mantener los secretos fuera del código y de las manos equivocadas. Es la decimocuarta lección de la Tanda 10.

Toda la información de esta lección es para mejorar la seguridad de tu propia infraestructura. Acceder a secretos ajenos sin autorización es intrusión.

Qué es la gestión de secretos#

La gestión de secretos (secrets management) es el conjunto de prácticas, herramientas y políticas que controlan el ciclo de vida de las credenciales sensibles: contraseñas, API keys, tokens, certificados, claves de cifrado y cualquier dato que otorgue acceso a un recurso protegido.

Arquitectura de gestión de secretos: comparación entre .env, variables de entorno, Vault y SOPS
Arquitectura de gestión de secretos: comparación entre .env, variables de entorno, Vault y SOPS

Un sistema de gestión de secretos resuelve cinco problemas fundamentales:

  • Almacenamiento seguro: los secretos se guardan cifrados en un backend dedicado, no en texto plano en el sistema de archivos.
  • Control de acceso: cada aplicación, servicio o persona accede solo a los secretos que necesita (principio de mínimo privilegio).
  • Auditoría: cada acceso a un secreto queda registrado (quién, cuándo, desde dónde).
  • Rotación: los secretos se cambian periódicamente sin intervención manual y sin downtime.
  • Revocación: si un secreto se compromete, se invalida inmediatamente y se emite uno nuevo.

Sin un sistema formal, los secretos acaban dispersos en archivos de configuración, variables de entorno, wikis internas, mensajes de Slack, scripts de deployment y, peor aún, en el historial de Git.

Por qué ocurre: la causa raíz#

Los secretos se filtran porque el camino más fácil es el más inseguro:

  • Hardcoding: escribir la credencial directamente en el código fuente es lo más rápido durante el desarrollo. El desarrollador planea "cambiarlo después" y nunca lo hace.
  • Archivos .env en el repositorio: .env nació como convención para separar configuración del código, pero si no está en .gitignore (o si se añadió antes de configurar el gitignore), acaba en el historial.
  • Variables de entorno sin protección: las variables de entorno son visibles para cualquier proceso hijo, se pueden leer con /proc/PID/environ en Linux y se vuelcan en crash reports y logs de depuración.
  • Falta de rotación: credenciales que no se cambian durante años. Si alguna vez se filtraron (y probablemente lo hicieron), siguen siendo válidas.
  • Secretos compartidos entre entornos: la misma API key para desarrollo, staging y producción. Un leak en dev compromete prod.
  • Falta de cultura de seguridad: equipos donde "funciona" es más importante que "es seguro". Sin revisión de seguridad en el pipeline de CI/CD, los secretos pasan desapercibidos.

Ejemplo de secreto hardcodeado que GitGuardian detecta miles de veces al día:

# config.py - NO hagas esto
DATABASE_URL = "postgresql://admin:[email protected]:5432/produccion"
AWS_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"
AWS_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"

Tipos y variantes#

Práctica inseguraRiesgoProbabilidad de filtración
Hardcoded en código fuenteUn git push lo publica para siempre, incluso si se borra despuésMuy alta
Archivo .env en el repositorioCualquiera con acceso al repo ve las credencialesAlta
Variables de entorno sin cifrarVisibles en /proc, crash dumps, logs y herramientas de orquestaciónMedia
Secretos en CI/CD (sin vault)Variables de pipeline visibles en logs de build o para todos los contribuidoresMedia
Wiki o Confluence internoSecretos en texto plano accesibles para toda la organizaciónAlta
Secretos en Docker image layersAunque se borren en un layer posterior, persisten en layers anterioresAlta
Secretos en Kubernetes Secrets sin cifradobase64 no es cifrado: cualquiera con acceso al namespace los decodificaAlta

Dónde aparece#

  • Repositorios de GitHub/GitLab: la fuente principal de filtraciones. Commits antiguos, ramas abandonadas, forks públicos de repos privados.
  • Docker Hub: imágenes públicas con credenciales embebidas en variables de entorno o en layers intermedios.
  • Kubernetes clusters: Secrets sin encryption at rest, ConfigMaps con credenciales, service account tokens con permisos excesivos.
  • Pipelines de CI/CD: Jenkins, GitHub Actions, GitLab CI con secretos en logs de build o en configuración accesible.
  • Aplicaciones web: archivos de configuración expuestos por path traversal o directory listing (config.php, .env, settings.py).
  • Infraestructura como código: archivos Terraform con provider credentials, Ansible playbooks con contraseñas en texto plano.

Cómo se detecta#

HerramientaQué buscaIntegración
GitGuardianMás de 350 tipos de secretos en repos GitPre-commit hook, CI/CD, GitHub App
TruffleHogSecretos en historial de Git completo, incluyendo commits borradosCLI, GitHub Actions
gitleaksSecretos con reglas regex personalizablesPre-commit hook, CI/CD
detect-secrets (Yelp)Secretos en archivos de código con baseline para falsos positivosPre-commit hook
SemgrepReglas de secretos hardcodeados en código fuenteCLI, CI/CD
KICS (Checkmarx)Secretos en IaC (Terraform, CloudFormation, Kubernetes)CLI, CI/CD

Escaneo manual#

Busca patrones comunes en tu codebase:

# Buscar posibles API keys o tokens
grep -rn "AKIA[A-Z0-9]\{16\}" .
grep -rn "password\s*=\s*['\"]" . --include="*.py" --include="*.js" --include="*.php"
grep -rn "sk-[a-zA-Z0-9]\{48\}" .

# Buscar archivos .env en el repositorio
git log --all --full-history -- "*/.env"

Explotación paso a paso en un laboratorio#

Paso 1 — Buscar secretos expuestos#

En el laboratorio, escanea la aplicación web buscando archivos de configuración expuestos:

# Buscar archivos comunes de configuración
curl -s http://IP_DEL_LAB/.env
curl -s http://IP_DEL_LAB/config.php.bak
curl -s http://IP_DEL_LAB/wp-config.php.old
curl -s http://IP_DEL_LAB/.git/config

Si la aplicación tiene un repositorio Git expuesto, extrae el historial completo:

# Descargar el repositorio Git expuesto
git-dumper http://IP_DEL_LAB/.git/ repo_dumped/

# Buscar secretos en el historial
cd repo_dumped
trufflehog filesystem . --json | head -20

Paso 2 — Buscar secretos en variables de entorno#

Si tienes ejecución de comandos (command injection, web shell), lee las variables de entorno:

# Desde una shell en el servidor
env | grep -iE "(key|secret|password|token|api)"
cat /proc/self/environ | tr '\0' '\n' | grep -iE "(key|secret|password|token)"

En contenedores Docker, busca secretos montados:

# Secretos montados como archivos en Kubernetes
ls -la /run/secrets/
cat /var/run/secrets/kubernetes.io/serviceaccount/token

# Variables de entorno del contenedor
printenv | sort

Paso 3 — Escalar con credenciales encontradas#

Con las credenciales obtenidas, intenta acceder a otros servicios:

# Si encontraste credenciales de base de datos
psql -h IP_DEL_LAB -U admin -d produccion

# Si encontraste un AWS access key
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
aws sts get-caller-identity
aws s3 ls

Paso 4 — Buscar secretos en Kubernetes (si aplica)#

Si el laboratorio tiene un cluster Kubernetes accesible:

# Listar secrets del namespace
kubectl get secrets -n default
kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d

Recuerda que los Kubernetes Secrets se almacenan en base64, no cifrados. Cualquiera con permisos de lectura en el namespace puede decodificarlos.

Qué puede lograr un atacante#

Secreto obtenidoImpacto
Credenciales de base de datos de producciónExfiltración, modificación o borrado de todos los datos
AWS/GCP/Azure access keysControl total de la infraestructura cloud: crear instancias, leer S3, modificar IAM
API keys de servicios externos (Stripe, SendGrid, Twilio)Fraude financiero, envío de correos maliciosos, acceso a datos de clientes
Tokens de servicio (JWT signing key)Forjar tokens de cualquier usuario, acceso administrativo total
Claves SSH privadasAcceso a servidores sin contraseña
Certificados TLS privadosMan-in-the-middle contra servicios cifrados

Cómo se corrige#

HashiCorp Vault: arquitectura de seal/unseal, dynamic secrets y transit engine
HashiCorp Vault: arquitectura de seal/unseal, dynamic secrets y transit engine

HashiCorp Vault#

Vault es el estándar de la industria para gestión centralizada de secretos. Su arquitectura separa el almacenamiento cifrado, el control de acceso y la auditoría:

# Iniciar Vault en modo desarrollo (solo para laboratorio)
vault server -dev -dev-root-token-id="root-token-lab"

# En producción: inicializar con Shamir's Secret Sharing
vault operator init -key-shares=5 -key-threshold=3

El mecanismo de seal/unseal protege la master key: Vault divide la clave maestra en N shares (por defecto 5) y requiere un umbral mínimo (por defecto 3) para desbloquear el vault. Ningún individuo puede acceder a los secretos solo.

Dynamic secrets generan credenciales únicas y temporales para cada cliente:

# Habilitar el motor de secretos de base de datos
vault secrets enable database

# Configurar conexión a PostgreSQL
vault write database/config/mydb \
  plugin_name=postgresql-database-plugin \
  connection_url="postgresql://{{username}}:{{password}}@db.lab:5432/app" \
  allowed_roles="readonly" \
  username="vault_admin" \
  password="vault_password"

# Crear rol con TTL de 1 hora
vault write database/roles/readonly \
  db_name=mydb \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="24h"

# Solicitar credenciales temporales
vault read database/creds/readonly

Cada solicitud genera un usuario y contraseña nuevos que expiran automáticamente. Si se filtran, solo son válidos durante el TTL.

Transit engine cifra datos sin exponer la clave:

# Habilitar transit engine
vault secrets enable transit

# Crear clave de cifrado
vault write -f transit/keys/mi-app

# Cifrar un valor
vault write transit/encrypt/mi-app plaintext=$(echo "dato-sensible" | base64)

# Descifrar
vault write transit/decrypt/mi-app ciphertext="vault:v1:..."

SOPS (Secrets OPerationS)#

SOPS cifra archivos de configuración selectivamente: solo los valores, no las claves. Compatible con AWS KMS, GCP KMS, Azure Key Vault y age/PGP:

# Crear archivo de configuración SOPS
cat > .sops.yaml <<'EOF'
creation_rules:
  - path_regex: \.env\.enc$
    age: >-
      age1abc123...
EOF

# Cifrar un archivo .env
sops --encrypt --in-place config.env.enc

# Editar (descifra en memoria, recifra al guardar)
sops config.env.enc

# Usar en CI/CD
sops --decrypt config.env.enc > .env

La ventaja de SOPS es que el archivo cifrado se puede versionar en Git sin riesgo. Solo quien tenga la clave KMS o la clave age puede descifrar los valores.

Sealed Secrets para Kubernetes#

Sealed Secrets (Bitnami) resuelve el problema de los Kubernetes Secrets en Git. Un controlador en el cluster tiene la clave privada; los desarrolladores cifran con la clave pública:

# Instalar el controlador en el cluster
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.27.3/controller.yaml

# Crear un Secret normal
kubectl create secret generic db-creds \
  --from-literal=password=SuperSecretPwd123 \
  --dry-run=client -o yaml > secret.yaml

# Cifrarlo con kubeseal
kubeseal --format yaml < secret.yaml > sealed-secret.yaml

# El sealed-secret.yaml se puede versionar en Git
# Solo el controlador del cluster puede descifrarlo
kubectl apply -f sealed-secret.yaml

Pre-commit hooks para prevenir filtraciones#

Instala un hook que bloquee commits con secretos antes de que lleguen al repositorio:

# Instalar gitleaks como pre-commit hook
cat > .pre-commit-config.yaml <<'EOF'
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.21.2
    hooks:
      - id: gitleaks
EOF

pre-commit install

Rotación automática#

Configura rotación periódica de secretos sin intervención manual:

Vault: Dynamic secrets con TTL corto (1h para DB, 8h para API keys)
AWS: IAM Access Keys con rotación cada 90 días vía AWS Config rule
Kubernetes: CertManager para renovación automática de certificados TLS
CI/CD: GitHub Actions secrets con rotación trimestral y alerta por antiguedad

Errores comunes al defenderse#

  • Cifrar con base64 y creer que es seguro: base64 es encoding, no cifrado. Los Kubernetes Secrets en base64 se decodifican con un comando.
  • Poner el vault token en una variable de entorno sin proteger: si el token de Vault está expuesto, el atacante accede a todos los secretos que ese token pueda leer.
  • No habilitar encryption at rest en etcd: Kubernetes almacena Secrets en etcd. Sin encryption at rest, un backup de etcd expone todos los secretos en texto plano.
  • Rotar la credencial pero no revocar la antigua: si solo generas una nueva sin invalidar la anterior, ambas funcionan.
  • Compartir secretos por Slack o correo: los mensajes se almacenan en servidores de terceros, se indexan, se buscan y no se borran.
  • No auditar los accesos a Vault: si nadie revisa los logs de auditoría, un acceso anómalo pasa desapercibido.
  • Un solo unseal key holder: si la persona que tiene las unseal keys deja la empresa o no está disponible, el vault queda sellado. Distribuye las shares entre al menos 3-5 personas.

Preguntas frecuentes#

Es seguro usar archivos .env si están en .gitignore?#

Es mejor que no tenerlo en .gitignore, pero sigue siendo inseguro. El archivo .env está en texto plano en el disco del servidor, es visible para cualquier proceso con acceso al filesystem, y si alguien añadió el archivo antes de configurar .gitignore, ya está en el historial de Git para siempre.

Qué diferencia hay entre Vault y un gestor de contraseñas como 1Password?#

Un gestor de contraseñas almacena secretos estáticos que un humano recupera manualmente. Vault está diseñado para que las aplicaciones y servicios obtengan credenciales programáticamente, con control de acceso granular, auditoría, rotación automática y dynamic secrets (credenciales que se generan on-demand y expiran). Son complementarios: 1Password para humanos, Vault para máquinas.

Los dynamic secrets de Vault eliminan el riesgo de filtración?#

Reducen drásticamente el impacto. Si una credencial dinámica se filtra, solo es válida durante su TTL (por ejemplo, 1 hora). Pasado ese tiempo, expira automáticamente. No elimina el riesgo por completo (durante la hora de validez sigue siendo explotable), pero convierte una brecha permanente en una ventana temporal corta.

SOPS o Sealed Secrets: cuál usar?#

SOPS es agnóstico de plataforma: funciona con cualquier archivo de configuración y se integra con múltiples proveedores de KMS. Sealed Secrets es específico de Kubernetes. Si tu infraestructura es 100 % Kubernetes, Sealed Secrets es más sencillo. Si tienes una mezcla de entornos (VMs, contenedores, serverless), SOPS es más flexible. Ambos son compatibles y se pueden usar juntos.

Cómo gestiono secretos en GitHub Actions?#

Usa los Repository Secrets o Environment Secrets de GitHub, que se inyectan como variables de entorno cifradas en el runner. Para mayor seguridad, integra con un vault externo usando la acción oficial de HashiCorp Vault o aws-actions/configure-aws-credentials con OIDC federation (sin access keys estáticas).

Resumen#

  • Los secretos hardcodeados y los archivos .env son la primera causa de filtraciones de credenciales. GitGuardian detecta millones de secretos expuestos en GitHub cada año.
  • HashiCorp Vault centraliza la gestión con seal/unseal (Shamir), dynamic secrets (credenciales temporales) y transit engine (cifrado sin exponer claves).
  • SOPS cifra selectivamente archivos de configuración para versionarlos en Git de forma segura.
  • Sealed Secrets resuelve el problema de los Kubernetes Secrets en repositorios de GitOps.
  • La rotación automática y la revocación inmediata convierten una filtración permanente en una ventana temporal corta.
  • Los pre-commit hooks (gitleaks, TruffleHog) son la primera línea de defensa para evitar que los secretos lleguen al repositorio.

Fuentes y lecturas recomendadas#

Practícalo en estos laboratorios

Ver todos los laboratorios de esta categoría