Ir al contenido

Sincronización de secretos entre contenedores Docker con Vault y rotación automática

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
#

Hace unos meses tenía un desastre de configuración en mi servidor doméstico. Las contraseñas de bases de datos estaban hardcodeadas en variables de entorno, distribuidas en varios contenedores, y cambiarlas era un dolor de cabeza. Un día necesitaba rotar las credenciales y tuve que reiniciar todo manualmente. Necesitaba algo mejor.

La solución que implementé fue usar HashiCorp Vault como servidor centralizado de secretos con rotación automática. Aquí documento exactamente cómo lo hice.

Instalación de Vault
#

Primero levanté Vault como contenedor:

version: '3.8'
services:
  vault:
    image: vault:latest
    container_name: vault
    ports:
      - "8200:8200"
    environment:
      VAULT_DEV_ROOT_TOKEN_ID: "s.xxxxxxxxxx"
      VAULT_DEV_LISTEN_ADDRESS: "0.0.0.0:8200"
    volumes:
      - vault_data:/vault/data
    cap_add:
      - IPC_LOCK
    networks:
      - servicios

volumes:
  vault_data:

networks:
  servicios:
    driver: bridge

Nota importante: Esto es modo desarrollo. Para producción necesitarías configuración de almacenamiento persistente y TLS adecuado. En mi caso en servidor doméstico uso modo dev pero con volumen persistente.

Configuración de Vault
#

Una vez corriendo, inicialicé Vault:

docker exec -it vault vault operator init
docker exec -it vault vault operator unseal <key>

Luego habilité el motor de secretos dinámicos para PostgreSQL (que es lo que uso en mi setup):

docker exec -it vault vault secrets enable database

Configuré la conexión a PostgreSQL:

docker exec -it vault vault write database/config/postgresql \
  plugin_name=postgresql-database-plugin \
  allowed_roles="readonly,readwrite" \
  connection_url="postgresql://{{username}}:{{password}}@postgres:5432/mydb" \
  username="vault" \
  password="vault_pass_strong"

Creación de roles y credenciales dinámicas
#

Definí roles con permisos específicos:

docker exec -it vault vault write database/roles/readonly \
  db_name=postgresql \
  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"

Las contraseñas se generan automáticamente y expiran según el TTL configurado. Vault renueva el rol antes de la expiración.

Consumo de secretos en contenedores
#

Para que mis aplicaciones accedieran a los secretos, usé Vault Agent como sidecar. Ejemplo con una app Node:

services:
  app:
    image: node:18
    container_name: app
    depends_on:
      - vault
    environment:
      VAULT_ADDR: "http://vault:8200"
      VAULT_TOKEN: "s.xxxxxxxxxx"
    volumes:
      - ./vault-agent.hcl:/etc/vault/agent.hcl
      - ./app:/app
    networks:
      - servicios
    command: sh -c "vault agent -config=/etc/vault/agent.hcl & sleep 2 && node /app/index.js"

El archivo vault-agent.hcl:

vault {
  address = "http://vault:8200"
}

auto_auth {
  method {
    type = "token"
    config = {
      token = "s.xxxxxxxxxx"
    }
  }
}

cache {
  use_auto_auth_token = true
}

listener "unix" {
  address = "/tmp/vault.sock"
  tls_disable = true
}

template {
  source = "/etc/vault/templates/db.tpl"
  destination = "/tmp/secrets.env"
  command = "kill -HUP $(pgrep -f 'node /app')"
}

Rotación automática
#

Vault rota automáticamente las credenciales. Mi aplicación detecta cambios leyendo el archivo generado:

const fs = require('fs');
const path = require('path');

const secretsPath = '/tmp/secrets.env';

function reloadSecrets() {
  const secrets = fs.readFileSync(secretsPath, 'utf8');
  // Parsear y aplicar nuevas credenciales
  Object.assign(process.env, parseEnv(secrets));
  console.log('[SECRETS] Credenciales rotadas exitosamente');
}

fs.watchFile(secretsPath, reloadSecrets);

Resultados
#

Implementar esto me llevó un par de tardes, pero ahora:

  • Centralización: Todos los secretos en un único lugar
  • Rotación automática: Sin intervención manual
  • Auditoría: Vault registra quién accede a qué secretos
  • Escalabilidad: Agregar nuevos contenedores es trivial

¿Qué falta? Hacer backup de los datos de Vault y pensar en recuperación ante desastres, pero eso es otro artículo.