Cuando metes PgBouncer entre Odoo y PostgreSQL, la pregunta real no es “¿conecta?”, sino:
¿quién puede conectarse?
¿dónde se guardan las credenciales?
¿qué permisos mínimos tiene cada rol?
¿qué pasa cuando rotas passwords?
Este post te deja un baseline de usuarios/roles, cómo mantener auth_file sano y una seguridad mínima que evita los errores más comunes (y los sustos).
1) Modelo mental rápido: hay 2 autenticaciones
Con PgBouncer, siempre hay dos “logins”:
Cliente → PgBouncer (Odoo autentica contra PgBouncer)
PgBouncer → PostgreSQL (PgBouncer abre conexiones reales al servidor)
Por eso tus decisiones de usuarios/auth_file afectan a dos enlaces, no uno. Crunchy Data
2) Usuarios recomendados (mínimos) y para qué sirve cada uno
A) odoo (usuario de aplicación)
Es el usuario con el que Odoo trabaja en la base. Debe tener permisos sobre su DB (tablas/funciones/secuencias que usa Odoo).
Recomendación: un usuario por instancia o por “grupo” de instancias (multi-tenant), según tu operación.
B) pgbouncer_auth (usuario técnico para auth_user / auth_query) — opcional, pero muy útil
Si quieres centralizar credenciales y no vivir sincronizando userlist.txt, PgBouncer puede consultar la contraseña en PostgreSQL usando auth_user + auth_query. PgBouncer+1
Ventaja: userlist.txt queda con muy pocos usuarios (y rotar claves se simplifica).
C) postgres / DBA (solo admin)
No lo uses para Odoo ni para PgBouncer.
3) auth_file (userlist.txt): formato, permisos y buenas prácticas
Formato correcto
PgBouncer documenta el formato así:
"username" "password" (texto plano)
"username" "md5..." (MD5 estilo Postgres)
"username" "SCRAM-SHA-256$<iterations>:<salt>$<storedkey>:<serverkey>" PgBouncer
Ejemplo:
"odoo" "SCRAM-SHA-256$4096:....$....:...." "pgbouncer_auth" "super-segura-en-otro-sistema" ; si decides usar texto plano (ver nota abajo)
Nota: PgBouncer “ignora el resto de la línea” después del segundo campo, así que puedes comentar a la derecha sin romper formato. PgBouncer
Permisos del archivo (seguridad mínima real)
Propietario: el usuario del servicio (ej. pgbouncer)
Permisos: 0600 (solo lectura/escritura dueño)
Ubicación: fuera de repositorios y backups “abiertos”
Esto es de lo más importante: si alguien lee ese archivo, puede abrirte la DB.
Rotación sin downtime
PgBouncer puede recargar el auth_file con reload (sin reiniciar duro). El doc de uso indica que el reload también recarga auth_file y auth_hba_file. PgBouncer
4) SCRAM, auth_user y la trampa más común
Si usas auth_type = scram-sha-256 (lo recomendado hoy), aparecen dos realidades:
Para validar al cliente, userlist.txt puede contener un SCRAM secret. PgBouncer+1
Para que PgBouncer se autentique hacia PostgreSQL usando auth_user, a veces necesitará texto plano (depende de cómo lo configures y del flujo exacto), porque el SCRAM secret no siempre es reutilizable como “credencial de login” por sí solo. Stack Overflow+1
Regla práctica para evitar dolores:
Si NO necesitas auth_user/auth_query: mantén todo simple con auth_file (SCRAM secrets) y listo.
Si SÍ necesitas auth_user/auth_query: revisa bien el caso, porque puedes terminar necesitando la password en texto plano del auth_user en userlist.txt (y eso exige aún más cuidado con permisos/secret management). GitHub+1
5) Opción 1 (simple): todo en auth_file (sin auth_user)
PgBouncer (pgbouncer.ini)
[pgbouncer] auth_type = scram-sha-256 auth_file = /etc/pgbouncer/userlist.txt pool_mode = transaction
Pros
Simple de entender y depurar.
No dependes de queries a DB para auth.
Contras
Cada rotación de password implica actualizar userlist.txt (y recargar PgBouncer).
6) Opción 2 (recomendada en equipos grandes): auth_user + auth_query para centralizar credenciales
La idea: PgBouncer consulta a Postgres el hash/password del usuario que intenta entrar, usando un usuario técnico (auth_user). PgBouncer lo soporta desde hace años (parámetros auth_user y auth_query). PgBouncer+2Crunchy Data+2
PgBouncer
[pgbouncer] auth_type = scram-sha-256 auth_file = /etc/pgbouncer/userlist.txt auth_user = pgbouncer_auth auth_query = SELECT usename, passwd FROM pg_shadow WHERE usename=$1
Ojo: el default clásico fue pg_shadow, aunque en versiones recientes hubo cambios/ajustes del default auth_query (tema “VALID UNTIL”), así que si tocas auth_query custom, mantén PgBouncer actualizado y revisa changelog/release notes. PgBouncer+1
Pros
userlist.txt queda mínimo (solo pgbouncer_auth + quizá 1-2 más).
Rotación de passwords: la fuente de verdad es Postgres.
Contras
Más moving parts.
Debes asegurar permisos del auth_user y entender SCRAM/credenciales (ver sección 4).
7) Seguridad mínima adicional (la que sí importa)
A) No uses auth_type=trust (salvo laboratorios)
“Trust” es literalmente “sin password”. Incluso guías lo muestran como ejemplo, pero para prod es un NO. Percona
B) Restringe por red (firewall) y por listen_addr
PgBouncer no debería escuchar en 0.0.0.0 si no hace falta.
Permite solo IPs de tus Odoo / tu red privada.
PostgreSQL: permite conexiones solo desde PgBouncer (y desde tu bastión/DBA).
C) Separa credenciales por entorno
odoo_prod, odoo_stage, etc.
Nada de reutilizar contraseñas entre ambientes.
D) TLS si hay red no confiable
Si Odoo↔PgBouncer o PgBouncer↔Postgres cruzan subredes que no controlas, considera TLS. (SCRAM no sustituye cifrado del canal.)
8) Checklist rápido (para pegar al final del post)
Tengo usuario odoo con permisos solo para su DB
userlist.txt con formato correcto ("user" "secret") PgBouncer
userlist.txt con permisos 0600
Evito trust Percona
Puedo recargar PgBouncer tras rotación (reload recarga auth_file) PgBouncer
Si uso auth_user/auth_query, entendí el tema SCRAM y el posible requisito de password en claro para auth_user GitHub+1
Firewall: Postgres acepta solo desde PgBouncer; PgBouncer solo desde Odoo