Skip to content

Credenciales y conexiones a BD

Cómo se manejan usuario/password de SQL Server y Postgres en todos los repos que hablan con las bases de Trivasa, y qué pasó el 2026-08-29 que hizo falta escribir esto.

Regla general

Host, puerto y nombre de base NO son secreto (son topología de red, documentada en Servidores y bases) — se quedan hardcodeados en cada script/archivo. Usuario y password SÍ son secreto — nunca hardcodeados, nunca comiteados a git, siempre por variable de entorno o un archivo de secretos gitignored.

Patrón canónico: trivasa-bi-core

Fuente real de las credenciales: dlt/.dlt/secrets.toml (gitignored). shared/db_credentials.py lo lee y expone dos cuentas nombradas:

from shared import db_credentials
user, password = db_credentials.sa()        # .200 / .204 / .205 (no productivas)
user, password = db_credentials.ealcocer()   # .207 (produccion)

Reusado por connections/connection_<host>_<db>.py (naming explícito, sin ambigüedad — el connection.py genérico sin sufijo se borró 2026-08-27 por ser el más confuso del directorio), por dlt/load_*.py y por observability/checks/*.py. Cada connection_*.py expone engine + q(sql)/show(sql), mismo shape en los 4 archivos.

Este patrón nació de un hallazgo real: git grep (2026-08-27) encontró el mismo usuario/password hardcodeado y duplicado en 12 archivos del repo, ya comiteados a git pese a que este mismo CLAUDE.md lo prohíbe.

Patrón en repos separados (no pueden importar shared/)

varela-bot y streamlit-reportes viven en su propio repo, sin acceso al paquete shared/ de trivasa-bi-core. Usan .env (gitignored) → os.environ, con un prefijo por motor de base:

Motor Prefijo
SQL Server (TRIVASADB) TRIVASADB_HOST, TRIVASADB_PORT, TRIVASADB_DATABASE, TRIVASADB_USER, TRIVASADB_PASSWORD
Postgres PG_HOST, PG_PORT, PG_DATABASE, PG_USER, PG_PASSWORD

Cualquier repo nuevo que no pueda importar shared/db_credentials.py copia este prefijo tal cual — evita que cada repo invente su propio esquema de nombres (antes streamlit-reportes usaba DB_HOST/DB_USER/ DB_PASS genérico, inconsistente con varela-bot; corregido 2026-08-29).

Infisical no se usa para inyectar estas credenciales en runtime. Decisión explícita en varela-bot (2026-08-12): una primera versión sí conectaba vía infisical run y se revirtió por ser más de lo que se pedía. Infisical queda como respaldo de secretos puntuales (tokens, no credenciales de BD que ya tienen su propio .env/secrets.toml como fuente real).

dbt

~/.dbt/profiles.yml, fuera de cualquier repo, nunca se comitea — dbt no lo autogenera, se arma a mano por máquina. Ya es la práctica estándar de dbt, no hace falta nada adicional.

Incidente 2026-08-29 — password de sa expuesto en repo público

docs/proyectos/consumo-interno-fifo/scripts/connection_205_trivasadb3.py en este mismo repo (público en GitHub) tenía el password de sa en texto plano desde antes de la centralización del 2026-08-27 — era una copia manual de trivasa-bi-core/connections/connection_205_trivasadb3.py que nunca se actualizó cuando el original se arregló.

No se pudo rotar el password (sin acceso a la cuenta sa para cambiarlo). Mitigación aplicada en su lugar:

  1. Se arregló el archivo para leer TRIVASADB_USER/TRIVASADB_PASSWORD de variable de entorno, se comiteó y pusheó normal.
  2. Se reescribió toda la historia de git con git-filter-repo (--replace-text, reemplazando el password literal por un placeholder) — afectaba 18 commits.
  3. git push --force --all — verificado después con git grep sobre todos los commits: cero apariciones del password en el historial.
  4. Se borraron 2 archivos más con el mismo password hardcodeado que vivían fuera de cualquier repo git (~/por_ordenar/exploracion/ solicitud-material/scripts/, ~/proyectos/duckdb-trivasadb3-poc/) — nunca estuvieron públicos, pero sin razón para conservarlos.

Repo sin forks (forkCount: 0) al momento del incidente, así que el force push no dejó copias huérfanas de la historia vieja en GitHub. Riesgo residual: cualquier clon local que alguien haya hecho antes de 2026-08-29 conserva la historia vieja con el password — no hay forma de alcanzar esos clones desde aquí.

Lección — regla para copiar connection_*.py entre repos

trivasa-bi-core/connections/ es la fuente. Cuando un proyecto en otro repo necesita una copia (patrón ya documentado en el skill trivasa-streamlit-reportes: "copiado, no symlink"):

  • Nunca copiar la versión con create_engine(url_con_password_inline) — eso fue exactamente lo que pasó aquí.
  • Si el repo destino no puede importar shared/db_credentials.py, la copia debe leer usuario/password de variable de entorno (os.environ), no traerlos embebidos.
  • Antes de comitear una copia nueva, git grep -n "mssql+pymssql://.*:.*@" sobre el archivo — si aparece un match con un password real en vez de una variable, no se comitea.

Pendiente / no resuelto

  • Harlequin (cliente SQL ad-hoc, en otro host, no en ctunlinux): no auditado desde aquí — no se pudo confirmar si su config (~/.config/ harlequin/... u otro) tiene el password en texto plano. Revisar en ese host y aplicar la misma regla (variable de entorno o keychain si el adaptador ODBC lo soporta) si lo tiene.
  • ~/.trivasa/secrets.env (creado 2026-08-04, permisos 600): no se encontró ningún cron ni script activo que lo lea — probable leftover de antes de shared/db_credentials.py. Confirmar que nada lo usa y borrarlo.
  • Password de sa sigue siendo el mismo de antes del incidente — rotarlo en cuanto haya acceso a la cuenta, y actualizar dlt/.dlt/secrets.toml (y su respaldo en Infisical si existe) cuando pase.