Ir al contenido

Recuperación de secretos comprometidos en Docker: rotación de credenciales sin downtime usando Vault y webhooks

·3 mins
Rogelio Guerra Riverón
Autor
Rogelio Guerra Riverón
Construyendo mi propia infraestructura web desde cero. Aquí documento cada paso: servidores, redes, contenedores y lo que vaya surgiendo.

El problema real
#

Descubrí que una de mis credenciales de base de datos estaba expuesta en un repositorio público. No fue divertido. Lo peor no fue el acceso no autorizado, sino darme cuenta de que rotar esa credencial significaría derribar todos mis contenedores en producción. No tenía tiempo para eso.

Así que decidí implementar un sistema de rotación de secretos sin downtime. Aquí está lo que hice.

Por qué Vault
#

HashiCorp Vault centraliza la gestión de secretos. En lugar de tener credenciales hardcodeadas o en variables de entorno, mis contenedores ahora las solicitan a Vault en tiempo de ejecución. Cuando cambia un secreto, no necesito redesplegable nada.

Arquitectura de la solución
#

La idea es simple:

  1. Vault almacena todos los secretos
  2. Los contenedores leen de Vault, no de variables de entorno
  3. Cuando un secreto se compromete, lo roto en Vault
  4. Un webhook notifica a los contenedores activos
  5. Los contenedores reconectan con nuevas credenciales sin reiniciarse

Instalación de Vault
#

Instalé Vault localmente con Docker:

version: '3.8'
services:
  vault:
    image: vault:latest
    ports:
      - "8200:8200"
    environment:
      VAULT_DEV_ROOT_TOKEN_ID: "myroot-token"
      VAULT_DEV_LISTEN_ADDRESS: "0.0.0.0:8200"
    command: server -dev
    volumes:
      - vault_data:/vault/file
    networks:
      - servicios

volumes:
  vault_data:

networks:
  servicios:
    driver: bridge

Accedí a http://localhost:8200 y activé el motor de secretos KV:

vault secrets enable -version=2 kv
vault kv put kv/database/prod username=dbuser password=oldpassword host=db.local

Adaptación del cliente Docker
#

Mis aplicaciones ahora incluyen un cliente que se conecta a Vault. Usé un sidecar en Python para este propósito:

import hvac
import os
import signal
import time

class VaultClient:
    def __init__(self):
        self.client = hvac.Client(url='http://vault:8200', token=os.getenv('VAULT_TOKEN'))
        self.secret_path = os.getenv('SECRET_PATH', 'kv/database/prod')
        self.cache = {}
        
    def get_secret(self):
        try:
            response = self.client.secrets.kv.read_secret_version(path=self.secret_path)
            return response['data']['data']
        except Exception as e:
            print(f"Error reading secret: {e}")
            return self.cache.get('last_known_good', {})
    
    def watch_for_updates(self, callback):
        while True:
            new_secret = self.get_secret()
            if new_secret != self.cache:
                self.cache = new_secret
                callback(new_secret)
            time.sleep(10)

Mi aplicación no se reinicia, simplemente recibe la notificación y reconecta:

def on_secret_change(new_creds):
    global db_connection
    print(f"Rotating credentials...")
    db_connection.close()
    db_connection = connect_database(new_creds)
    print(f"New connection established")

vault = VaultClient()
thread = threading.Thread(target=vault.watch_for_updates, args=(on_secret_change,), daemon=True)
thread.start()

Rotación real
#

Cuando descubrí que un secreto estaba comprometido, simplemente ejecuté:

vault kv put kv/database/prod username=dbuser password=newpassword host=db.local

Mi sidecar detectó el cambio en el siguiente ciclo de verificación (máximo 10 segundos después). La aplicación cerró la conexión anterior y estableció una nueva con las credenciales actualizadas.

Sin downtime. Sin redesplegable.

Webhook para urgencias
#

Para situaciones críticas, implementé un webhook que Vault llama cuando se rotarota un secreto:

vault write -f auth/token/renew
# Trigger webhook manualmente
curl -X POST http://localhost:8000/rotate-secret \
  -H "Content-Type: application/json" \
  -d '{"secret_path":"kv/database/prod"}'

El webhook puede forzar una reconexión inmediata en lugar de esperar al siguiente ciclo de verificación.

Lecciones aprendidas
#

  • No confíes en variables de entorno para secretos sensibles
  • La rotación sin downtime es posible si planificas desde el inicio
  • Vault tiene overhead, pero vale la pena por la seguridad
  • Mantén un mecanismo de fallback en caso de que Vault falle

Ahora duermo mejor sabiendo que puedo rotar cualquier credencial comprometida en segundos sin afectar mis servicios.