Skip to main content
Fetcher guarda credenciales de bases de datos que no controla, y mueve filas que salieron de ellas. Esta página describe qué protege cada una de esas cosas, y qué tiene que hacer un operador.

Una clave maestra, cuatro claves derivadas


APP_ENC_KEY es la única clave que provees. Genérala con make generate-master-key, que produce un valor de 32 bytes codificado en base64. Define el mismo valor en el Manager y en el Worker. Fetcher nunca usa esa clave de forma directa. La expande con HKDF-SHA256 (RFC 5869) en cuatro claves independientes, una por propósito. La separación es el punto. Un consumidor que tiene la clave externa puede verificar la firma de un resultado. No puede descifrar una credencial almacenada, y no puede falsificar un mensaje entre los dos servicios.
Una clave maestra incorrecta detiene el servicio. Una clave sin definir, un Base64 inválido o un valor por debajo de 32 bytes terminan el proceso al arrancar. Un valor Base64 inválido falla al decodificar; master key too short: got 0 bytes, minimum 32 required corresponde a una clave vacía o corta después de decodificar. Fetcher no tiene modo alternativo en texto plano.

Versión de clave y rotación


APP_ENC_KEY_VERSION etiqueta la clave que está en vigor. Cada registro de conexión guarda la versión que cifró su contraseña, de modo que un operador puede saber a qué clave pertenece un registro. Incrementa la versión cuando cambies la clave maestra. Fetcher no conserva claves de credenciales anteriores. Después de cambiar la clave maestra y su versión, vuelve a proporcionar cada contraseña conocida para actualizar y cifrar de nuevo la conexión con la versión actual, o elimina y vuelve a crear la conexión de forma deliberada. No esperes una migración automática de claves. La clave HMAC externa cambia junto con la clave maestra. Deriva la clave nueva y entrégala a cada consumidor que verifica firmas. Los resultados anteriores se verifican contra la clave anterior. Genera la clave externa con make derive-key KEY="<tu-clave-maestra-base64>". La herramienta también lee APP_ENC_KEY del entorno o la clave desde la entrada estándar, e imprime una clave hexadecimal de 64 caracteres.

Credenciales en reposo


La contraseña de un datasource nunca llega a MongoDB en claro. El Manager la cifra con AES-256-GCM bajo la clave de credenciales derivada, y después guarda el texto cifrado y la versión de la clave. En modo single-tenant, los datasources internos fijos se cargan desde variables de entorno DATASOURCE_{NAME}_*. En modo multi-tenant, Fetcher los resuelve por tenant mediante Tenant Manager. En ambos modos son conexiones internas en memoria, con una versión de clave vacía y sin registro de conexión en MongoDB.

Resultados en reposo


El Worker protege un resultado almacenado en dos pasos:
  1. Firma el JSON en texto plano con HMAC-SHA256 bajo la clave externa derivada, y registra el algoritmo y la firma junto con el resultado.
  2. Cifra el payload con AES-GCM bajo la clave de almacenamiento derivada, con un nonce aleatorio nuevo de 12 bytes, y guarda el resultado codificado en base64.
La firma cubre el texto plano, así que un consumidor verifica los datos que recibió y no el sobre que los envuelve. El repositorio incluye una guía de verificación en scripts/crypto/derive-key/verification-guide.md. El modo directo devuelve las filas en línea sin cifrado. El engine las reporta como texto plano y les adjunta un digest SHA-256 sobre los bytes exactos.

Mensajes firmados entre los servicios


Cada mensaje de RabbitMQ que el Manager publica hacia el Worker lleva una firma HMAC-SHA256 bajo la clave interna derivada. La firma cubre el timestamp, la versión de la firma, el identificador de tenant, el exchange, la routing key y el cuerpo del mensaje. La vinculación de contexto evita que un mensaje firmado se redirija a otro tenant o ruta. La vigencia rechaza por separado mensajes de más de cinco minutos y mensajes con más de 30 segundos de desfase futuro. Por sí sola, la vinculación no evita repetir el mismo mensaje en el mismo contexto durante esa ventana. De forma predeterminada, el Worker exige este sobre canónico ligado al tenant y a la ruta. RABBITMQ_ALLOW_LEGACY_BODY_SIGNATURE_FALLBACK=true es una configuración temporal de migración que también acepta una firma heredada solo del cuerpo para drenar mensajes ya encolados. El publicador elimina headers de seguridad enviados por quien llama antes de firmar, y el firmante compara firmas en tiempo constante.

Validación de host de datasource


Un tenant que registra su propia conexión podría apuntarla a tu red interna. Con MULTI_TENANT_ENABLED=true, Fetcher revisa el host antes de conectarse. La validación corre en dos capas:
  1. Al parsear la petición. Fetcher rechaza una dirección IP literal en un rango bloqueado, sin resolución DNS.
  2. En la fábrica de datasources. Fetcher revisa el nombre de host contra una lista de bloqueo que cubre localhost, nombres de metadatos de nube y los sufijos .local, .internal y .cluster.local. Después resuelve el nombre de host y revisa cada dirección que obtiene.
Fetcher rechaza un host bloqueado con un error de host prohibido. Delega la clasificación de hostnames e IP a su dependencia SSRF de lib-commons. Los datasources internos quedan exentos de forma deliberada: en modo single-tenant provienen del entorno del operador, y en modo multi-tenant los resuelve Tenant Manager.

Aislamiento de tenant


El Engine acota cada operación por identificador de tenant, y ese identificador es el único límite de aislamiento dentro del Engine. Un identificador de tenant mal formado falla antes de que Fetcher toque cualquier recurso. Fetcher también aplica propiedad por producto en la capa host: una conexión externa pertenece a un producto y el metadata.source de un job debe coincidir; los datasources internos son la excepción. En modo multi-tenant, Fetcher resuelve los recursos de metadatos a partir del contexto de tenant que suministra el middleware de tenant. Los consumidores Worker usan su contexto de tenant autoritativo. No atribuyas la resolución de tenant específicamente a claims de JWT salvo que el propietario del middleware documente ese contrato. El acceso falla cerrado: una solicitud sin base de datos de tenant resuelta devuelve un error en lugar de leer una base de datos compartida.

Autenticación y superficies de sonda


PLUGIN_AUTH_ENABLED=true pone el middleware de Access Manager delante de la API. Las peticiones llevan entonces un token bearer, y Fetcher autoriza cada operación contra un recurso y una acción. /health, /readyz, /readyz/tenant/:id, /metrics y /version se montan antes de ese middleware, así que las sondas de Kubernetes y del balanceador de carga siguen sin autenticación.

Los errores nunca filtran material de conexión


Fetcher descarta el error crudo del driver en la frontera del engine y devuelve un mensaje fijo en su lugar. Un error crudo del driver puede incrustar un DSN o una credencial, así que quien llama ve failed to connect to datasource en lugar de la cadena que produjo el driver. Los fallos llegan clasificados en categorías estables — validación, no autorizado, prohibido, límite excedido, conexión, timeout y otras — así que un host las mapea a sus propios códigos de estado sin parsear texto.

Próximos pasos


Configuración

Todas las variables de entorno, por componente.

Despliegue

Dependencias, retención en almacenamiento, escalado y verificaciones de arranque.

Observabilidad

Sondas, comportamiento de drenaje, métricas y trazas.

Conexiones

Registra, prueba y usa una conexión.