query-api — puente SQL de solo lectura para Cowork
Puente HTTP genérico entre Cowork (Claude en la nube, solo tiene salida HTTPS, sin red
hacia ctunlinux) y las bases de Trivasa: el warehouse Postgres
(trivasa_dw) y SQL Server (.207 producción, .205 staging). Código en
trivasa-bi-core/query_api/, documentado a fondo en
query-api dentro de trivasa-context
(docs/proyectos/query-api/index.md) — esta página es el resumen de infraestructura
del lado de ctunlinux, no repite las reglas de seguridad completas.
No expone endpoints por tabla: un único POST /query acepta cualquier
SELECT/WITH ... SELECT, para que quien lo consuma explore el esquema y arme
cualquier query de solo lectura sin pedir cambios en la API.
Arquitectura
Cowork -> reportesweb.frento.com.mx (cloudflared) -> query-api:8765 (uvicorn)
│
├── postgres_dw -> trivasa_dw (rol ealcocer_ro)
├── mssql_207 -> TRIVASADB (producción real)
└── mssql_205 -> TRIVASADB3 (staging)
Servicio systemd (query-api.service, enabled + active), no un proceso suelto ni
un contenedor Docker — mismo patrón que cloudflared.service en este host. Escucha
solo en 127.0.0.1:8765, nunca 0.0.0.0: todo el tráfico entrante pasa por el túnel.
Solo lectura, en dos capas
Filtro de texto (bloquea todo lo que no sea un único SELECT/WITH, sin ; en medio)
más una segunda capa real de conexión: Postgres abre la transacción en modo
postgresql_readonly=True; para SQL Server (pymssql no soporta un modo read-only real
por sesión) la red de seguridad es nunca llamar commit() — SQLAlchemy hace rollback
de lo que quede abierto al cerrar la conexión. Probado real, no solo revisado el
código: DELETE/UPDATE/INSERT/multi-statement contra postgres_dw devuelven
HTTP 400 sin tocar la base, tanto en local como a través del túnel público.
Publicación: mismo túnel de ctunlinux
reportesweb.frento.com.mx → http://127.0.0.1:8765 es una regla más del único
cloudflared.service de este host (ver ctunlinux § túnel), mismo
patrón que Metabase/Perses/Lightdash/varela-bot — no se creó túnel
nuevo. Ese hostname ya estaba reservado (“pruebas de API ad-hoc”) de una pasada
anterior; este servicio es lo primero real que lo ocupa. Antes del despliegue, el
puerto 8765 lo tenía tomado un script de prueba stdlib (~/api-cowork/api.py,
/health//ping//echo) que se dio de baja para dejarle el lugar — volvió a
levantarse solo una vez durante el arranque (relanzado a mano fuera de la sesión que
montó query-api, sin cron/systemd/watchdog de por medio), vale la pena confirmar que
no se vuelva a correr por error mientras el puerto siga en uso real.
Secretos: dos proyectos de Infisical distintos, encadenados
A diferencia del resto de los servicios de este host (que usan un solo proyecto de
Infisical o ninguno), query-api necesita secretos de dos proyectos al mismo
tiempo: Trivasa (b6567423-9986-448e-b2b8-dffe44fe1657, las credenciales del rol
ealcocer_ro de Postgres) y secret-management (2aefdbd1-389c-4fd0-bdb8-a5621af8aac1,
el bearer token QUERY_API_TOKEN — mismo proyecto que usa
varela-bot para su TELEGRAM_BOT_TOKEN). infisical run no
soporta apuntar a dos proyectos en una sola invocación, así que el ExecStart de la
unidad encadena dos llamadas anidadas, la de afuera envolviendo a la de adentro, que a
su vez envuelve a uvicorn.
Gotcha real (2026-09-07): el EnvironmentFile de systemd no acepta el prefijo
export, pese a que la documentación de systemd.exec dice que sí — en la práctica
lo ignora en silencio (Ignoring invalid environment assignment) en vez de fallar
fuerte, y el servicio entraba en loop de reinicio pensando que no había sesión de
login de Infisical. El archivo que usa el shell interactivo
(~/.config/infisical/ehas-uk.env, formato export KEY=valor) no sirve tal cual para
EnvironmentFile= — hizo falta un segundo archivo,
~/.config/infisical/ehas-uk-systemd.env, en texto plano KEY=valor sin export,
dedicado solo a esta unidad.
Auditoría
Cada consulta se loguea a Loki (job=query_api) y a
trivasa-bi-core/logs/query_api.log — mismo criterio best-effort que el resto de
observability/checks/*.py de ese repo: si Loki está caído, solo se advierte, nunca
tumba la consulta.
Véase también
- ctunlinux — el host y el túnel que publica
reportesweb.frento.com.mx - trivasa-context — la documentación completa del proyecto (reglas de seguridad, endpoints, rotación del token) vive ahí, no aquí
- varela-bot — el otro servicio de este host que también respalda un
secreto en el proyecto
secret-managementde Infisical - Gestión de secretos — por qué las credenciales de Postgres van por Infisical pero las de SQL Server no